From 46b36839e2e7cdc2ee9fda9186d7c1d57408cec7 Mon Sep 17 00:00:00 2001 From: James Allen Date: Wed, 12 Feb 2014 17:27:43 +0000 Subject: [PATCH 001/754] Initial open source commit --- services/clsi/.gitignore | 12 + services/clsi/Gruntfile.coffee | 99 + services/clsi/app.coffee | 36 + services/clsi/app/coffee/CommandRunner.coffee | 12 + .../clsi/app/coffee/CompileController.coffee | 40 + .../clsi/app/coffee/CompileManager.coffee | 39 + services/clsi/app/coffee/LatexRunner.coffee | 57 + services/clsi/app/coffee/Metrics.coffee | 23 + .../clsi/app/coffee/OutputFileFinder.coffee | 58 + .../coffee/ProjectPersistenceManager.coffee | 54 + services/clsi/app/coffee/RequestParser.coffee | 74 + .../clsi/app/coffee/ResourceWriter.coffee | 68 + services/clsi/app/coffee/UrlCache.coffee | 113 + services/clsi/app/coffee/UrlFetcher.coffee | 23 + services/clsi/app/coffee/db.coffee | 24 + services/clsi/config/settings.testing.coffee | 35 + services/clsi/package.json | 36 + .../coffee/BrokenLatexFileTests.coffee | 46 + .../coffee/DeleteOldFilesTest.coffee | 34 + .../coffee/ExampleDocumentTests.coffee | 79 + .../coffee/SimpleLatexFileTests.coffee | 39 + .../acceptance/coffee/TimeoutTests.coffee | 27 + .../acceptance/coffee/UrlCachingTests.coffee | 220 + .../acceptance/coffee/helpers/Client.coffee | 69 + .../biber_bibliography/bibliography.bib | 9 + .../examples/biber_bibliography/main.tex | 12 + .../examples/biber_bibliography/output.bbl | 48 + .../examples/biber_bibliography/output.pdf | Bin 0 -> 58996 bytes .../biber_bibliography/output.run.xml | 84 + .../epstopdf/image-eps-converted-to.pdf | Bin 0 -> 26279 bytes .../fixtures/examples/epstopdf/image.eps | 6673 +++++++++++++++++ .../fixtures/examples/epstopdf/main.tex | 10 + .../fixtures/examples/epstopdf/output.pdf | Bin 0 -> 31035 bytes .../fixtures/examples/feynmf/main.tex | 28 + .../fixtures/examples/feynmf/output.pdf | Bin 0 -> 26753 bytes .../fixtures/examples/feynmp/main.tex | 28 + .../fixtures/examples/feynmp/options.json | 3 + .../fixtures/examples/feynmp/output.pdf | Bin 0 -> 6290 bytes .../fixtures/examples/glossaries/main.tex | 17 + .../fixtures/examples/glossaries/output.glg | 7 + .../fixtures/examples/glossaries/output.glo | 1 + .../fixtures/examples/glossaries/output.gls | 6 + .../fixtures/examples/glossaries/output.ist | 29 + .../fixtures/examples/glossaries/output.pdf | Bin 0 -> 34478 bytes .../fixtures/examples/gnuplot/main.tex | 26 + .../fixtures/examples/gnuplot/output.pdf | Bin 0 -> 23013 bytes .../fixtures/examples/knitr/main.Rtex | 13 + .../fixtures/examples/knitr/output.pdf | Bin 0 -> 43271 bytes .../examples/latex_compiler/image.eps | 6673 +++++++++++++++++ .../fixtures/examples/latex_compiler/main.tex | 9 + .../examples/latex_compiler/options.json | 3 + .../examples/latex_compiler/output.dvi | Bin 0 -> 272 bytes .../examples/latex_compiler/output.pdf | Bin 0 -> 23194 bytes .../examples/lualatex_compiler/main.tex | 8 + .../examples/lualatex_compiler/options.json | 3 + .../examples/lualatex_compiler/output.pdf | Bin 0 -> 11405 bytes .../fixtures/examples/makeindex/main.tex | 12 + .../fixtures/examples/makeindex/output.pdf | Bin 0 -> 24562 bytes .../fixtures/examples/minted/main.tex | 10 + .../fixtures/examples/minted/output.pdf | Bin 0 -> 20399 bytes .../multibib_bibliography/bibliography.bib | 15 + .../examples/multibib_bibliography/main.tex | 23 + .../examples/multibib_bibliography/one.bbl | 8 + .../examples/multibib_bibliography/output.bbl | 8 + .../examples/multibib_bibliography/output.pdf | Bin 0 -> 42190 bytes .../references_in_include/chapter1.tex | 1 + .../references_in_include/chapter2.tex | 2 + .../examples/references_in_include/main.tex | 8 + .../examples/references_in_include/output.pdf | Bin 0 -> 16916 bytes .../simple_bibliography/bibliography.bib | 9 + .../examples/simple_bibliography/main.tex | 10 + .../examples/simple_bibliography/output.bbl | 8 + .../examples/simple_bibliography/output.pdf | Bin 0 -> 36989 bytes .../examples/subdirectories/chapter2.tex | 1 + .../examples/subdirectories/output.pdf | Bin 0 -> 47892 bytes .../subdirectory/bibliography.bib | 10 + .../subdirectories/subdirectory/chapter1.tex | 1 + .../subdirectories/subdirectory/image.png | Bin 0 -> 10601 bytes .../subdirectories/subdirectory/main.tex | 19 + .../examples/xelatex_compiler/Zapfino.ttf | Bin 0 -> 528844 bytes .../examples/xelatex_compiler/main.tex | 7 + .../examples/xelatex_compiler/options.json | 3 + .../examples/xelatex_compiler/output.pdf | Bin 0 -> 7863 bytes .../clsi/test/acceptance/fixtures/lion.png | Bin 0 -> 6498 bytes .../clsi/test/smoke/coffee/SmokeTests.coffee | 35 + services/clsi/test/smoke/js/SmokeTests.js | 64 + .../unit/coffee/CompileControllerTests.coffee | 92 + .../unit/coffee/CompileManagerTests.coffee | 73 + .../test/unit/coffee/LatexRunnerTests.coffee | 56 + .../unit/coffee/OutputFileFinderTests.coffee | 41 + .../ProjectPersistenceManagerTests.coffee | 60 + .../unit/coffee/RequestParserTests.coffee | 209 + .../unit/coffee/ResourceWriterTests.coffee | 152 + .../test/unit/coffee/UrlCacheTests.coffee | 200 + .../test/unit/coffee/UrlFetcherTests.coffee | 74 + 95 files changed, 16218 insertions(+) create mode 100644 services/clsi/.gitignore create mode 100644 services/clsi/Gruntfile.coffee create mode 100644 services/clsi/app.coffee create mode 100644 services/clsi/app/coffee/CommandRunner.coffee create mode 100644 services/clsi/app/coffee/CompileController.coffee create mode 100644 services/clsi/app/coffee/CompileManager.coffee create mode 100644 services/clsi/app/coffee/LatexRunner.coffee create mode 100644 services/clsi/app/coffee/Metrics.coffee create mode 100644 services/clsi/app/coffee/OutputFileFinder.coffee create mode 100644 services/clsi/app/coffee/ProjectPersistenceManager.coffee create mode 100644 services/clsi/app/coffee/RequestParser.coffee create mode 100644 services/clsi/app/coffee/ResourceWriter.coffee create mode 100644 services/clsi/app/coffee/UrlCache.coffee create mode 100644 services/clsi/app/coffee/UrlFetcher.coffee create mode 100644 services/clsi/app/coffee/db.coffee create mode 100644 services/clsi/config/settings.testing.coffee create mode 100644 services/clsi/package.json create mode 100644 services/clsi/test/acceptance/coffee/BrokenLatexFileTests.coffee create mode 100644 services/clsi/test/acceptance/coffee/DeleteOldFilesTest.coffee create mode 100644 services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee create mode 100644 services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee create mode 100644 services/clsi/test/acceptance/coffee/TimeoutTests.coffee create mode 100644 services/clsi/test/acceptance/coffee/UrlCachingTests.coffee create mode 100644 services/clsi/test/acceptance/coffee/helpers/Client.coffee create mode 100644 services/clsi/test/acceptance/fixtures/examples/biber_bibliography/bibliography.bib create mode 100644 services/clsi/test/acceptance/fixtures/examples/biber_bibliography/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/biber_bibliography/output.bbl create mode 100644 services/clsi/test/acceptance/fixtures/examples/biber_bibliography/output.pdf create mode 100644 services/clsi/test/acceptance/fixtures/examples/biber_bibliography/output.run.xml create mode 100644 services/clsi/test/acceptance/fixtures/examples/epstopdf/image-eps-converted-to.pdf create mode 100644 services/clsi/test/acceptance/fixtures/examples/epstopdf/image.eps create mode 100644 services/clsi/test/acceptance/fixtures/examples/epstopdf/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/epstopdf/output.pdf create mode 100644 services/clsi/test/acceptance/fixtures/examples/feynmf/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/feynmf/output.pdf create mode 100644 services/clsi/test/acceptance/fixtures/examples/feynmp/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/feynmp/options.json create mode 100644 services/clsi/test/acceptance/fixtures/examples/feynmp/output.pdf create mode 100644 services/clsi/test/acceptance/fixtures/examples/glossaries/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/glossaries/output.glg create mode 100644 services/clsi/test/acceptance/fixtures/examples/glossaries/output.glo create mode 100644 services/clsi/test/acceptance/fixtures/examples/glossaries/output.gls create mode 100644 services/clsi/test/acceptance/fixtures/examples/glossaries/output.ist create mode 100644 services/clsi/test/acceptance/fixtures/examples/glossaries/output.pdf create mode 100644 services/clsi/test/acceptance/fixtures/examples/gnuplot/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/gnuplot/output.pdf create mode 100644 services/clsi/test/acceptance/fixtures/examples/knitr/main.Rtex create mode 100644 services/clsi/test/acceptance/fixtures/examples/knitr/output.pdf create mode 100644 services/clsi/test/acceptance/fixtures/examples/latex_compiler/image.eps create mode 100644 services/clsi/test/acceptance/fixtures/examples/latex_compiler/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/latex_compiler/options.json create mode 100644 services/clsi/test/acceptance/fixtures/examples/latex_compiler/output.dvi create mode 100644 services/clsi/test/acceptance/fixtures/examples/latex_compiler/output.pdf create mode 100644 services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/options.json create mode 100644 services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/output.pdf create mode 100644 services/clsi/test/acceptance/fixtures/examples/makeindex/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/makeindex/output.pdf create mode 100644 services/clsi/test/acceptance/fixtures/examples/minted/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/minted/output.pdf create mode 100644 services/clsi/test/acceptance/fixtures/examples/multibib_bibliography/bibliography.bib create mode 100644 services/clsi/test/acceptance/fixtures/examples/multibib_bibliography/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/multibib_bibliography/one.bbl create mode 100644 services/clsi/test/acceptance/fixtures/examples/multibib_bibliography/output.bbl create mode 100644 services/clsi/test/acceptance/fixtures/examples/multibib_bibliography/output.pdf create mode 100644 services/clsi/test/acceptance/fixtures/examples/references_in_include/chapter1.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/references_in_include/chapter2.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/references_in_include/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/references_in_include/output.pdf create mode 100644 services/clsi/test/acceptance/fixtures/examples/simple_bibliography/bibliography.bib create mode 100644 services/clsi/test/acceptance/fixtures/examples/simple_bibliography/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/simple_bibliography/output.bbl create mode 100644 services/clsi/test/acceptance/fixtures/examples/simple_bibliography/output.pdf create mode 100644 services/clsi/test/acceptance/fixtures/examples/subdirectories/chapter2.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/subdirectories/output.pdf create mode 100644 services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/bibliography.bib create mode 100644 services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/chapter1.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/image.png create mode 100644 services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/xelatex_compiler/Zapfino.ttf create mode 100644 services/clsi/test/acceptance/fixtures/examples/xelatex_compiler/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/xelatex_compiler/options.json create mode 100644 services/clsi/test/acceptance/fixtures/examples/xelatex_compiler/output.pdf create mode 100644 services/clsi/test/acceptance/fixtures/lion.png create mode 100644 services/clsi/test/smoke/coffee/SmokeTests.coffee create mode 100644 services/clsi/test/smoke/js/SmokeTests.js create mode 100644 services/clsi/test/unit/coffee/CompileControllerTests.coffee create mode 100644 services/clsi/test/unit/coffee/CompileManagerTests.coffee create mode 100644 services/clsi/test/unit/coffee/LatexRunnerTests.coffee create mode 100644 services/clsi/test/unit/coffee/OutputFileFinderTests.coffee create mode 100644 services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.coffee create mode 100644 services/clsi/test/unit/coffee/RequestParserTests.coffee create mode 100644 services/clsi/test/unit/coffee/ResourceWriterTests.coffee create mode 100644 services/clsi/test/unit/coffee/UrlCacheTests.coffee create mode 100644 services/clsi/test/unit/coffee/UrlFetcherTests.coffee diff --git a/services/clsi/.gitignore b/services/clsi/.gitignore new file mode 100644 index 0000000000..a039a12bd1 --- /dev/null +++ b/services/clsi/.gitignore @@ -0,0 +1,12 @@ +**.swp +node_modules +app/js +test/unit/js +test/acceptance/js +test/acceptance/fixtures/tmp +compiles +app.js +.DS_Store +*~ +cache +.vagrant diff --git a/services/clsi/Gruntfile.coffee b/services/clsi/Gruntfile.coffee new file mode 100644 index 0000000000..37fa78f188 --- /dev/null +++ b/services/clsi/Gruntfile.coffee @@ -0,0 +1,99 @@ +module.exports = (grunt) -> + grunt.initConfig + coffee: + app_src: + expand: true, + flatten: true, + cwd: "app" + src: ['coffee/*.coffee'], + dest: 'app/js/', + ext: '.js' + + app: + src: "app.coffee" + dest: "app.js" + + unit_tests: + expand: true + cwd: "test/unit/coffee" + src: ["**/*.coffee"] + dest: "test/unit/js/" + ext: ".js" + + acceptance_tests: + expand: true + cwd: "test/acceptance/coffee" + src: ["**/*.coffee"] + dest: "test/acceptance/js/" + ext: ".js" + + smoke_tests: + expand: true + cwd: "test/smoke/coffee" + src: ["**/*.coffee"] + dest: "test/smoke/js" + ext: ".js" + + watch: + app: + files: ['app/coffee/*.coffee'] + tasks: ['coffee'] + + clean: + app: ["app/js/"] + unit_tests: ["test/unit/js"] + acceptance_tests: ["test/acceptance/js"] + smoke_tests: ["test/smoke/js"] + + nodemon: + dev: + options: + file: 'app.js' + + concurrent: + dev: + tasks: ['nodemon', 'watch'] + options: + logConcurrentOutput: true + + mochaTest: + unit: + options: + reporter: "spec" + src: ["test/unit/js/**/*.js"] + acceptance: + options: + reporter: "spec" + timeout: 40000 + src: ["test/acceptance/js/**/*.js"] + smoke: + options: + reported: "spec" + timeout: 10000 + src: ["test/smoke/js/**/*.js"] + + grunt.loadNpmTasks 'grunt-contrib-coffee' + grunt.loadNpmTasks 'grunt-contrib-watch' + grunt.loadNpmTasks 'grunt-contrib-clean' + grunt.loadNpmTasks 'grunt-nodemon' + grunt.loadNpmTasks 'grunt-concurrent' + grunt.loadNpmTasks 'grunt-mocha-test' + grunt.loadNpmTasks 'grunt-shell' + + grunt.registerTask 'compile:app', ['clean:app', 'coffee:app', 'coffee:app_src'] + grunt.registerTask 'run', ['compile:app', 'concurrent'] + + grunt.registerTask 'compile:unit_tests', ['clean:unit_tests', 'coffee:unit_tests'] + grunt.registerTask 'test:unit', ['compile:app', 'compile:unit_tests', 'mochaTest:unit'] + + grunt.registerTask 'compile:acceptance_tests', ['clean:acceptance_tests', 'coffee:acceptance_tests'] + grunt.registerTask 'test:acceptance', ['compile:acceptance_tests', 'mochaTest:acceptance'] + + grunt.registerTask 'compile:smoke_tests', ['clean:smoke_tests', 'coffee:smoke_tests'] + grunt.registerTask 'test:smoke', ['compile:smoke_tests', 'mochaTest:smoke'] + + grunt.registerTask 'install', 'compile:app' + + grunt.registerTask 'default', ['run'] + + diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee new file mode 100644 index 0000000000..3869bf8132 --- /dev/null +++ b/services/clsi/app.coffee @@ -0,0 +1,36 @@ +CompileController = require "./app/js/CompileController" +Settings = require "settings-sharelatex" +logger = require "logger-sharelatex" +logger.initialize("clsi") +smokeTest = require "smoke-test-sharelatex" + +ProjectPersistenceManager = require "./app/js/ProjectPersistenceManager" + +require("./app/js/db").sync() + +express = require "express" +app = express() + +app.post "/project/:project_id/compile", express.bodyParser(), CompileController.compile +app.del "/project/:project_id", CompileController.clearCache + +staticServer = express.static(Settings.path.compilesDir) +app.get "/project/:project_id/output/*", (req, res, next) -> + req.url = "/#{req.params.project_id}/#{req.params[0]}" + staticServer(req, res, next) + +app.get "/status", (req, res, next) -> + res.send "CLSI is alive\n" + +app.get "/health_check", smokeTest.run(require.resolve(__dirname + "/test/smoke/js/SmokeTests.js")) + +app.use (error, req, res, next) -> + logger.error err: error, "server error" + res.send 500 + +app.listen port = (Settings.internal?.clsi?.port or 3013), host = (Settings.internal?.clsi?.host or "localhost"), (error) -> + logger.log "CLSI listening on #{host}:#{port}" + +setInterval () -> + ProjectPersistenceManager.clearExpiredProjects() +, tenMinutes = 10 * 60 * 1000 diff --git a/services/clsi/app/coffee/CommandRunner.coffee b/services/clsi/app/coffee/CommandRunner.coffee new file mode 100644 index 0000000000..55dec333d6 --- /dev/null +++ b/services/clsi/app/coffee/CommandRunner.coffee @@ -0,0 +1,12 @@ +spawn = require("child_process").spawn +logger = require "logger-sharelatex" + +module.exports = CommandRunner = + run: (project_id, command, directory, timeout, callback = (error) ->) -> + command = (arg.replace('$COMPILE_DIR', directory) for arg in command) + logger.log project_id: project_id, command: command, directory: directory, "running command" + logger.warn "timeouts and sandboxing are not enabled with CommandRunner" + + proc = spawn command[0], command.slice(1), stdio: "inherit", cwd: directory + proc.on "close", () -> + callback() \ No newline at end of file diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee new file mode 100644 index 0000000000..c513450e8d --- /dev/null +++ b/services/clsi/app/coffee/CompileController.coffee @@ -0,0 +1,40 @@ +RequestParser = require "./RequestParser" +CompileManager = require "./CompileManager" +Settings = require "settings-sharelatex" +Metrics = require "./Metrics" +ProjectPersistenceManager = require "./ProjectPersistenceManager" +logger = require "logger-sharelatex" + +module.exports = CompileController = + compile: (req, res, next = (error) ->) -> + timer = new Metrics.Timer("compile-request") + RequestParser.parse req.body, (error, request) -> + return next(error) if error? + request.project_id = req.params.project_id + ProjectPersistenceManager.markProjectAsJustAccessed request.project_id, (error) -> + return next(error) if error? + CompileManager.doCompile request, (error, outputFiles = []) -> + if error? + logger.error err: error, project_id: request.project_id, "error running compile" + error = error.message or error + status = "failure" + else + status = "failure" + for file in outputFiles + if file.type == "pdf" + status = "success" + + timer.done() + res.send JSON.stringify { + compile: + status: status + error: error + outputFiles: outputFiles.map (file) -> + url: "#{Settings.apis.clsi.url}/project/#{request.project_id}/output/#{file.path}" + type: file.type + } + + clearCache: (req, res, next = (error) ->) -> + ProjectPersistenceManager.clearProject req.params.project_id, (error) -> + return next(error) if error? + res.send 204 # No content diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee new file mode 100644 index 0000000000..9d2bd2acc2 --- /dev/null +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -0,0 +1,39 @@ +ResourceWriter = require "./ResourceWriter" +LatexRunner = require "./LatexRunner" +OutputFileFinder = require "./OutputFileFinder" +Settings = require("settings-sharelatex") +Path = require "path" +logger = require "logger-sharelatex" +Metrics = require "./Metrics" +rimraf = require "rimraf" + +module.exports = CompileManager = + doCompile: (request, callback = (error, outputFiles) ->) -> + compileDir = Path.join(Settings.path.compilesDir, request.project_id) + + timer = new Metrics.Timer("write-to-disk") + logger.log project_id: request.project_id, "starting compile" + ResourceWriter.syncResourcesToDisk request.project_id, request.resources, compileDir, (error) -> + return callback(error) if error? + logger.log project_id: request.project_id, time_taken: Date.now() - timer.start, "written files to disk" + timer.done() + + timer = new Metrics.Timer("run-compile") + Metrics.inc("compiles") + LatexRunner.runLatex request.project_id, { + directory: compileDir + mainFile: request.rootResourcePath + compiler: request.compiler + timeout: request.timeout + }, (error) -> + return callback(error) if error? + logger.log project_id: request.project_id, time_taken: Date.now() - timer.start, "done compile" + timer.done() + + OutputFileFinder.findOutputFiles request.resources, compileDir, (error, outputFiles) -> + return callback(error) if error? + callback null, outputFiles + + clearProject: (project_id, callback = (error) ->) -> + compileDir = Path.join(Settings.compileDir, project_id) + rimraf compileDir, callback diff --git a/services/clsi/app/coffee/LatexRunner.coffee b/services/clsi/app/coffee/LatexRunner.coffee new file mode 100644 index 0000000000..8f032cdad8 --- /dev/null +++ b/services/clsi/app/coffee/LatexRunner.coffee @@ -0,0 +1,57 @@ +Path = require "path" +Settings = require "settings-sharelatex" +logger = require "logger-sharelatex" +Metrics = require "./Metrics" +CommandRunner = require(Settings.clsi?.commandRunner or "./CommandRunner") + +module.exports = LatexRunner = + runLatex: (project_id, options, callback = (error) ->) -> + {directory, mainFile, compiler, timeout} = options + compiler ||= "pdflatex" + timeout ||= 60000 # milliseconds + + logger.log directory: directory, compiler: compiler, timeout: timeout, mainFile: mainFile, "starting compile" + + # We want to run latexmk on the tex file which we it will automatically + # generate from the Rtex file. + mainFile = mainFile.replace(/\.Rtex$/, ".tex") + + if compiler == "pdflatex" + command = LatexRunner._pdflatexCommand mainFile + else if compiler == "latex" + command = LatexRunner._latexCommand mainFile + else if compiler == "xelatex" + command = LatexRunner._xelatexCommand mainFile + else if compiler == "lualatex" + command = LatexRunner._lualatexCommand mainFile + else + return callback new Error("unknown compiler: #{compiler}") + + CommandRunner.run project_id, command, directory, timeout, callback + + _latexmkBaseCommand: [ "latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR"] + + _pdflatexCommand: (mainFile) -> + LatexRunner._latexmkBaseCommand.concat [ + "-pdf", "-e", "$pdflatex='pdflatex -interaction=batchmode %O %S'", + Path.join("$COMPILE_DIR", mainFile) + ] + + _latexCommand: (mainFile) -> + LatexRunner._latexmkBaseCommand.concat [ + "-pdfdvi", "-e", "$latex='latex -interaction=batchmode %O %S'", + Path.join("$COMPILE_DIR", mainFile) + ] + + _xelatexCommand: (mainFile) -> + LatexRunner._latexmkBaseCommand.concat [ + "-xelatex", "-e", "$pdflatex='xelatex -interaction=batchmode %O %S'", + Path.join("$COMPILE_DIR", mainFile) + ] + + _lualatexCommand: (mainFile) -> + LatexRunner._latexmkBaseCommand.concat [ + "-pdf", "-e", "$pdflatex='lualatex -interaction=batchmode %O %S'", + Path.join("$COMPILE_DIR", mainFile) + ] + diff --git a/services/clsi/app/coffee/Metrics.coffee b/services/clsi/app/coffee/Metrics.coffee new file mode 100644 index 0000000000..107c5ffc7e --- /dev/null +++ b/services/clsi/app/coffee/Metrics.coffee @@ -0,0 +1,23 @@ +StatsD = require('lynx') +statsd = new StatsD('localhost', 8125, {on_error:->}) + +buildKey = (key)-> "clsi.#{process.env.NODE_ENV or "testing"}.#{key}" + +module.exports = + set : (key, value, sampleRate = 1)-> + statsd.set buildKey(key), value, sampleRate + + inc : (key, sampleRate = 1)-> + statsd.increment buildKey(key), sampleRate + + Timer : class + constructor :(key, sampleRate = 1)-> + this.start = new Date() + this.key = buildKey(key) + done:-> + timeSpan = new Date - this.start + statsd.timing(this.key, timeSpan, this.sampleRate) + + gauge : (key, value, sampleRate = 1)-> + statsd.gauge key, value, sampleRate + diff --git a/services/clsi/app/coffee/OutputFileFinder.coffee b/services/clsi/app/coffee/OutputFileFinder.coffee new file mode 100644 index 0000000000..c04edbc28f --- /dev/null +++ b/services/clsi/app/coffee/OutputFileFinder.coffee @@ -0,0 +1,58 @@ +async = require "async" +fs = require "fs" +Path = require "path" +wrench = require "wrench" + +module.exports = OutputFileFinder = + findOutputFiles: (resources, directory, callback = (error, outputFiles) ->) -> + incomingResources = {} + for resource in resources + incomingResources[resource.path] = true + + OutputFileFinder._getAllFiles directory, (error, allFiles) -> + jobs = [] + outputFiles = [] + for file in allFiles + do (file) -> + jobs.push (callback) -> + if incomingResources[file.path] + return callback() + else + OutputFileFinder._isDirectory Path.join(directory, file.path), (error, directory) -> + return callback(error) if error? + if !directory + outputFiles.push file + callback() + + async.series jobs, (error) -> + return callback(error) if error? + callback null, outputFiles + + _isDirectory: (path, callback = (error, directory) ->) -> + fs.stat path, (error, stat) -> + callback error, stat?.isDirectory() + + _getAllFiles: (directory, _callback = (error, outputFiles) ->) -> + callback = (error, outputFiles) -> + _callback(error, outputFiles) + _callback = () -> + + outputFiles = [] + + wrench.readdirRecursive directory, (error, files) => + if error? + if error.code == "ENOENT" + # Directory doesn't exist, which is not a problem + return callback(null, []) + else + return callback(error) + + # readdirRecursive returns multiple times and finishes with a null response + if !files? + return callback(null, outputFiles) + + for file in files + outputFiles.push + path: file + type: file.match(/\.([^\.]+)$/)?[1] + diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.coffee b/services/clsi/app/coffee/ProjectPersistenceManager.coffee new file mode 100644 index 0000000000..8b37947317 --- /dev/null +++ b/services/clsi/app/coffee/ProjectPersistenceManager.coffee @@ -0,0 +1,54 @@ +UrlCache = require "./UrlCache" +CompileManager = require "./CompileManager" +db = require "./db" +async = require "async" +logger = require "logger-sharelatex" + +module.exports = ProjectPersistenceManager = + EXPIRY_TIMEOUT: oneDay = 24 * 60 * 60 * 1000 #ms + + markProjectAsJustAccessed: (project_id, callback = (error) ->) -> + db.Project.findOrCreate(project_id: project_id) + .success( + (project) -> + project.updateAttributes(lastAccessed: new Date()) + .success(() -> callback()) + .error callback + ) + .error callback + + clearExpiredProjects: (callback = (error) ->) -> + ProjectPersistenceManager._findExpiredProjectIds (error, project_ids) -> + return callback(error) if error? + logger.log project_ids: project_ids, "clearing expired projects" + jobs = for project_id in (project_ids or []) + do (project_id) -> + (callback) -> + ProjectPersistenceManager.clearProject project_id, (err) -> + if err? + logger.error err: err, project_id: project_id, "error clearing project" + callback() + async.series jobs, callback + + clearProject: (project_id, callback = (error) ->) -> + logger.log project_id: project_id, "clearing project" + CompileManager.clearProject project_id, (error) -> + return callback(error) if error? + UrlCache.clearProject project_id, (error) -> + return callback(error) if error? + ProjectPersistenceManager._clearProjectFromDatabase project_id, (error) -> + return callback(error) if error? + callback() + + _clearProjectFromDatabase: (project_id, callback = (error) ->) -> + db.Project.destroy(project_id: project_id) + .success(() -> callback()) + .error callback + + _findExpiredProjectIds: (callback = (error, project_ids) ->) -> + db.Project.findAll(where: ["lastAccessed < ?", new Date(Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT)]) + .success( + (projects) -> + callback null, projects.map((project) -> project.project_id) + ) + .error callback diff --git a/services/clsi/app/coffee/RequestParser.coffee b/services/clsi/app/coffee/RequestParser.coffee new file mode 100644 index 0000000000..66765fb180 --- /dev/null +++ b/services/clsi/app/coffee/RequestParser.coffee @@ -0,0 +1,74 @@ +module.exports = RequestParser = + VALID_COMPILERS: ["pdflatex", "latex", "xelatex", "lualatex"] + MAX_TIMEOUT: 60 + + parse: (body, callback = (error, data) ->) -> + response = {} + + if !body.compile? + return callback "top level object should have a compile attribute" + + compile = body.compile + compile.options ||= {} + + try + response.compiler = @_parseAttribute "compiler", + compile.options.compiler, + validValues: @VALID_COMPILERS + default: "pdflatex" + type: "string" + response.timeout = @_parseAttribute "timeout", + compile.options.timeout + default: RequestParser.MAX_TIMEOUT + type: "number" + + if response.timeout > RequestParser.MAX_TIMEOUT + response.timeout = RequestParser.MAX_TIMEOUT + response.timeout = response.timeout * 1000 # milliseconds + + response.resources = (@_parseResource(resource) for resource in (compile.resources or [])) + response.rootResourcePath = @_parseAttribute "rootResourcePath", + compile.rootResourcePath + default: "main.tex" + type: "string" + catch error + return callback error + + callback null, response + + _parseResource: (resource) -> + if !resource.path? or typeof resource.path != "string" + throw "all resources should have a path attribute" + + if resource.modified? + modified = new Date(resource.modified) + if isNaN(modified.getTime()) + throw "resource modified date could not be understood: #{resource.modified}" + + if !resource.url? and !resource.content? + throw "all resources should have either a url or content attribute" + if resource.content? and typeof resource.content != "string" + throw "content attribute should be a string" + if resource.url? and typeof resource.url != "string" + throw "url attribute should be a string" + + return { + path: resource.path + modified: modified + url: resource.url + content: resource.content + } + + _parseAttribute: (name, attribute, options) -> + if attribute? + if options.validValues? + if options.validValues.indexOf(attribute) == -1 + throw "#{name} attribute should be one of: #{options.validValues.join(", ")}" + if options.type? + if typeof attribute != options.type + throw "#{name} attribute should be a #{options.type}" + else + return options.default if options.default? + throw "Default not implemented" + return attribute + diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee new file mode 100644 index 0000000000..78667013a8 --- /dev/null +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -0,0 +1,68 @@ +UrlCache = require "./UrlCache" +Path = require "path" +fs = require "fs" +async = require "async" +mkdirp = require "mkdirp" +OutputFileFinder = require "./OutputFileFinder" +Metrics = require "./Metrics" + +module.exports = ResourceWriter = + syncResourcesToDisk: (project_id, resources, basePath, callback = (error) ->) -> + @_removeExtraneousFiles resources, basePath, (error) => + return callback(error) if error? + jobs = for resource in resources + do (resource) => + (callback) => @_writeResourceToDisk(project_id, resource, basePath, callback) + async.series jobs, callback + + _removeExtraneousFiles: (resources, basePath, _callback = (error) ->) -> + timer = new Metrics.Timer("unlink-output-files") + callback = (error) -> + timer.done() + _callback(error) + + OutputFileFinder.findOutputFiles resources, basePath, (error, outputFiles) -> + return callback(error) if error? + + jobs = [] + for file in outputFiles or [] + do (file) -> + path = file.path + should_delete = true + if path.match(/^output\./) or path.match(/\.aux$/) + should_delete = false + if path == "output.pdf" or path == "output.dvi" or path == "output.log" + should_delete = true + if should_delete + jobs.push (callback) -> ResourceWriter._deleteFileIfNotDirectory Path.join(basePath, path), callback + + async.series jobs, callback + + _deleteFileIfNotDirectory: (path, callback = (error) ->) -> + fs.stat path, (error, stat) -> + return callback(error) if error? + if stat.isFile() + fs.unlink path, callback + else + callback() + + _writeResourceToDisk: (project_id, resource, basePath, callback = (error) ->) -> + path = Path.normalize(Path.join(basePath, resource.path)) + if (path.slice(0, basePath.length) != basePath) + return callback new Error("resource path is outside root directory") + + mkdirp Path.dirname(path), (error) -> + return callback(error) if error? + # TODO: Don't overwrite file if it hasn't been modified + if resource.url? + UrlCache.downloadUrlToFile( + project_id, + resource.url, + path, + resource.modified, + callback + ) + else + fs.writeFile path, resource.content, callback + + diff --git a/services/clsi/app/coffee/UrlCache.coffee b/services/clsi/app/coffee/UrlCache.coffee new file mode 100644 index 0000000000..1836a4e73c --- /dev/null +++ b/services/clsi/app/coffee/UrlCache.coffee @@ -0,0 +1,113 @@ +db = require("./db") +UrlFetcher = require("./UrlFetcher") +Settings = require("settings-sharelatex") +crypto = require("crypto") +fs = require("fs") +logger = require "logger-sharelatex" +async = require "async" + +module.exports = UrlCache = + downloadUrlToFile: (project_id, url, destPath, lastModified, callback = (error) ->) -> + UrlCache._ensureUrlIsInCache project_id, url, lastModified, (error, pathToCachedUrl) => + return callback(error) if error? + UrlCache._copyFile(pathToCachedUrl, destPath, callback) + + clearProject: (project_id, callback = (error) ->) -> + UrlCache._findAllUrlsInProject project_id, (error, urls) -> + logger.log project_id: project_id, url_count: urls.length, "clearing project URLs" + return callback(error) if error? + jobs = for url in (urls or []) + do (url) -> + (callback) -> + UrlCache._clearUrlFromCache project_id, url, (error) -> + if error? + logger.error err: error, project_id: project_id, url: url, "error clearing project URL" + callback() + async.series jobs, callback + + _ensureUrlIsInCache: (project_id, url, lastModified, callback = (error, pathOnDisk) ->) -> + if lastModified? + # MYSQL only stores dates to an accuracy of a second but the incoming lastModified might have milliseconds. + # So round down to seconds + lastModified = new Date(Math.floor(lastModified.getTime() / 1000) * 1000) + UrlCache._doesUrlNeedDownloading project_id, url, lastModified, (error, needsDownloading) => + return callback(error) if error? + if needsDownloading + logger.log url: url, lastModified: lastModified, "downloading URL" + UrlFetcher.pipeUrlToFile url, UrlCache._cacheFilePathForUrl(project_id, url), (error) => + return callback(error) if error? + UrlCache._updateOrCreateUrlDetails project_id, url, lastModified, (error) => + return callback(error) if error? + callback null, UrlCache._cacheFilePathForUrl(project_id, url) + else + logger.log url: url, lastModified: lastModified, "URL is up to date in cache" + callback null, UrlCache._cacheFilePathForUrl(project_id, url) + + _doesUrlNeedDownloading: (project_id, url, lastModified, callback = (error, needsDownloading) ->) -> + if !lastModified? + return callback null, true + + UrlCache._findUrlDetails project_id, url, (error, urlDetails) -> + return callback(error) if error? + if !urlDetails? or !urlDetails.lastModified? or urlDetails.lastModified.getTime() < lastModified.getTime() + return callback null, true + else + return callback null, false + + _cacheFileNameForUrl: (project_id, url) -> + project_id + ":" + crypto.createHash("md5").update(url).digest("hex") + + _cacheFilePathForUrl: (project_id, url) -> + "#{Settings.path.clsiCacheDir}/#{UrlCache._cacheFileNameForUrl(project_id, url)}" + + _copyFile: (from, to, _callback = (error) ->) -> + callbackOnce = (error) -> + _callback(error) + _callback = () -> + writeStream = fs.createWriteStream(to) + readStream = fs.createReadStream(from) + writeStream.on "error", callbackOnce + readStream.on "error", callbackOnce + writeStream.on "close", () -> callbackOnce() + readStream.pipe(writeStream) + + _clearUrlFromCache: (project_id, url, callback = (error) ->) -> + UrlCache._clearUrlDetails project_id, url, (error) -> + return callback(error) if error? + UrlCache._deleteUrlCacheFromDisk project_id, url, (error) -> + return callback(error) if error? + callback null + + _deleteUrlCacheFromDisk: (project_id, url, callback = (error) ->) -> + fs.unlink UrlCache._cacheFilePathForUrl(project_id, url), callback + + _findUrlDetails: (project_id, url, callback = (error, urlDetails) ->) -> + db.UrlCache.find(where: { url: url, project_id: project_id }) + .success((urlDetails) -> callback null, urlDetails) + .error callback + + _updateOrCreateUrlDetails: (project_id, url, lastModified, callback = (error) ->) -> + db.UrlCache.findOrCreate(url: url, project_id: project_id) + .success( + (urlDetails) -> + urlDetails.updateAttributes(lastModified: lastModified) + .success(() -> callback()) + .error(callback) + ) + .error callback + + _clearUrlDetails: (project_id, url, callback = (error) ->) -> + db.UrlCache.destroy(url: url, project_id: project_id) + .success(() -> callback null) + .error callback + + _findAllUrlsInProject: (project_id, callback = (error, urls) ->) -> + db.UrlCache.findAll(where: { project_id: project_id }) + .success( + (urlEntries) -> + callback null, urlEntries.map((entry) -> entry.url) + ) + .error callback + + + diff --git a/services/clsi/app/coffee/UrlFetcher.coffee b/services/clsi/app/coffee/UrlFetcher.coffee new file mode 100644 index 0000000000..a89e37e9d2 --- /dev/null +++ b/services/clsi/app/coffee/UrlFetcher.coffee @@ -0,0 +1,23 @@ +request = require("request").defaults(jar: false) +fs = require("fs") + +module.exports = UrlFetcher = + pipeUrlToFile: (url, filePath, _callback = (error) ->) -> + callbackOnce = (error) -> + _callback(error) + _callback = () -> + + urlStream = request.get(url) + fileStream = fs.createWriteStream(filePath) + + urlStream.on "response", (res) -> + if res.statusCode >= 200 and res.statusCode < 300 + urlStream.pipe(fileStream) + else + callbackOnce(new Error("URL returned non-success status code: #{res.statusCode}")) + + urlStream.on "error", (error) -> + callbackOnce(error or new Error("Something went wrong downloading the URL")) + + urlStream.on "end", () -> + callbackOnce() diff --git a/services/clsi/app/coffee/db.coffee b/services/clsi/app/coffee/db.coffee new file mode 100644 index 0000000000..5438fc594b --- /dev/null +++ b/services/clsi/app/coffee/db.coffee @@ -0,0 +1,24 @@ +Sequelize = require("sequelize") +Settings = require("settings-sharelatex") + +sequelize = new Sequelize( + Settings.mysql.clsi.database, + Settings.mysql.clsi.username, + Settings.mysql.clsi.password, + Settings.mysql.clsi +) + +module.exports = + UrlCache: sequelize.define("UrlCache", { + url: Sequelize.STRING + project_id: Sequelize.STRING + lastModified: Sequelize.DATE + }) + + Project: sequelize.define("Project", { + project_id: Sequelize.STRING + lastAccessed: Sequelize.DATE + }) + + sync: () -> sequelize.sync() + diff --git a/services/clsi/config/settings.testing.coffee b/services/clsi/config/settings.testing.coffee new file mode 100644 index 0000000000..9407a50906 --- /dev/null +++ b/services/clsi/config/settings.testing.coffee @@ -0,0 +1,35 @@ +Path = require "path" + +module.exports = + # Options are passed to Sequelize. + # See http://sequelizejs.com/documentation#usage-options for details + mysql: + clsi: + database: "clsi" + username: "clsi" + password: null + + + path: + compilesDir: Path.resolve(__dirname + "/../compiles") + clsiCacheDir: Path.resolve(__dirname + "/../cache") + + clsi: + # commandRunner: "docker-runner-sharelatex" + # docker: + # image: "quay.io/sharelatex/texlive-full" + # env: + # PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2013/bin/x86_64-linux/" + # HOME: "/tmp" + # socketPath: "/var/run/docker.sock" + # user: "tex" + + internal: + clsi: + port: 3013 + host: "" + + apis: + clsi: + url: "http://localhost:3013" + diff --git a/services/clsi/package.json b/services/clsi/package.json new file mode 100644 index 0000000000..0cfaeadc11 --- /dev/null +++ b/services/clsi/package.json @@ -0,0 +1,36 @@ +{ + "name": "node-clsi", + "description": "A Node.js implementation of the CLSI LaTeX web-API", + "version": "0.0.1-dev", + "author": "James Allen ", + "dependencies": { + "async": "0.2.9", + "express": "3.3.1", + "lynx": "0.0.11", + "mkdirp": "0.3.5", + "mysql": "2.0.0-alpha7", + "request": "~2.21.0", + "rimraf": "2.1.4", + "logger-sharelatex": "git+ssh://git@bitbucket.org:sharelatex/logger-sharelatex.git#bunyan", + "settings-sharelatex": "git+ssh://git@bitbucket.org:sharelatex/settings-sharelatex.git#master", + "sequelize": "~2.0.0-beta.2", + "wrench": "~1.5.4", + "smoke-test-sharelatex": "git+ssh://git@bitbucket.org:sharelatex/smoke-test-sharelatex.git#master" + }, + "devDependencies": { + "mocha": "1.10.0", + "coffee-script": "1.6.0", + "chai": "~1.8.1", + "sinon": "~1.7.3", + "grunt": "~0.4.2", + "grunt-contrib-coffee": "~0.7.0", + "grunt-contrib-watch": "~0.5.3", + "grunt-concurrent": "~0.4.2", + "grunt-nodemon": "~0.1.2", + "grunt-contrib-clean": "~0.5.0", + "grunt-shell": "~0.6.1", + "grunt-mocha-test": "~0.8.1", + "sandboxed-module": "~0.3.0", + "timekeeper": "0.0.4" + } +} diff --git a/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.coffee b/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.coffee new file mode 100644 index 0000000000..5a92d5fcfb --- /dev/null +++ b/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.coffee @@ -0,0 +1,46 @@ +Client = require "./helpers/Client" +request = require "request" +require("chai").should() + +describe "Broken LaTeX file", -> + before -> + @broken_request = + resources: [ + path: "main.tex" + content: ''' + \\documentclass{articl % :( + \\begin{documen % :( + Broken + \\end{documen % :( + ''' + ] + @correct_request = + resources: [ + path: "main.tex" + content: ''' + \\documentclass{article} + \\begin{document} + Hello world + \\end{document} + ''' + ] + + describe "on first run", -> + before (done) -> + @project_id = Client.randomId() + Client.compile @project_id, @broken_request, (@error, @res, @body) => done() + + it "should return a failure status", -> + @body.compile.status.should.equal "failure" + + describe "on second run", -> + before (done) -> + @project_id = Client.randomId() + Client.compile @project_id, @correct_request, () => + Client.compile @project_id, @broken_request, (@error, @res, @body) => + done() + + it "should return a failure status", -> + @body.compile.status.should.equal "failure" + + diff --git a/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.coffee b/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.coffee new file mode 100644 index 0000000000..b8a1ff37f4 --- /dev/null +++ b/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.coffee @@ -0,0 +1,34 @@ +Client = require "./helpers/Client" +request = require "request" +require("chai").should() + +describe "Deleting Old Files", -> + before -> + @request = + resources: [ + path: "main.tex" + content: ''' + \\documentclass{article} + \\begin{document} + Hello world + \\end{document} + ''' + ] + + describe "on first run", -> + before (done) -> + @project_id = Client.randomId() + Client.compile @project_id, @request, (@error, @res, @body) => done() + + it "should return a success status", -> + @body.compile.status.should.equal "success" + + describe "after file has been deleted", -> + before (done) -> + @request.resources = [] + Client.compile @project_id, @request, (@error, @res, @body) => + done() + + it "should return a failure status", -> + @body.compile.status.should.equal "failure" + diff --git a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee new file mode 100644 index 0000000000..448d22fa06 --- /dev/null +++ b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee @@ -0,0 +1,79 @@ +Client = require "./helpers/Client" +request = require "request" +require("chai").should() +fs = require "fs" +ChildProcess = require "child_process" + +fixturePath = (path) -> __dirname + "/../fixtures/" + path + +convertToPng = (pdfPath, pngPath, callback = (error) ->) -> + convert = ChildProcess.exec "convert #{fixturePath(pdfPath)} #{fixturePath(pngPath)}" + convert.on "exit", () -> + callback() + +compare = (originalPath, generatedPath, callback = (error, same) ->) -> + proc = ChildProcess.exec "compare -metric mae #{fixturePath(originalPath)} #{fixturePath(generatedPath)} #{fixturePath("tmp/diff.png")}" + stderr = "" + proc.stderr.on "data", (chunk) -> stderr += chunk + proc.on "exit", () -> + if stderr.trim() == "0 (0)" + callback null, true + else + console.log stderr + callback null, false + +compareMultiplePages = (project_id, callback = (error) ->) -> + compareNext = (page_no, callback) -> + path = "tmp/#{project_id}-source-#{page_no}.png" + fs.stat fixturePath(path), (error, stat) -> + if error? + callback() + else + compare "tmp/#{project_id}-source-#{page_no}.png", "tmp/#{project_id}-generated-#{page_no}.png", (error, same) => + throw error if error? + same.should.equal true + compareNext page_no + 1, callback + compareNext 0, callback + +downloadAndComparePdf = (project_id, example_dir, url, callback = (error) ->) -> + writeStream = fs.createWriteStream(fixturePath("tmp/#{project_id}.pdf")) + request.get(url).pipe(writeStream) + writeStream.on "close", () => + convertToPng "tmp/#{project_id}.pdf", "tmp/#{project_id}-generated.png", (error) => + throw error if error? + convertToPng "examples/#{example_dir}/output.pdf", "tmp/#{project_id}-source.png", (error) => + throw error if error? + fs.stat fixturePath("tmp/#{project_id}-source-0.png"), (error, stat) => + if error? + compare "tmp/#{project_id}-source.png", "tmp/#{project_id}-generated.png", (error, same) => + throw error if error? + same.should.equal true + callback() + else + compareMultiplePages project_id, (error) -> + throw error if error? + callback() + +Client.runServer(4242, fixturePath("examples")) + +describe "Example Documents", -> + before (done) -> + ChildProcess.exec("rm test/acceptance/fixtures/tmp/*").on "exit", () -> done() + + for example_dir in fs.readdirSync fixturePath("examples") + do (example_dir) -> + describe example_dir, -> + before -> + @project_id = Client.randomId() + + it "should generate the correct pdf", (done) -> + Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => + pdf = Client.getOutputFile body, "pdf" + downloadAndComparePdf(@project_id, example_dir, pdf.url, done) + + it "should generate the correct pdf on the second run as well", (done) -> + Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => + pdf = Client.getOutputFile body, "pdf" + downloadAndComparePdf(@project_id, example_dir, pdf.url, done) + + diff --git a/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee b/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee new file mode 100644 index 0000000000..2693f63eab --- /dev/null +++ b/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee @@ -0,0 +1,39 @@ +Client = require "./helpers/Client" +request = require "request" +require("chai").should() + +describe "Simple LaTeX file", -> + before (done) -> + @project_id = Client.randomId() + @request = + resources: [ + path: "main.tex" + content: ''' + \\documentclass{article} + \\begin{document} + Hello world + \\end{document} + ''' + ] + Client.compile @project_id, @request, (@error, @res, @body) => done() + + it "should return the PDF", -> + pdf = Client.getOutputFile(@body, "pdf") + pdf.type.should.equal "pdf" + + it "should return the log", -> + log = Client.getOutputFile(@body, "log") + log.type.should.equal "log" + + it "should provide the pdf for download", (done) -> + pdf = Client.getOutputFile(@body, "pdf") + request.get pdf.url, (error, res, body) -> + res.statusCode.should.equal 200 + done() + + it "should provide the log for download", (done) -> + log = Client.getOutputFile(@body, "pdf") + request.get log.url, (error, res, body) -> + res.statusCode.should.equal 200 + done() + diff --git a/services/clsi/test/acceptance/coffee/TimeoutTests.coffee b/services/clsi/test/acceptance/coffee/TimeoutTests.coffee new file mode 100644 index 0000000000..dd87e61458 --- /dev/null +++ b/services/clsi/test/acceptance/coffee/TimeoutTests.coffee @@ -0,0 +1,27 @@ +Client = require "./helpers/Client" +request = require "request" +require("chai").should() + +describe "Timed out compile", -> + before (done) -> + @request = + options: + timeout: 0.01 #seconds + resources: [ + path: "main.tex" + content: ''' + \\documentclass{article} + \\begin{document} + Hello world + \\end{document} + ''' + ] + @project_id = Client.randomId() + Client.compile @project_id, @request, (@error, @res, @body) => done() + + it "should return a timeout error", -> + @body.compile.error.should.equal "container timed out" + + it "should return a failure status", -> + @body.compile.status.should.equal "failure" + diff --git a/services/clsi/test/acceptance/coffee/UrlCachingTests.coffee b/services/clsi/test/acceptance/coffee/UrlCachingTests.coffee new file mode 100644 index 0000000000..9e6f3d627d --- /dev/null +++ b/services/clsi/test/acceptance/coffee/UrlCachingTests.coffee @@ -0,0 +1,220 @@ +Client = require "./helpers/Client" +request = require "request" +require("chai").should() +sinon = require "sinon" + +host = "localhost" + +Server = + run: () -> + express = require "express" + app = express() + + staticServer = express.static __dirname + "/../fixtures/" + app.get "/:random_id/*", (req, res, next) => + @getFile(req.url) + req.url = "/" + req.params[0] + staticServer(req, res, next) + + app.listen 31415, host + + getFile: () -> + + randomId: () -> + Math.random().toString(16).slice(2) + +Server.run() + +describe "Url Caching", -> + describe "Downloading an image for the first time", -> + before (done) -> + @project_id = Client.randomId() + @file = "#{Server.randomId()}/lion.png" + @request = + resources: [{ + path: "main.tex" + content: ''' + \\documentclass{article} + \\usepackage{graphicx} + \\begin{document} + \\includegraphics{lion.png} + \\end{document} + ''' + }, { + path: "lion.png" + url: "http://#{host}:31415/#{@file}" + }] + + sinon.spy Server, "getFile" + Client.compile @project_id, @request, (@error, @res, @body) => done() + + afterEach -> + Server.getFile.restore() + + it "should download the image", -> + Server.getFile + .calledWith("/" + @file) + .should.equal true + + describe "When an image is in the cache and the last modified date is unchanged", -> + before (done) -> + @project_id = Client.randomId() + @file = "#{Server.randomId()}/lion.png" + @request = + resources: [{ + path: "main.tex" + content: ''' + \\documentclass{article} + \\usepackage{graphicx} + \\begin{document} + \\includegraphics{lion.png} + \\end{document} + ''' + }, @image_resource = { + path: "lion.png" + url: "http://#{host}:31415/#{@file}" + modified: Date.now() + }] + + Client.compile @project_id, @request, (@error, @res, @body) => + sinon.spy Server, "getFile" + Client.compile @project_id, @request, (@error, @res, @body) => + done() + + after -> + Server.getFile.restore() + + it "should not download the image again", -> + Server.getFile.called.should.equal false + + describe "When an image is in the cache and the last modified date is advanced", -> + before (done) -> + @project_id = Client.randomId() + @file = "#{Server.randomId()}/lion.png" + @request = + resources: [{ + path: "main.tex" + content: ''' + \\documentclass{article} + \\usepackage{graphicx} + \\begin{document} + \\includegraphics{lion.png} + \\end{document} + ''' + }, @image_resource = { + path: "lion.png" + url: "http://#{host}:31415/#{@file}" + modified: @last_modified = Date.now() + }] + + Client.compile @project_id, @request, (@error, @res, @body) => + sinon.spy Server, "getFile" + @image_resource.modified = new Date(@last_modified + 3000) + Client.compile @project_id, @request, (@error, @res, @body) => + done() + + afterEach -> + Server.getFile.restore() + + it "should download the image again", -> + Server.getFile.called.should.equal true + + describe "When an image is in the cache and the last modified date is further in the past", -> + before (done) -> + @project_id = Client.randomId() + @file = "#{Server.randomId()}/lion.png" + @request = + resources: [{ + path: "main.tex" + content: ''' + \\documentclass{article} + \\usepackage{graphicx} + \\begin{document} + \\includegraphics{lion.png} + \\end{document} + ''' + }, @image_resource = { + path: "lion.png" + url: "http://#{host}:31415/#{@file}" + modified: @last_modified = Date.now() + }] + + Client.compile @project_id, @request, (@error, @res, @body) => + sinon.spy Server, "getFile" + @image_resource.modified = new Date(@last_modified - 3000) + Client.compile @project_id, @request, (@error, @res, @body) => + done() + + afterEach -> + Server.getFile.restore() + + it "should not download the image again", -> + Server.getFile.called.should.equal false + + describe "When an image is in the cache and the last modified date is not specified", -> + before (done) -> + @project_id = Client.randomId() + @file = "#{Server.randomId()}/lion.png" + @request = + resources: [{ + path: "main.tex" + content: ''' + \\documentclass{article} + \\usepackage{graphicx} + \\begin{document} + \\includegraphics{lion.png} + \\end{document} + ''' + }, @image_resource = { + path: "lion.png" + url: "http://#{host}:31415/#{@file}" + modified: @last_modified = Date.now() + }] + + Client.compile @project_id, @request, (@error, @res, @body) => + sinon.spy Server, "getFile" + delete @image_resource.modified + Client.compile @project_id, @request, (@error, @res, @body) => + done() + + afterEach -> + Server.getFile.restore() + + it "should download the image again", -> + Server.getFile.called.should.equal true + + describe "After clearing the cache", -> + before (done) -> + @project_id = Client.randomId() + @file = "#{Server.randomId()}/lion.png" + @request = + resources: [{ + path: "main.tex" + content: ''' + \\documentclass{article} + \\usepackage{graphicx} + \\begin{document} + \\includegraphics{lion.png} + \\end{document} + ''' + }, @image_resource = { + path: "lion.png" + url: "http://#{host}:31415/#{@file}" + modified: @last_modified = Date.now() + }] + + Client.compile @project_id, @request, (error) => + throw error if error? + Client.clearCache @project_id, (error, res, body) => + throw error if error? + sinon.spy Server, "getFile" + Client.compile @project_id, @request, (@error, @res, @body) => + done() + + afterEach -> + Server.getFile.restore() + + it "should download the image again", -> + Server.getFile.called.should.equal true + + diff --git a/services/clsi/test/acceptance/coffee/helpers/Client.coffee b/services/clsi/test/acceptance/coffee/helpers/Client.coffee new file mode 100644 index 0000000000..bb6ddc4087 --- /dev/null +++ b/services/clsi/test/acceptance/coffee/helpers/Client.coffee @@ -0,0 +1,69 @@ +request = require "request" +fs = require "fs" +Settings = require "../../../../app/js/Settings" + +host = "localhost" + +module.exports = Client = + host: Settings.externalUrl + + randomId: () -> + Math.random().toString(16).slice(2) + + compile: (project_id, data, callback = (error, res, body) ->) -> + request.post { + url: "#{@host}/project/#{project_id}/compile" + json: + compile: data + }, callback + + clearCache: (project_id, callback = (error, res, body) ->) -> + request.del "#{@host}/project/#{project_id}", callback + + getOutputFile: (response, type) -> + for file in response.compile.outputFiles + if file.type == type + return file + return null + + runServer: (port, directory) -> + express = require("express") + app = express() + app.use express.static(directory) + app.listen(port, host) + + compileDirectory: (project_id, baseDirectory, directory, serverPort, callback = (error, res, body) ->) -> + resources = [] + entities = fs.readdirSync("#{baseDirectory}/#{directory}") + rootResourcePath = "main.tex" + while (entities.length > 0) + entity = entities.pop() + stat = fs.statSync("#{baseDirectory}/#{directory}/#{entity}") + if stat.isDirectory() + entities = entities.concat fs.readdirSync("#{baseDirectory}/#{directory}/#{entity}").map (subEntity) -> + if subEntity == "main.tex" + rootResourcePath = "#{entity}/#{subEntity}" + return "#{entity}/#{subEntity}" + else if stat.isFile() and entity != "output.pdf" + extension = entity.split(".").pop() + if ["tex", "bib", "cls", "sty", "pdf_tex", "Rtex"].indexOf(extension) > -1 + resources.push + path: entity + content: fs.readFileSync("#{baseDirectory}/#{directory}/#{entity}").toString() + else if ["eps", "ttf", "png", "jpg", "pdf", "jpeg"].indexOf(extension) > -1 + resources.push + path: entity + url: "http://#{host}:#{serverPort}/#{directory}/#{entity}" + modified: stat.mtime + + fs.readFile "#{baseDirectory}/#{directory}/options.json", (error, body) => + req = + resources: resources + rootResourcePath: rootResourcePath + + if !error? + body = JSON.parse body + req.options = body + + @compile project_id, req, callback + diff --git a/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/bibliography.bib b/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/bibliography.bib new file mode 100644 index 0000000000..5e796e057f --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/bibliography.bib @@ -0,0 +1,9 @@ +@book{DouglasAdams, + title={The Hitchhiker's Guide to the Galaxy}, + author={Adams, Douglas}, + isbn={9781417642595}, + url={http://books.google.com/books?id=W-xMPgAACAAJ}, + year={1995}, + publisher={San Val} +} + diff --git a/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/main.tex b/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/main.tex new file mode 100644 index 0000000000..2f032d653f --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/main.tex @@ -0,0 +1,12 @@ +\documentclass{article} + +\usepackage[backend=biber]{biblatex} +\addbibresource{bibliography.bib} + +\begin{document} + +The meaning of life, the universe and everything is 42 \cite{DouglasAdams} + +\printbibliography + +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/output.bbl b/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/output.bbl new file mode 100644 index 0000000000..48e803b7fb --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/output.bbl @@ -0,0 +1,48 @@ +% $ biblatex auxiliary file $ +% $ biblatex version 1.5 $ +% $ biber version 0.9.3 $ +% Do not modify the above lines! +% +% This is an auxiliary file used by the 'biblatex' package. +% This file may safely be deleted. It will be recreated by +% biber or bibtex as required. +% +\begingroup +\makeatletter +\@ifundefined{ver@biblatex.sty} + {\@latex@error + {Missing 'biblatex' package} + {The bibliography requires the 'biblatex' package.} + \aftergroup\endinput} + {} +\endgroup + + +\refsection{0} + \entry{DouglasAdams}{book}{} + \name{labelname}{1}{}{% + {{}{Adams}{A\bibinitperiod}{Douglas}{D\bibinitperiod}{}{}{}{}}% + } + \name{author}{1}{}{% + {{}{Adams}{A\bibinitperiod}{Douglas}{D\bibinitperiod}{}{}{}{}}% + } + \list{publisher}{1}{% + {San Val}% + } + \strng{namehash}{AD1} + \strng{fullhash}{AD1} + \field{sortinit}{A} + \field{isbn}{9781417642595} + \field{title}{The Hitchhiker's Guide to the Galaxy} + \field{year}{1995} + \verb{url} + \verb http://books.google.com/books?id=W-xMPgAACAAJ + \endverb + \endentry + + \lossort + \endlossort + +\endrefsection +\endinput + diff --git a/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/output.pdf b/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/output.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cf7a1c24004dfe008ede61bcaa2d3ff5c8cea6b2 GIT binary patch literal 58996 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 + + + + + + + + + + + + + + + + + + + + + + + +]> + + + latex + + output.bcf + + + output.bbl + + + blx-compat.def + biblatex.def + numeric.bbx + standard.bbx + numeric.cbx + biblatex.cfg + english.lbx + + + + biber + + biber + output + + + output.bcf + + + output.bbl + + + output.bbl + + + output.bcf + + + bibliography.bib + + + diff --git a/services/clsi/test/acceptance/fixtures/examples/epstopdf/image-eps-converted-to.pdf b/services/clsi/test/acceptance/fixtures/examples/epstopdf/image-eps-converted-to.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7b92690ce100cbe14582bf7a5a1d944b29ac936e GIT binary patch literal 26279 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[GoldenRatio^(-1), 0.6180339887498948], +% Axes->None, +% AxesOrigin->{0, 0}, +% Epilog->{ +% InsetBox[ +% BoxData[ +% FormBox[ +% InterpretationBox[ +% Cell[ +% BoxData[ +% FormBox["\"|q| = 0.02\"", TraditionalForm]], "Text", "TR"], +% Text["|q| = 0.02"]], TraditionalForm]], {0.8, 4}, BaseStyle \ +%-> 14], +% InsetBox[ +% BoxData[ +% FormBox[ +% InterpretationBox[ +% Cell[ +% BoxData[ +% FormBox["\"|q| = 0.05\"", TraditionalForm]], "Text", "TR"], +% Text["|q| = 0.05"]], TraditionalForm]], {1.67, 6.4}, \ +%BaseStyle -> 14], +% InsetBox[ +% BoxData[ +% FormBox[ +% InterpretationBox[ +% Cell[ +% BoxData[ +% FormBox["\"|q| = 0.07\"", TraditionalForm]], "Text", "TR"], +% Text["|q| = 0.07"]], TraditionalForm]], {2.85, 12}, \ +%BaseStyle -> 14], +% InsetBox[ +% BoxData[ +% FormBox[ +% InterpretationBox[ +% Cell[ +% BoxData[ +% FormBox["\"|q| = 0.1\"", TraditionalForm]], "Text", "TR"], \ +% +% Text["|q| = 0.1"]], TraditionalForm]], {3.2, 7}, BaseStyle \ +%-> 14], Null}, +% Frame->True, +% FrameLabel->{ +% FormBox["\"Impact parameter, b\"", TraditionalForm], +% FormBox["\"Spatial rotation, \[Chi]\"", TraditionalForm]}, +% FrameStyle->{{14, +% GrayLevel[1]}, {14, +% GrayLevel[1]}}, +% FrameTicks->{Automatic, {{0, +% FormBox["0", TraditionalForm]}, { +% NCache[Pi, 3.141592653589793], +% FormBox["\[Pi]", TraditionalForm]}, { +% NCache[2 Pi, 6.283185307179586], +% FormBox[ +% RowBox[{"2", " ", "\[Pi]"}], TraditionalForm]}, { +% NCache[3 Pi, 9.42477796076938], +% FormBox[ +% RowBox[{"3", " ", "\[Pi]"}], TraditionalForm]}, { +% NCache[4 Pi, 12.566370614359172`], +% FormBox[ +% RowBox[{"4", " ", "\[Pi]"}], TraditionalForm]}}}, +% FrameTicksStyle->{16, 16}, +% ImageSize->600, +% PlotRange->{All, All}, +% PlotRangeClipping->True, +% PlotRangePadding->{Automatic, Automatic}, +% TicksStyle->16]], "Output", +% CellChangeTimes->{3.556953242036603*^9, {3.556953596625984*^9, \ +%3.556953702375863*^9}, {3.556953796337514*^9, 3.556953956593231*^9}, \ +%{3.556954020687426*^9, 3.556954030706046*^9}, { +% 3.558693364370013*^9, 3.5586933829491863`*^9}, \ +%{3.55869345851305*^9, 3.5586934742140837`*^9}}] +%%EndMathematicaCell +p +np 33 1 m +33 273 L +469 273 L +469 1 L +cp +clip np +p +np 35 3 m +35 271 L +467 271 L +467 3 L +cp +clip np +3.239 setmiterlimit +p +np 70 3 m +70 238 L +450 238 L +450 3 L +cp +clip np +P +p +np 70 3 m +70 238 L +450 238 L +450 3 L +cp +clip np +P +p +np 70 3 m +70 238 L +450 238 L +450 3 L +cp +clip np +0 g +0.36 w +[ ] 0 setdash +3.25 setmiterlimit +78.19 226.892 m +78.919 226.724 L +79.648 226.555 L +80.376 226.386 L +81.105 226.217 L +81.834 226.048 L +82.562 225.878 L +83.291 225.707 L +84.02 225.537 L +84.749 225.366 L +85.477 225.194 L +86.206 225.022 L +86.935 224.85 L +87.663 224.678 L +88.392 224.505 L +89.121 224.331 L +89.849 224.158 L +90.578 223.984 L +91.307 223.809 L +92.035 223.635 L +92.764 223.459 L +93.493 223.284 L +94.221 223.108 L +94.95 222.932 L +95.679 222.755 L +96.407 222.578 L +97.136 222.4 L +97.865 222.222 L +98.593 222.044 L +99.322 221.865 L +100.051 221.686 L +100.779 221.507 L +101.508 221.327 L +102.237 221.147 L +102.965 220.966 L +103.694 220.785 L +104.423 220.604 L +105.152 220.422 L +105.88 220.24 L +106.609 220.057 L +107.338 219.874 L +108.066 219.691 L +108.795 219.507 L +109.524 219.323 L +110.252 219.138 L +110.981 218.953 L +111.71 218.767 L +112.438 218.581 L +113.167 218.395 L +113.896 218.208 L +114.624 218.021 L +115.353 217.833 L +116.082 217.645 L +116.81 217.457 L +117.539 217.268 L +118.268 217.078 L +118.996 216.889 L +119.725 216.698 L +120.454 216.508 L +121.182 216.316 L +121.911 216.125 L +122.64 215.933 L +123.368 215.74 L +124.097 215.547 L +124.826 215.354 L +125.554 215.16 L +126.283 214.965 L +127.012 214.77 L +127.741 214.575 L +128.469 214.379 L +129.198 214.183 L +129.927 213.986 L +130.655 213.789 L +131.384 213.591 L +132.113 213.392 L +132.841 213.194 L +133.57 212.994 L +134.299 212.794 L +135.027 212.594 L +135.756 212.393 L +136.485 212.192 L +137.213 211.99 L +137.942 211.787 L +138.671 211.584 L +139.399 211.38 L +140.128 211.176 L +140.857 210.972 L +141.585 210.766 L +142.314 210.56 L +143.043 210.354 L +143.771 210.147 L +144.5 209.939 L +145.229 209.731 L +145.957 209.522 L +146.686 209.313 L +147.415 209.103 L +148.144 208.893 L +148.872 208.681 L +149.601 208.47 L +150.33 208.257 L +151.058 208.044 L +151.787 207.83 L +152.516 207.616 L +153.244 207.401 L +153.973 207.185 L +154.702 206.969 L +155.43 206.752 L +156.159 206.534 L +156.888 206.316 L +157.616 206.097 L +158.345 205.877 L +159.074 205.656 L +159.802 205.435 L +160.531 205.213 L +161.26 204.99 L +161.988 204.767 L +162.717 204.542 L +163.446 204.317 L +164.174 204.092 L +164.903 203.865 L +165.632 203.638 L +166.36 203.409 L +167.089 203.181 L +167.818 202.951 L +168.547 202.72 L +169.275 202.489 L +170.004 202.256 L +170.733 202.023 L +171.461 201.789 L +172.19 201.554 L +172.919 201.318 L +173.647 201.081 L +174.376 200.844 L +175.105 200.605 L +175.833 200.365 L +176.562 200.125 L +177.291 199.883 L +178.019 199.641 L +178.748 199.397 L +179.477 199.153 L +180.205 198.907 L +180.934 198.661 L +181.663 198.413 L +182.391 198.164 L +183.12 197.914 L +183.849 197.664 L +184.577 197.412 L +185.306 197.158 L +186.035 196.904 L +186.763 196.649 L +187.492 196.392 L +188.221 196.134 L +188.95 195.875 L +189.678 195.615 L +190.407 195.353 L +191.136 195.091 L +191.864 194.826 L +192.593 194.561 L +193.322 194.294 L +194.05 194.026 L +194.779 193.757 L +195.508 193.486 L +196.236 193.214 L +196.965 192.94 L +197.694 192.665 L +198.422 192.388 L +199.151 192.11 L +199.88 191.83 L +200.608 191.549 L +201.337 191.266 L +202.066 190.981 L +202.794 190.695 L +203.523 190.408 L +204.252 190.118 L +204.98 189.827 L +205.709 189.534 L +206.438 189.239 L +207.166 188.942 L +207.895 188.644 L +208.624 188.343 L +209.353 188.041 L +210.081 187.737 L +210.81 187.43 L +211.539 187.122 L +212.267 186.811 L +212.996 186.499 L +213.725 186.184 L +214.453 185.867 L +215.182 185.548 L +215.911 185.226 L +216.639 184.903 L +217.368 184.576 L +218.097 184.248 L +218.825 183.917 L +219.554 183.583 L +220.283 183.247 L +221.011 182.908 L +221.74 182.566 L +222.469 182.221 L +223.197 181.874 L +223.926 181.524 L +224.655 181.171 L +225.383 180.815 L +226.112 180.455 L +226.841 180.093 L +227.569 179.727 L +228.298 179.358 L +229.027 178.986 L +229.756 178.61 L +230.484 178.23 L +231.213 177.847 L +231.942 177.46 L +232.67 177.069 L +233.399 176.674 L +234.128 176.276 L +234.856 175.872 L +235.585 175.465 L +236.314 175.053 L +237.042 174.637 L +237.771 174.216 L +238.5 173.79 L +239.228 173.359 L +239.957 172.924 L +240.686 172.482 L +241.414 172.036 L +242.143 171.584 L +242.872 171.126 L +243.6 170.662 L +244.329 170.192 L +245.058 169.716 L +245.786 169.233 L +246.515 168.744 L +247.244 168.247 L +247.972 167.743 L +248.701 167.232 L +249.43 166.713 L +250.159 166.186 L +250.887 165.65 L +251.616 165.106 L +252.345 164.553 L +253.073 163.99 L +253.802 163.417 L +254.531 162.834 L +255.259 162.241 L +255.988 161.636 L +256.717 161.02 L +257.445 160.391 L +258.174 159.75 L +258.903 159.095 L +259.631 158.426 L +260.36 157.743 L +261.089 157.044 L +261.817 156.328 L +262.546 155.595 L +263.275 154.844 L +264.003 154.074 L +264.732 153.283 L +265.461 152.471 L +266.189 151.635 L +266.918 150.774 L +267.647 149.887 L +268.375 148.972 L +269.104 148.026 L +269.833 147.047 L +270.561 146.033 L +271.29 144.98 L +272.019 143.886 L +272.748 142.746 L +273.476 141.556 L +274.205 140.31 L +274.934 139.004 L +275.662 137.63 L +276.391 136.179 L +277.12 134.642 L +277.848 133.005 L +278.577 131.258 L +279.306 129.378 L +280.034 127.344 L +280.763 125.125 L +281.492 122.683 L +282.22 119.959 L +282.949 116.88 L +283.678 113.326 L +284.406 109.112 L +285.135 103.916 L +285.864 97.083 L +286.592 86.986 L +287.321 66.511 L +288.05 107.215 L +288.778 120.201 L +289.507 125.455 L +290.236 128.764 L +290.964 131.167 L +291.693 133.043 L +292.422 134.577 L +293.151 135.87 L +293.879 136.983 L +294.608 137.958 L +295.337 138.825 L +296.065 139.602 L +296.794 140.306 L +297.523 140.948 L +298.251 141.537 L +298.98 142.081 L +299.709 142.585 L +300.437 143.054 L +301.166 143.493 L +301.895 143.904 L +302.623 144.291 L +303.352 144.655 L +304.081 145 L +304.809 145.327 L +305.538 145.636 L +306.267 145.931 L +306.995 146.212 L +307.724 146.479 L +308.453 146.735 L +309.181 146.98 L +309.91 147.214 L +310.639 147.439 L +311.367 147.655 L +312.096 147.863 L +312.825 148.062 L +313.554 148.255 L +314.282 148.44 L +315.011 148.619 L +315.74 148.792 L +316.468 148.959 L +317.197 149.12 L +317.926 149.276 L +318.654 149.427 L +319.383 149.574 L +320.112 149.716 L +320.84 149.853 L +321.569 149.987 L +322.298 150.117 L +323.026 150.243 L +323.755 150.366 L +324.484 150.485 L +325.212 150.601 L +325.941 150.713 L +326.67 150.823 L +327.398 150.93 L +328.127 151.035 L +328.856 151.136 L +329.584 151.235 L +330.313 151.332 L +331.042 151.426 L +331.77 151.518 L +332.499 151.608 L +333.228 151.696 L +333.957 151.782 L +334.685 151.866 L +335.414 151.948 L +336.143 152.028 L +336.871 152.106 L +337.6 152.183 L +338.329 152.258 L +339.057 152.331 L +339.786 152.403 L +340.515 152.474 L +341.243 152.542 L +341.972 152.61 L +342.701 152.676 L +343.429 152.741 L +344.158 152.805 L +344.887 152.867 L +345.615 152.928 L +346.344 152.988 L +347.073 153.047 L +347.801 153.105 L +348.53 153.161 L +349.259 153.217 L +349.987 153.271 L +350.716 153.325 L +351.445 153.377 L +352.173 153.429 L +352.902 153.48 L +353.631 153.53 L +354.36 153.579 L +355.088 153.627 L +355.817 153.674 L +356.546 153.721 L +357.274 153.766 L +358.003 153.811 L +358.732 153.855 L +359.46 153.899 L +360.189 153.942 L +360.918 153.984 L +361.646 154.025 L +362.375 154.066 L +363.104 154.106 L +363.832 154.145 L +364.561 154.184 L +365.29 154.222 L +366.018 154.26 L +366.747 154.297 L +367.476 154.333 L +368.204 154.369 L +368.933 154.405 L +369.662 154.44 L +370.39 154.474 L +371.119 154.508 L +371.848 154.541 L +372.576 154.574 L +373.305 154.606 L +374.034 154.638 L +374.763 154.669 L +375.491 154.7 L +376.22 154.731 L +376.949 154.761 L +377.677 154.791 L +378.406 154.82 L +379.135 154.849 L +379.863 154.877 L +380.592 154.905 L +381.321 154.933 L +382.049 154.96 L +382.778 154.987 L +383.507 155.014 L +384.235 155.04 L +384.964 155.066 L +385.693 155.092 L +386.421 155.117 L +387.15 155.142 L +387.879 155.166 L +388.607 155.19 L +389.336 155.214 L +390.065 155.238 L +390.793 155.261 L +391.522 155.284 L +392.251 155.307 L +392.979 155.33 L +393.708 155.352 L +394.437 155.374 L +395.166 155.395 L +395.894 155.417 L +396.623 155.438 L +397.352 155.458 L +398.08 155.479 L +398.809 155.499 L +399.538 155.519 L +400.266 155.539 L +400.995 155.559 L +401.724 155.578 L +402.452 155.597 L +403.181 155.616 L +403.91 155.635 L +404.638 155.653 L +405.367 155.672 L +406.096 155.69 L +406.824 155.708 L +407.553 155.725 L +408.282 155.743 L +409.01 155.76 L +409.739 155.777 L +410.468 155.794 L +411.196 155.811 L +411.925 155.827 L +412.654 155.843 L +413.382 155.859 L +414.111 155.875 L +414.84 155.891 L +415.568 155.907 L +416.297 155.922 L +417.026 155.937 L +417.755 155.952 L +418.483 155.967 L +419.212 155.982 L +419.941 155.997 L +420.669 156.011 L +421.398 156.025 L +422.127 156.04 L +422.855 156.054 L +423.584 156.067 L +424.313 156.081 L +425.041 156.095 L +425.77 156.108 L +426.499 156.121 L +427.227 156.135 L +427.956 156.148 L +428.685 156.16 L +429.413 156.173 L +430.142 156.186 L +430.871 156.198 L +431.599 156.211 L +432.328 156.223 L +433.057 156.235 L +433.785 156.247 L +434.514 156.259 L +435.243 156.271 L +435.971 156.282 L +436.7 156.294 L +437.429 156.305 L +438.158 156.316 L +438.886 156.328 L +439.615 156.339 L +440.344 156.35 L +441.072 156.36 L +441.801 156.371 L +442.53 156.382 L +s +78.19 227.244 m +78.919 227.062 L +79.648 226.879 L +80.376 226.696 L +81.105 226.513 L +81.834 226.329 L +82.562 226.144 L +83.291 225.96 L +84.02 225.774 L +84.749 225.589 L +85.477 225.403 L +86.206 225.216 L +86.935 225.029 L +87.663 224.842 L +88.392 224.654 L +89.121 224.465 L +89.849 224.277 L +90.578 224.088 L +91.307 223.898 L +92.035 223.708 L +92.764 223.518 L +93.493 223.327 L +94.221 223.135 L +94.95 222.944 L +95.679 222.751 L +96.407 222.559 L +97.136 222.366 L +97.865 222.172 L +98.593 221.978 L +99.322 221.783 L +100.051 221.589 L +100.779 221.393 L +101.508 221.197 L +102.237 221.001 L +102.965 220.804 L +103.694 220.607 L +104.423 220.409 L +105.152 220.211 L +105.88 220.013 L +106.609 219.813 L +107.338 219.614 L +108.066 219.414 L +108.795 219.213 L +109.524 219.012 L +110.252 218.811 L +110.981 218.609 L +111.71 218.406 L +112.438 218.203 L +113.167 217.999 L +113.896 217.795 L +114.624 217.591 L +115.353 217.386 L +116.082 217.18 L +116.81 216.974 L +117.539 216.767 L +118.268 216.56 L +118.996 216.352 L +119.725 216.144 L +120.454 215.935 L +121.182 215.726 L +121.911 215.516 L +122.64 215.305 L +123.368 215.094 L +124.097 214.883 L +124.826 214.67 L +125.554 214.458 L +126.283 214.244 L +127.012 214.03 L +127.741 213.816 L +128.469 213.6 L +129.198 213.385 L +129.927 213.168 L +130.655 212.951 L +131.384 212.733 L +132.113 212.515 L +132.841 212.296 L +133.57 212.076 L +134.299 211.856 L +135.027 211.635 L +135.756 211.413 L +136.485 211.191 L +137.213 210.968 L +137.942 210.744 L +138.671 210.52 L +139.399 210.294 L +140.128 210.068 L +140.857 209.842 L +141.585 209.614 L +142.314 209.386 L +143.043 209.157 L +143.771 208.927 L +144.5 208.697 L +145.229 208.466 L +145.957 208.234 L +146.686 208.001 L +147.415 207.767 L +148.144 207.532 L +148.872 207.297 L +149.601 207.06 L +150.33 206.823 L +151.058 206.585 L +151.787 206.346 L +152.516 206.106 L +153.244 205.865 L +153.973 205.623 L +154.702 205.381 L +155.43 205.137 L +156.159 204.892 L +156.888 204.646 L +157.616 204.4 L +158.345 204.152 L +159.074 203.903 L +159.802 203.653 L +160.531 203.402 L +161.26 203.15 L +161.988 202.897 L +162.717 202.643 L +163.446 202.387 L +164.174 202.131 L +164.903 201.873 L +165.632 201.614 L +166.36 201.354 L +167.089 201.092 L +167.818 200.83 L +168.547 200.565 L +169.275 200.3 L +170.004 200.033 L +170.733 199.765 L +171.461 199.496 L +172.19 199.225 L +172.919 198.953 L +173.647 198.68 L +174.376 198.405 L +175.105 198.128 L +175.833 197.85 L +176.562 197.57 L +177.291 197.289 L +178.019 197.006 L +178.748 196.722 L +179.477 196.435 L +180.205 196.148 L +180.934 195.858 L +181.663 195.567 L +182.391 195.274 L +183.12 194.979 L +183.849 194.682 L +184.577 194.383 L +185.306 194.083 L +186.035 193.78 L +186.763 193.475 L +187.492 193.169 L +188.221 192.86 L +188.95 192.549 L +189.678 192.236 L +190.407 191.921 L +191.136 191.603 L +191.864 191.284 L +192.593 190.961 L +193.322 190.637 L +194.05 190.31 L +194.779 189.98 L +195.508 189.648 L +196.236 189.313 L +196.965 188.976 L +197.694 188.635 L +198.422 188.292 L +199.151 187.946 L +199.88 187.598 L +200.608 187.246 L +201.337 186.891 L +202.066 186.533 L +202.794 186.171 L +203.523 185.807 L +204.252 185.438 L +204.98 185.067 L +205.709 184.692 L +206.438 184.313 L +207.166 183.93 L +207.895 183.544 L +208.624 183.153 L +209.353 182.759 L +210.081 182.36 L +210.81 181.957 L +211.539 181.549 L +212.267 181.137 L +212.996 180.72 L +213.725 180.299 L +214.453 179.872 L +215.182 179.44 L +215.911 179.003 L +216.639 178.56 L +217.368 178.112 L +218.097 177.657 L +218.825 177.197 L +219.554 176.73 L +220.283 176.257 L +221.011 175.777 L +221.74 175.29 L +222.469 174.796 L +223.197 174.295 L +223.926 173.785 L +224.655 173.268 L +225.383 172.742 L +226.112 172.207 L +226.841 171.662 L +227.569 171.109 L +228.298 170.546 L +229.027 169.971 L +229.756 169.387 L +230.484 168.79 L +231.213 168.182 L +231.942 167.561 L +232.67 166.927 L +233.399 166.28 L +234.128 165.617 L +234.856 164.94 L +235.585 164.246 L +236.314 163.535 L +237.042 162.806 L +237.771 162.058 L +238.5 161.289 L +239.228 160.499 L +239.957 159.686 L +240.686 158.848 L +241.414 157.985 L +242.143 157.093 L +242.872 156.17 L +243.6 155.216 L +244.329 154.226 L +245.058 153.198 L +245.786 152.13 L +246.515 151.016 L +247.244 149.854 L +247.972 148.638 L +248.701 95.265 L +249.43 93.925 L +250.159 92.513 L +250.887 91.021 L +251.616 89.437 L +252.345 87.751 L +253.073 85.948 L +253.802 84.008 L +254.531 81.914 L +255.259 79.635 L +255.988 77.137 L +256.717 74.37 L +257.445 71.271 L +258.174 67.749 L +258.903 63.665 L +259.631 58.799 L +260.36 52.774 L +261.089 44.836 L +261.817 33.16 L +262.546 10.25 L +263.275 102.86 L +264.003 118.171 L +264.732 124.159 L +265.461 127.877 L +266.189 130.553 L +266.918 132.626 L +267.647 134.311 L +268.375 135.722 L +269.104 136.932 L +269.833 137.986 L +270.561 138.919 L +271.29 139.753 L +272.019 140.505 L +272.748 141.189 L +273.476 141.814 L +274.205 142.389 L +274.934 142.921 L +275.662 143.415 L +276.391 143.875 L +277.12 144.305 L +277.848 144.709 L +278.577 145.088 L +279.306 145.446 L +280.034 145.784 L +280.763 146.105 L +281.492 146.409 L +282.22 146.698 L +282.949 146.973 L +283.678 147.236 L +284.406 147.486 L +285.135 147.726 L +285.864 147.955 L +286.592 148.175 L +287.321 148.386 L +288.05 148.589 L +288.778 148.784 L +289.507 148.972 L +290.236 149.152 L +290.964 149.327 L +291.693 149.495 L +292.422 149.657 L +293.151 149.814 L +293.879 149.965 L +294.608 150.112 L +295.337 150.254 L +296.065 150.392 L +296.794 150.525 L +297.523 150.654 L +298.251 150.78 L +298.98 150.901 L +299.709 151.02 L +300.437 151.135 L +301.166 151.246 L +301.895 151.355 L +302.623 151.46 L +303.352 151.563 L +304.081 151.663 L +304.809 151.761 L +305.538 151.856 L +306.267 151.949 L +306.995 152.039 L +307.724 152.127 L +308.453 152.213 L +309.181 152.297 L +309.91 152.379 L +310.639 152.459 L +311.367 152.537 L +312.096 152.613 L +312.825 152.688 L +313.554 152.761 L +314.282 152.832 L +315.011 152.902 L +315.74 152.97 L +316.468 153.037 L +317.197 153.102 L +317.926 153.166 L +318.654 153.228 L +319.383 153.29 L +320.112 153.35 L +320.84 153.409 L +321.569 153.466 L +322.298 153.523 L +323.026 153.578 L +323.755 153.633 L +324.484 153.686 L +325.212 153.738 L +325.941 153.789 L +326.67 153.84 L +327.398 153.889 L +328.127 153.937 L +328.856 153.985 L +329.584 154.032 L +330.313 154.078 L +331.042 154.123 L +331.77 154.167 L +332.499 154.21 L +333.228 154.253 L +333.957 154.295 L +334.685 154.336 L +335.414 154.376 L +336.143 154.416 L +336.871 154.455 L +337.6 154.494 L +338.329 154.532 L +339.057 154.569 L +339.786 154.606 L +340.515 154.642 L +341.243 154.677 L +341.972 154.712 L +342.701 154.746 L +343.429 154.78 L +344.158 154.813 L +344.887 154.846 L +345.615 154.878 L +346.344 154.91 L +347.073 154.941 L +347.801 154.972 L +348.53 155.002 L +349.259 155.032 L +349.987 155.061 L +350.716 155.09 L +351.445 155.118 L +352.173 155.146 L +352.902 155.174 L +353.631 155.201 L +354.36 155.228 L +355.088 155.255 L +355.817 155.281 L +356.546 155.306 L +357.274 155.332 L +358.003 155.357 L +358.732 155.381 L +359.46 155.406 L +360.189 155.43 L +360.918 155.453 L +361.646 155.476 L +362.375 155.499 L +363.104 155.522 L +363.832 155.544 L +364.561 155.567 L +365.29 155.588 L +366.018 155.61 L +366.747 155.631 L +367.476 155.652 L +368.204 155.672 L +368.933 155.693 L +369.662 155.713 L +370.39 155.733 L +371.119 155.752 L +371.848 155.772 L +372.576 155.791 L +373.305 155.81 L +374.034 155.828 L +374.763 155.847 L +375.491 155.865 L +376.22 155.883 L +376.949 155.9 L +377.677 155.918 L +378.406 155.935 L +379.135 155.952 L +379.863 155.969 L +380.592 155.986 L +381.321 156.002 L +382.049 156.018 L +382.778 156.034 L +383.507 156.05 L +384.235 156.066 L +384.964 156.081 L +385.693 156.097 L +386.421 156.112 L +387.15 156.127 L +387.879 156.141 L +388.607 156.156 L +389.336 156.171 L +390.065 156.185 L +390.793 156.199 L +391.522 156.213 L +392.251 156.227 L +392.979 156.24 L +393.708 156.254 L +394.437 156.267 L +395.166 156.28 L +395.894 156.293 L +396.623 156.306 L +397.352 156.319 L +398.08 156.331 L +398.809 156.344 L +399.538 156.356 L +400.266 156.368 L +400.995 156.38 L +401.724 156.392 L +402.452 156.404 L +403.181 156.416 L +403.91 156.427 L +404.638 156.439 L +405.367 156.45 L +406.096 156.461 L +406.824 156.472 L +407.553 156.483 L +408.282 156.494 L +409.01 156.505 L +409.739 156.515 L +410.468 156.526 L +411.196 156.536 L +411.925 156.546 L +412.654 156.557 L +413.382 156.567 L +414.111 156.577 L +414.84 156.587 L +415.568 156.596 L +416.297 156.606 L +417.026 156.616 L +417.755 156.625 L +418.483 156.634 L +419.212 156.644 L +419.941 156.653 L +420.669 156.662 L +421.398 156.671 L +422.127 156.68 L +422.855 156.689 L +423.584 156.697 L +424.313 156.706 L +425.041 156.715 L +425.77 156.723 L +426.499 156.732 L +427.227 156.74 L +427.956 156.748 L +428.685 156.756 L +429.413 156.764 L +430.142 156.772 L +430.871 156.78 L +431.599 156.788 L +432.328 156.796 L +433.057 156.804 L +433.785 156.811 L +434.514 156.819 L +435.243 156.826 L +435.971 156.834 L +436.7 156.841 L +437.429 156.849 L +438.158 156.856 L +438.886 156.863 L +439.615 156.87 L +440.344 156.877 L +441.072 156.884 L +441.801 156.891 L +442.53 156.898 L +s +78.19 227.796 m +78.555 227.699 L +78.919 227.602 L +79.283 227.504 L +79.648 227.407 L +80.012 227.309 L +80.376 227.211 L +80.741 227.113 L +81.105 227.016 L +81.469 226.917 L +81.834 226.819 L +82.198 226.721 L +82.562 226.622 L +82.927 226.524 L +83.291 226.425 L +83.655 226.327 L +84.02 226.228 L +84.384 226.129 L +84.749 226.03 L +85.113 225.93 L +85.477 225.831 L +85.842 225.732 L +86.206 225.632 L +86.57 225.532 L +86.935 225.433 L +87.299 225.333 L +87.663 225.233 L +88.028 225.133 L +88.392 225.032 L +88.756 224.932 L +89.121 224.832 L +89.485 224.731 L +89.849 224.63 L +90.214 224.53 L +90.578 224.429 L +90.942 224.328 L +91.307 224.227 L +91.671 224.125 L +92.035 224.024 L +92.4 223.923 L +92.764 223.821 L +93.128 223.719 L +93.493 223.618 L +93.857 223.516 L +94.221 223.414 L +94.586 223.311 L +94.95 223.209 L +95.314 223.107 L +95.679 223.004 L +96.043 222.902 L +96.407 222.799 L +96.772 222.696 L +97.136 222.593 L +97.5 222.49 L +97.865 222.387 L +98.229 222.284 L +98.593 222.18 L +98.958 222.077 L +99.322 221.973 L +99.686 221.869 L +100.051 221.765 L +100.415 221.661 L +100.779 221.557 L +101.144 221.453 L +101.508 221.349 L +101.872 221.244 L +102.237 221.14 L +102.601 221.035 L +102.965 220.93 L +103.33 220.825 L +103.694 220.72 L +104.058 220.615 L +104.423 220.51 L +104.787 220.404 L +105.152 220.299 L +105.516 220.193 L +105.88 220.087 L +106.245 219.981 L +106.609 219.875 L +106.973 219.769 L +107.338 219.663 L +107.702 219.556 L +108.066 219.45 L +108.431 219.343 L +108.795 219.236 L +109.159 219.129 L +109.524 219.022 L +109.888 218.915 L +110.252 218.808 L +110.617 218.7 L +110.981 218.593 L +111.345 218.485 L +111.71 218.377 L +112.074 218.269 L +112.438 218.161 L +112.803 218.053 L +113.167 217.945 L +113.531 217.836 L +113.896 217.727 L +114.26 217.619 L +114.624 217.51 L +114.989 217.401 L +115.353 217.291 L +115.717 217.182 L +116.082 217.073 L +116.446 216.963 L +116.81 216.853 L +117.175 216.743 L +117.539 216.633 L +117.903 216.523 L +118.268 216.413 L +118.632 216.303 L +118.996 216.192 L +119.361 216.081 L +119.725 215.97 L +120.089 215.859 L +120.454 215.748 L +120.818 215.637 L +121.182 215.525 L +121.547 215.414 L +121.911 215.302 L +122.275 215.19 L +122.64 215.078 L +123.004 214.965 L +123.368 214.853 L +123.733 214.74 L +124.097 214.628 L +124.461 214.515 L +124.826 214.402 L +125.19 214.288 L +125.554 214.175 L +125.919 214.061 L +126.283 213.948 L +126.648 213.834 L +127.012 213.72 L +127.376 213.605 L +127.741 213.491 L +128.105 213.376 L +128.469 213.262 L +128.834 213.147 L +129.198 213.032 L +129.562 212.916 L +129.927 212.801 L +130.291 212.685 L +130.655 212.57 L +131.02 212.454 L +131.384 212.337 L +131.748 212.221 L +132.113 212.105 L +132.477 211.988 L +132.841 211.871 L +133.206 211.754 L +133.57 211.636 L +133.934 211.519 L +134.299 211.401 L +134.663 211.283 L +135.027 211.165 L +135.392 211.047 L +135.756 210.928 L +136.12 210.81 L +136.485 210.691 L +136.849 210.572 L +137.213 210.452 L +137.578 210.332 L +137.942 210.213 L +138.306 210.093 L +138.671 209.973 L +139.035 209.852 L +139.399 209.732 L +139.764 209.611 L +140.128 209.49 L +140.492 209.368 L +140.857 209.247 L +141.221 209.125 L +141.585 209.003 L +141.95 208.881 L +142.314 208.758 L +142.678 208.635 L +143.043 208.513 L +143.407 208.389 L +143.771 208.266 L +144.136 208.142 L +144.5 208.018 L +144.864 207.894 L +145.229 207.77 L +145.593 207.645 L +145.957 207.52 L +146.322 207.395 L +146.686 207.269 L +147.051 207.143 L +147.415 207.017 L +147.779 206.891 L +148.144 206.764 L +148.508 206.637 L +148.872 206.51 L +149.237 206.383 L +149.601 206.255 L +149.965 206.127 L +150.33 205.999 L +150.694 205.87 L +151.058 205.741 L +151.423 205.612 L +151.787 205.483 L +152.151 205.353 L +152.516 205.223 L +152.88 205.092 L +153.244 204.962 L +153.609 204.831 L +153.973 204.699 L +154.337 204.567 L +154.702 204.435 L +155.066 204.303 L +155.43 204.17 L +155.795 204.037 L +156.159 203.904 L +156.523 203.77 L +156.888 203.636 L +157.252 203.502 L +157.616 203.367 L +157.981 203.232 L +158.345 203.096 L +158.709 202.961 L +159.074 202.824 L +159.438 202.688 L +159.802 202.551 L +160.167 202.414 L +160.531 202.276 L +160.895 202.138 L +161.26 201.999 L +161.624 201.86 L +161.988 201.721 L +162.353 201.581 L +162.717 201.441 L +163.081 201.301 L +163.446 201.16 L +163.81 201.018 L +164.174 200.877 L +164.539 200.735 L +164.903 200.592 L +165.267 200.449 L +165.632 200.305 L +165.996 200.161 L +166.36 200.017 L +166.725 199.872 L +167.089 199.727 L +167.454 199.581 L +167.818 199.435 L +168.182 199.288 L +168.547 199.141 L +168.911 198.993 L +169.275 198.845 L +169.64 198.696 L +170.004 198.547 L +170.368 198.397 L +170.733 198.247 L +171.097 198.097 L +171.461 197.945 L +171.826 197.793 L +172.19 197.641 L +172.554 197.488 L +172.919 197.335 L +173.283 197.181 L +173.647 197.026 L +174.012 196.871 L +174.376 196.716 L +174.74 196.559 L +175.105 196.403 L +175.469 196.245 L +175.833 196.087 L +176.198 195.929 L +176.562 195.77 L +176.926 195.61 L +177.291 195.449 L +177.655 195.288 L +178.019 195.126 L +178.384 194.964 L +178.748 194.801 L +179.112 194.637 L +179.477 194.473 L +179.841 194.308 L +180.205 194.142 L +180.57 193.976 L +180.934 193.809 L +181.298 193.641 L +181.663 193.472 L +182.027 193.304 L +182.391 193.134 L +182.756 192.963 L +183.12 192.791 L +183.484 192.62 L +183.849 192.447 L +184.213 192.273 L +184.577 192.098 L +184.942 191.923 L +185.306 191.747 L +185.67 191.57 L +186.035 191.392 L +186.399 191.214 L +186.763 191.035 L +187.128 190.854 L +187.492 190.673 L +187.856 190.491 L +188.221 190.309 L +188.585 190.125 L +188.95 189.94 L +189.314 189.755 L +189.678 189.568 L +190.043 189.381 L +190.407 189.192 L +190.771 189.003 L +191.136 188.813 L +191.5 188.622 L +191.864 188.429 L +192.229 188.236 L +192.593 188.042 L +192.957 187.847 L +193.322 187.65 L +193.686 187.453 L +194.05 187.254 L +194.415 187.054 L +194.779 186.854 L +195.143 186.652 L +195.508 186.449 L +195.872 186.244 L +196.236 186.039 L +196.601 185.832 L +196.965 185.625 L +197.329 185.415 L +197.694 185.205 L +198.058 184.994 L +198.422 184.781 L +198.787 184.567 L +199.151 184.351 L +199.515 184.134 L +199.88 131.817 L +200.244 131.597 L +200.608 131.376 L +200.973 131.153 L +201.337 130.93 L +201.701 130.704 L +202.066 130.477 L +202.43 130.249 L +202.794 130.019 L +203.159 129.787 L +203.523 129.554 L +203.887 129.318 L +204.252 129.082 L +204.616 128.844 L +204.98 128.604 L +205.345 128.362 L +205.709 128.118 L +206.073 127.873 L +206.438 127.625 L +206.802 127.376 L +207.166 127.125 L +207.531 126.872 L +207.895 126.616 L +208.259 126.358 L +208.624 126.099 L +208.988 125.837 L +209.353 125.573 L +209.717 125.307 L +210.081 125.038 L +210.446 124.767 L +210.81 124.494 L +211.174 124.217 L +211.539 123.939 L +211.903 123.657 L +212.267 123.373 L +212.632 123.086 L +212.996 122.797 L +213.36 122.504 L +213.725 122.208 L +214.089 121.91 L +214.453 121.608 L +214.818 121.302 L +215.182 120.994 L +215.546 120.681 L +215.911 120.366 L +216.275 120.046 L +216.639 119.723 L +217.004 119.395 L +217.368 119.064 L +217.732 118.728 L +218.097 118.389 L +218.461 118.044 L +218.825 117.695 L +219.19 117.34 L +219.554 116.982 L +219.918 116.617 L +220.283 116.248 L +220.647 115.872 L +221.011 115.491 L +221.376 115.103 L +221.74 114.71 L +222.104 114.31 L +222.469 113.903 L +222.833 113.488 L +223.197 113.067 L +223.562 112.637 L +223.926 112.199 L +224.29 111.753 L +224.655 111.297 L +225.019 110.833 L +225.383 110.358 L +225.748 109.874 L +226.112 109.378 L +226.476 108.871 L +226.841 108.353 L +227.205 107.821 L +227.569 107.276 L +227.934 106.717 L +228.298 106.144 L +228.662 105.556 L +229.027 104.951 L +229.391 104.329 L +229.756 103.689 L +230.12 103.031 L +230.484 102.352 L +230.849 101.653 L +231.213 100.932 L +231.577 100.19 L +231.942 99.426 L +232.306 98.638 L +232.67 97.827 L +233.035 96.995 L +233.399 96.143 L +233.763 95.275 L +234.128 94.395 L +234.492 93.515 L +234.856 92.647 L +235.221 91.816 L +235.585 91.055 L +235.949 90.419 L +236.314 89.987 L +236.678 89.872 L +237.042 90.228 L +237.407 91.227 L +237.771 93.008 L +238.135 95.595 L +238.5 98.813 L +238.864 102.357 L +239.228 105.928 L +239.593 109.305 L +239.957 112.388 L +240.321 115.15 L +240.686 117.611 L +241.05 119.793 L +241.414 121.734 L +241.779 123.473 L +242.143 125.035 L +242.507 126.442 L +242.872 127.722 L +243.236 128.889 L +243.6 129.959 L +243.965 130.941 L +244.329 131.852 L +244.693 132.693 L +245.058 133.477 L +245.422 134.21 L +245.786 134.895 L +246.151 135.537 L +246.515 136.14 L +246.879 136.711 L +247.244 137.249 L +247.608 137.759 L +247.972 138.243 L +248.337 138.702 L +248.701 139.14 L +249.065 139.557 L +249.43 139.955 L +249.794 140.335 L +250.159 140.699 L +250.523 141.048 L +250.887 141.382 L +251.252 141.704 L +251.616 142.013 L +251.98 142.31 L +252.345 142.596 L +252.709 142.871 L +253.073 143.138 L +253.438 143.394 L +253.802 143.643 L +254.166 143.882 L +254.531 144.115 L +254.895 144.339 L +255.259 144.557 L +255.624 144.768 L +255.988 144.972 L +256.352 145.171 L +256.717 145.364 L +257.081 145.551 L +257.445 145.733 L +257.81 145.91 L +258.174 146.082 L +258.538 146.25 L +258.903 146.413 L +259.267 146.572 L +259.631 146.727 L +259.996 146.878 L +260.36 147.025 L +260.724 147.169 L +261.089 147.309 L +261.453 147.446 L +261.817 147.579 L +262.182 147.71 L +262.546 147.837 L +262.91 147.962 L +263.275 148.084 L +263.639 148.203 L +264.003 148.32 L +264.368 148.434 L +264.732 148.546 L +265.096 148.655 L +265.461 148.763 L +265.825 148.868 L +266.189 148.97 L +266.554 149.071 L +266.918 149.17 L +267.282 149.267 L +267.647 149.362 L +268.011 149.455 L +268.375 149.547 L +268.74 149.636 L +269.104 149.724 L +269.468 149.811 L +269.833 149.896 L +270.197 149.979 L +270.561 150.061 L +270.926 150.141 L +271.29 150.22 L +271.655 150.298 L +272.019 150.374 L +272.383 150.449 L +272.748 150.523 L +273.112 150.595 L +273.476 150.667 L +273.841 150.737 L +274.205 150.806 L +274.569 150.874 L +274.934 150.941 L +275.298 151.006 L +275.662 151.071 L +276.027 151.135 L +276.391 151.198 L +276.755 151.259 L +277.12 151.32 L +277.484 151.38 L +277.848 151.439 L +278.213 151.497 L +278.577 151.554 L +278.941 151.611 L +279.306 151.666 L +279.67 151.721 L +280.034 151.775 L +280.399 151.828 L +280.763 151.881 L +281.127 151.932 L +281.492 151.983 L +281.856 152.034 L +282.22 152.083 L +282.585 152.132 L +282.949 152.181 L +283.313 152.228 L +283.678 152.275 L +284.042 152.321 L +284.406 152.367 L +284.771 152.412 L +285.135 152.457 L +285.499 152.501 L +285.864 152.544 L +286.228 152.587 L +286.592 152.63 L +286.957 152.671 L +287.321 152.713 L +287.685 152.753 L +288.05 152.794 L +288.414 152.833 L +288.778 152.872 L +289.143 152.911 L +289.507 152.949 L +289.871 152.987 L +290.236 153.025 L +290.6 153.062 L +290.964 153.098 L +291.329 153.134 L +291.693 153.17 L +292.058 153.205 L +292.422 153.24 L +292.786 153.274 L +293.151 153.308 L +293.515 153.342 L +293.879 153.375 L +294.244 153.408 L +294.608 153.44 L +294.972 153.473 L +295.337 153.504 L +295.701 153.536 L +296.065 153.567 L +296.43 153.597 L +296.794 153.628 L +297.158 153.658 L +297.523 153.688 L +297.887 153.717 L +298.251 153.746 L +298.616 153.775 L +298.98 153.803 L +299.344 153.832 L +299.709 153.86 L +300.073 153.887 L +300.437 153.914 L +300.802 153.941 L +301.166 153.968 L +301.53 153.995 L +301.895 154.021 L +302.259 154.047 L +302.623 154.072 L +302.988 154.098 L +303.352 154.123 L +303.716 154.148 L +304.081 154.173 L +304.445 154.197 L +304.809 154.221 L +305.174 154.245 L +305.538 154.269 L +305.902 154.292 L +306.267 154.315 L +306.631 154.338 L +306.995 154.361 L +307.36 154.384 L +307.724 154.406 L +308.088 154.428 L +308.453 154.45 L +308.817 154.472 L +309.181 154.493 L +309.546 154.515 L +309.91 154.536 L +310.274 154.557 L +310.639 154.578 L +311.003 154.598 L +311.367 154.618 L +311.732 154.639 L +312.096 154.659 L +312.461 154.678 L +312.825 154.698 L +313.189 154.717 L +313.554 154.737 L +313.918 154.756 L +314.282 154.775 L +314.647 154.793 L +315.011 154.812 L +315.375 154.83 L +315.74 154.849 L +316.104 154.867 L +316.468 154.885 L +316.833 154.902 L +317.197 154.92 L +317.561 154.938 L +317.926 154.955 L +318.29 154.972 L +318.654 154.989 L +319.019 155.006 L +319.383 155.023 L +319.747 155.039 L +320.112 155.056 L +320.476 155.072 L +320.84 155.088 L +321.205 155.104 L +321.569 155.12 L +321.933 155.136 L +322.298 155.152 L +322.662 155.167 L +323.026 155.183 L +323.391 155.198 L +323.755 155.213 L +324.119 155.228 L +324.484 155.243 L +324.848 155.258 L +325.212 155.272 L +325.577 155.287 L +325.941 155.301 L +326.305 155.316 L +326.67 155.33 L +327.034 155.344 L +327.398 155.358 L +327.763 155.372 L +328.127 155.386 L +328.491 155.399 L +328.856 155.413 L +329.22 155.426 L +329.584 155.439 L +329.949 155.453 L +330.313 155.466 L +330.677 155.479 L +331.042 155.492 L +331.406 155.505 L +331.77 155.517 L +332.135 155.53 L +332.499 155.542 L +332.864 155.555 L +333.228 155.567 L +333.592 155.579 L +333.957 155.592 L +334.321 155.604 L +334.685 155.616 L +335.05 155.628 L +335.414 155.639 L +335.778 155.651 L +336.143 155.663 L +336.507 155.674 L +336.871 155.686 L +337.236 155.697 L +337.6 155.708 L +337.964 155.719 L +338.329 155.731 L +338.693 155.742 L +339.057 155.753 L +339.422 155.763 L +339.786 155.774 L +340.15 155.785 L +340.515 155.796 L +340.879 155.806 L +341.243 155.817 L +341.608 155.827 L +341.972 155.838 L +342.336 155.848 L +342.701 155.858 L +343.065 155.868 L +343.429 155.878 L +343.794 155.888 L +344.158 155.898 L +344.522 155.908 L +344.887 155.918 L +345.251 155.927 L +345.615 155.937 L +345.98 155.947 L +346.344 155.956 L +346.708 155.966 L +347.073 155.975 L +347.437 155.984 L +347.801 155.994 L +348.166 156.003 L +348.53 156.012 L +348.894 156.021 L +349.259 156.03 L +349.623 156.039 L +349.987 156.048 L +350.352 156.057 L +350.716 156.065 L +351.08 156.074 L +351.445 156.083 L +351.809 156.091 L +352.173 156.1 L +352.538 156.108 L +352.902 156.117 L +353.266 156.125 L +353.631 156.133 L +353.995 156.142 L +354.36 156.15 L +354.724 156.158 L +355.088 156.166 L +355.453 156.174 L +355.817 156.182 L +356.181 156.19 L +356.546 156.198 L +356.91 156.206 L +357.274 156.214 L +357.639 156.221 L +358.003 156.229 L +358.367 156.237 L +358.732 156.244 L +359.096 156.252 L +359.46 156.259 L +359.825 156.267 L +360.189 156.274 L +360.553 156.282 L +360.918 156.289 L +361.282 156.296 L +361.646 156.303 L +362.011 156.311 L +362.375 156.318 L +362.739 156.325 L +363.104 156.332 L +363.468 156.339 L +363.832 156.346 L +364.197 156.353 L +364.561 156.36 L +364.925 156.366 L +365.29 156.373 L +365.654 156.38 L +366.018 156.387 L +366.383 156.393 L +366.747 156.4 L +367.111 156.407 L +367.476 156.413 L +367.84 156.42 L +368.204 156.426 L +368.569 156.433 L +368.933 156.439 L +369.297 156.445 L +369.662 156.452 L +370.026 156.458 L +370.39 156.464 L +370.755 156.47 L +371.119 156.476 L +371.483 156.483 L +371.848 156.489 L +372.212 156.495 L +372.576 156.501 L +372.941 156.507 L +373.305 156.513 L +373.669 156.519 L +374.034 156.524 L +374.398 156.53 L +374.763 156.536 L +375.127 156.542 L +375.491 156.548 L +375.856 156.553 L +376.22 156.559 L +376.584 156.565 L +376.949 156.57 L +377.313 156.576 L +377.677 156.581 L +378.042 156.587 L +378.406 156.592 L +378.77 156.598 L +379.135 156.603 L +379.499 156.609 L +379.863 156.614 L +380.228 156.619 L +380.592 156.625 L +380.956 156.63 L +381.321 156.635 L +381.685 156.64 L +382.049 156.646 L +382.414 156.651 L +382.778 156.656 L +383.142 156.661 L +383.507 156.666 L +383.871 156.671 L +384.235 156.676 L +384.6 156.681 L +384.964 156.686 L +385.328 156.691 L +385.693 156.696 L +386.057 156.701 L +386.421 156.706 L +386.786 156.71 L +387.15 156.715 L +387.514 156.72 L +387.879 156.725 L +388.243 156.729 L +388.607 156.734 L +388.972 156.739 L +389.336 156.743 L +389.7 156.748 L +390.065 156.753 L +390.429 156.757 L +390.793 156.762 L +391.158 156.766 L +391.522 156.771 L +391.886 156.775 L +392.251 156.78 L +392.615 156.784 L +392.979 156.788 L +393.344 156.793 L +393.708 156.797 L +394.072 156.802 L +394.437 156.806 L +394.801 156.81 L +395.166 156.814 L +395.53 156.819 L +395.894 156.823 L +396.259 156.827 L +396.623 156.831 L +396.987 156.835 L +397.352 156.84 L +397.716 156.844 L +398.08 156.848 L +398.445 156.852 L +398.809 156.856 L +399.173 156.86 L +399.538 156.864 L +399.902 156.868 L +400.266 156.872 L +400.631 156.876 L +400.995 156.88 L +401.359 156.884 L +401.724 156.887 L +402.088 156.891 L +402.452 156.895 L +402.817 156.899 L +403.181 156.903 L +403.545 156.907 L +403.91 156.91 L +404.274 156.914 L +404.638 156.918 L +405.003 156.921 L +405.367 156.925 L +405.731 156.929 L +406.096 156.933 L +406.46 156.936 L +406.824 156.94 L +407.189 156.943 L +407.553 156.947 L +407.917 156.95 L +408.282 156.954 L +408.646 156.958 L +409.01 156.961 L +409.375 156.965 L +409.739 156.968 L +410.103 156.972 L +410.468 156.975 L +410.832 156.978 L +411.196 156.982 L +411.561 156.985 L +411.925 156.989 L +412.289 156.992 L +412.654 156.995 L +413.018 156.999 L +413.382 157.002 L +413.747 157.005 L +414.111 157.008 L +414.475 157.012 L +414.84 157.015 L +415.204 157.018 L +415.568 157.021 L +415.933 157.025 L +416.297 157.028 L +416.662 157.031 L +417.026 157.034 L +417.39 157.037 L +417.755 157.04 L +418.119 157.043 L +418.483 157.047 L +418.848 157.05 L +419.212 157.053 L +419.576 157.056 L +419.941 157.059 L +420.305 157.062 L +420.669 157.065 L +421.034 157.068 L +421.398 157.071 L +421.762 157.074 L +422.127 157.077 L +422.491 157.08 L +422.855 157.083 L +423.22 157.085 L +423.584 157.088 L +423.948 157.091 L +424.313 157.094 L +424.677 157.097 L +425.041 157.1 L +425.406 157.103 L +425.77 157.105 L +426.134 157.108 L +426.499 157.111 L +426.863 157.114 L +427.227 157.117 L +427.592 157.119 L +427.956 157.122 L +428.32 157.125 L +428.685 157.127 L +429.049 157.13 L +429.413 157.133 L +429.778 157.136 L +430.142 157.138 L +430.506 157.141 L +430.871 157.143 L +431.235 157.146 L +431.599 157.149 L +431.964 157.151 L +432.328 157.154 L +432.692 157.156 L +433.057 157.159 L +433.421 157.162 L +433.785 157.164 L +434.15 157.167 L +434.514 157.169 L +434.878 157.172 L +435.243 157.174 L +435.607 157.177 L +435.971 157.179 L +436.336 157.182 L +436.7 157.184 L +437.065 157.187 L +437.429 157.189 L +437.793 157.191 L +438.158 157.194 L +438.522 157.196 L +438.886 157.199 L +439.251 157.201 L +439.615 157.203 L +439.979 157.206 L +440.344 157.208 L +440.708 157.21 L +441.072 157.213 L +441.437 157.215 L +441.801 157.217 L +442.165 157.22 L +442.53 157.222 L +s +78.19 230.21 m +78.919 229.983 L +79.648 229.756 L +80.376 229.529 L +81.105 229.302 L +81.834 229.074 L +82.562 228.847 L +83.291 228.619 L +84.02 228.391 L +84.749 228.163 L +85.477 227.935 L +86.206 227.707 L +86.935 227.479 L +87.663 227.251 L +88.392 227.023 L +89.121 226.795 L +89.849 226.567 L +90.578 226.339 L +91.307 226.111 L +92.035 225.884 L +92.764 225.656 L +93.493 225.429 L +94.221 225.201 L +94.95 224.974 L +95.679 224.747 L +96.407 224.52 L +97.136 224.294 L +97.865 224.067 L +98.593 223.841 L +99.322 223.615 L +100.051 223.39 L +100.779 223.164 L +101.508 222.939 L +102.237 222.714 L +102.965 222.49 L +103.694 222.266 L +104.423 222.042 L +105.152 221.819 L +105.88 221.596 L +106.609 221.373 L +107.338 221.151 L +108.066 220.929 L +108.795 220.707 L +109.524 220.486 L +110.252 220.265 L +110.981 220.045 L +111.71 219.825 L +112.438 219.606 L +113.167 219.387 L +113.896 219.169 L +114.624 218.951 L +115.353 218.733 L +116.082 218.517 L +116.81 218.3 L +117.539 218.085 L +118.268 217.869 L +118.996 217.655 L +119.725 217.44 L +120.454 217.227 L +121.182 217.014 L +121.911 216.801 L +122.64 216.59 L +123.368 216.378 L +124.097 216.168 L +124.826 215.958 L +125.554 215.748 L +126.283 215.539 L +127.012 163.232 L +127.741 163.025 L +128.469 162.818 L +129.198 162.612 L +129.927 162.406 L +130.655 162.201 L +131.384 161.997 L +132.113 161.794 L +132.841 161.591 L +133.57 161.39 L +134.299 161.188 L +135.027 160.988 L +135.756 160.789 L +136.485 160.59 L +137.213 160.392 L +137.942 160.194 L +138.671 159.998 L +139.399 159.803 L +140.128 159.608 L +140.857 159.414 L +141.585 159.221 L +142.314 159.029 L +143.043 158.839 L +143.771 158.648 L +144.5 158.459 L +145.229 158.271 L +145.957 158.084 L +146.686 157.898 L +147.415 157.714 L +148.144 157.53 L +148.872 157.347 L +149.601 157.166 L +150.33 156.985 L +151.058 156.806 L +151.787 156.628 L +152.516 156.452 L +153.244 156.277 L +153.973 156.103 L +154.702 155.931 L +155.43 155.761 L +156.159 155.591 L +156.888 155.424 L +157.616 155.258 L +158.345 155.094 L +159.074 154.931 L +159.802 154.771 L +160.531 154.612 L +161.26 154.455 L +161.988 154.3 L +162.717 154.148 L +163.446 153.997 L +164.174 153.849 L +164.903 153.703 L +165.632 153.56 L +166.36 153.419 L +167.089 153.28 L +167.818 153.145 L +168.547 153.012 L +169.275 152.882 L +170.004 152.754 L +170.733 152.631 L +171.461 152.51 L +172.19 152.392 L +172.919 152.278 L +173.647 152.167 L +174.376 152.06 L +175.105 151.956 L +175.833 151.856 L +176.562 151.76 L +177.291 151.668 L +178.019 151.58 L +178.748 151.497 L +179.477 151.417 L +180.205 151.342 L +180.934 151.271 L +181.663 151.205 L +182.391 151.143 L +183.12 151.086 L +183.849 151.033 L +184.577 150.985 L +185.306 150.942 L +186.035 150.904 L +186.763 150.87 L +187.492 150.841 L +188.221 150.817 L +188.95 150.797 L +189.678 150.783 L +190.407 150.773 L +191.136 150.767 L +191.864 150.766 L +192.593 150.77 L +193.322 150.778 L +194.05 150.79 L +194.779 150.806 L +195.508 150.827 L +196.236 150.851 L +196.965 150.88 L +197.694 150.911 L +198.422 150.947 L +199.151 150.986 L +199.88 151.028 L +200.608 151.073 L +201.337 151.121 L +202.066 151.172 L +202.794 151.225 L +203.523 151.281 L +204.252 151.339 L +204.98 151.398 L +205.709 151.46 L +206.438 151.524 L +207.166 151.589 L +207.895 151.656 L +208.624 151.723 L +209.353 151.792 L +210.081 151.862 L +210.81 151.933 L +211.539 152.005 L +212.267 152.077 L +212.996 152.149 L +213.725 152.222 L +214.453 152.295 L +215.182 152.368 L +215.911 152.441 L +216.639 152.515 L +217.368 152.588 L +218.097 152.661 L +218.825 152.733 L +219.554 152.806 L +220.283 152.877 L +221.011 152.949 L +221.74 153.019 L +222.469 153.09 L +223.197 153.159 L +223.926 153.228 L +224.655 153.296 L +225.383 153.364 L +226.112 153.431 L +226.841 153.497 L +227.569 153.562 L +228.298 153.626 L +229.027 153.69 L +229.756 153.753 L +230.484 153.815 L +231.213 153.876 L +231.942 153.936 L +232.67 153.995 L +233.399 154.053 L +234.128 154.111 L +234.856 154.167 L +235.585 154.223 L +236.314 154.278 L +237.042 154.332 L +237.771 154.385 L +238.5 154.437 L +239.228 154.488 L +239.957 154.539 L +240.686 154.589 L +241.414 154.637 L +242.143 154.685 L +242.872 154.733 L +243.6 154.779 L +244.329 154.824 L +245.058 154.869 L +245.786 154.913 L +246.515 154.957 L +247.244 154.999 L +247.972 155.041 L +248.701 155.082 L +249.43 155.122 L +250.159 155.162 L +250.887 155.201 L +251.616 155.239 L +252.345 155.276 L +253.073 155.313 L +253.802 155.35 L +254.531 155.385 L +255.259 155.42 L +255.988 155.455 L +256.717 155.488 L +257.445 155.521 L +258.174 155.554 L +258.903 155.586 L +259.631 155.618 L +260.36 155.648 L +261.089 155.679 L +261.817 155.709 L +262.546 155.738 L +263.275 155.767 L +264.003 155.795 L +264.732 155.823 L +265.461 155.85 L +266.189 155.877 L +266.918 155.904 L +267.647 155.93 L +268.375 155.955 L +269.104 155.98 L +269.833 156.005 L +270.561 156.029 L +271.29 156.053 L +272.019 156.077 L +272.748 156.1 L +273.476 156.122 L +274.205 156.145 L +274.934 156.167 L +275.662 156.188 L +276.391 156.209 L +277.12 156.23 L +277.848 156.251 L +278.577 156.271 L +279.306 156.291 L +280.034 156.31 L +280.763 156.33 L +281.492 156.349 L +282.22 156.367 L +282.949 156.385 L +283.678 156.404 L +284.406 156.421 L +285.135 156.439 L +285.864 156.456 L +286.592 156.473 L +287.321 156.49 L +288.05 156.506 L +288.778 156.522 L +289.507 156.538 L +290.236 156.554 L +290.964 156.569 L +291.693 156.584 L +292.422 156.599 L +293.151 156.614 L +293.879 156.628 L +294.608 156.643 L +295.337 156.657 L +296.065 156.671 L +296.794 156.684 L +297.523 156.698 L +298.251 156.711 L +298.98 156.724 L +299.709 156.737 L +300.437 156.75 L +301.166 156.762 L +301.895 156.774 L +302.623 156.787 L +303.352 156.799 L +304.081 156.81 L +304.809 156.822 L +305.538 156.833 L +306.267 156.845 L +306.995 156.856 L +307.724 156.867 L +308.453 156.878 L +309.181 156.888 L +309.91 156.899 L +310.639 156.909 L +311.367 156.92 L +312.096 156.93 L +312.825 156.94 L +313.554 156.949 L +314.282 156.959 L +315.011 156.969 L +315.74 156.978 L +316.468 156.988 L +317.197 156.997 L +317.926 157.006 L +318.654 157.015 L +319.383 157.024 L +320.112 157.032 L +320.84 157.041 L +321.569 157.049 L +322.298 157.058 L +323.026 157.066 L +323.755 157.074 L +324.484 157.082 L +325.212 157.09 L +325.941 157.098 L +326.67 157.106 L +327.398 157.113 L +328.127 157.121 L +328.856 157.128 L +329.584 157.136 L +330.313 157.143 L +331.042 157.15 L +331.77 157.157 L +332.499 157.164 L +333.228 157.171 L +333.957 157.178 L +334.685 157.185 L +335.414 157.192 L +336.143 157.198 L +336.871 157.205 L +337.6 157.211 L +338.329 157.217 L +339.057 157.224 L +339.786 157.23 L +340.515 157.236 L +341.243 157.242 L +341.972 157.248 L +342.701 157.254 L +343.429 157.26 L +344.158 157.266 L +344.887 157.271 L +345.615 157.277 L +346.344 157.283 L +347.073 157.288 L +347.801 157.294 L +348.53 157.299 L +349.259 157.304 L +349.987 157.31 L +350.716 157.315 L +351.445 157.32 L +352.173 157.325 L +352.902 157.33 L +353.631 157.335 L +354.36 157.34 L +355.088 157.345 L +355.817 157.35 L +356.546 157.354 L +357.274 157.359 L +358.003 157.364 L +358.732 157.368 L +359.46 157.373 L +360.189 157.377 L +360.918 157.382 L +361.646 157.386 L +362.375 157.391 L +363.104 157.395 L +363.832 157.399 L +364.561 157.403 L +365.29 157.408 L +366.018 157.412 L +366.747 157.416 L +367.476 157.42 L +368.204 157.424 L +368.933 157.428 L +369.662 157.432 L +370.39 157.436 L +371.119 157.439 L +371.848 157.443 L +372.576 157.447 L +373.305 157.451 L +374.034 157.454 L +374.763 157.458 L +375.491 157.462 L +376.22 157.465 L +376.949 157.469 L +377.677 157.472 L +378.406 157.476 L +379.135 157.479 L +379.863 157.482 L +380.592 157.486 L +381.321 157.489 L +382.049 157.492 L +382.778 157.496 L +383.507 157.499 L +384.235 157.502 L +384.964 157.505 L +385.693 157.508 L +386.421 157.512 L +387.15 157.515 L +387.879 157.518 L +388.607 157.521 L +389.336 157.524 L +390.065 157.527 L +390.793 157.53 L +391.522 157.532 L +392.251 157.535 L +392.979 157.538 L +393.708 157.541 L +394.437 157.544 L +395.166 157.547 L +395.894 157.549 L +396.623 157.552 L +397.352 157.555 L +398.08 157.557 L +398.809 157.56 L +399.538 157.563 L +400.266 157.565 L +400.995 157.568 L +401.724 157.57 L +402.452 157.573 L +403.181 157.575 L +403.91 157.578 L +404.638 157.58 L +405.367 157.583 L +406.096 157.585 L +406.824 157.587 L +407.553 157.59 L +408.282 157.592 L +409.01 157.594 L +409.739 157.597 L +410.468 157.599 L +411.196 157.601 L +411.925 157.603 L +412.654 157.606 L +413.382 157.608 L +414.111 157.61 L +414.84 157.612 L +415.568 157.614 L +416.297 157.616 L +417.026 157.619 L +417.755 157.621 L +418.483 157.623 L +419.212 157.625 L +419.941 157.627 L +420.669 157.629 L +421.398 157.631 L +422.127 157.633 L +422.855 157.635 L +423.584 157.637 L +424.313 157.639 L +425.041 157.64 L +425.77 157.642 L +426.499 157.644 L +427.227 157.646 L +427.956 157.648 L +428.685 157.65 L +429.413 157.652 L +430.142 157.653 L +430.871 157.655 L +431.599 157.657 L +432.328 157.659 L +433.057 157.66 L +433.785 157.662 L +434.514 157.664 L +435.243 157.666 L +435.971 157.667 L +436.7 157.669 L +437.429 157.671 L +438.158 157.672 L +438.886 157.674 L +439.615 157.675 L +440.344 157.677 L +441.072 157.679 L +441.801 157.68 L +442.53 157.682 L +s +P +0 g +0.144 w +[ ] 0 setdash +3.25 setmiterlimit +450.12 237.508 m +70.6 237.508 L +s +70.6 237.508 m +70.6 2.952 L +s +1 g +[ ] 0 setdash +70.6 2.952 m +450.12 2.952 L +s +450.12 2.952 m +450.12 237.508 L +s +0 g +[ ] 0 setdash +p +0 setlinecap +78.19 237.508 m +78.19 234.611 L +s +P +p +np 74 239 m +74 253 L +82 253 L +82 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 75.19 239.668 ] concat +1 w +[ ] 0 setdash +p +np -2.19 -1.668 m +-2.19 14.332 L +7.81 14.332 L +7.81 -1.668 L +cp +clip np +/MISOfy +{ + /newfontname exch def + /oldfontname exch def + oldfontname findfont + dup length dict begin + {1 index/FID ne{def}{pop pop}ifelse}forall + /Encoding ISOLatin1Encoding def + currentdict + end + newfontname exch definefont pop +}def +%%IncludeResource: font Times-Roman +%%IncludeFont: Times-Roman +%%BeginResource: font Times-Roman-MISO +%%BeginFont: Times-Roman-MISO +/Times-Roman /Times-Roman-MISO MISOfy +%%EndFont +%%EndResource +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(0) N +P +[1 0 0 1 -75.19 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +92.764 237.508 m +92.764 235.77 L +s +P +p +0 setlinecap +107.338 237.508 m +107.338 235.77 L +s +P +p +0 setlinecap +121.911 237.508 m +121.911 235.77 L +s +P +p +0 setlinecap +136.485 237.508 m +136.485 235.77 L +s +P +p +0 setlinecap +151.058 237.508 m +151.058 234.611 L +s +P +p +np 147 239 m +147 253 L +155 253 L +155 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 148.058 239.668 ] concat +1 w +[ ] 0 setdash +p +np -2.058 -1.668 m +-2.058 14.332 L +7.942 14.332 L +7.942 -1.668 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(1) N +P +[1 0 0 1 -148.058 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +165.632 237.508 m +165.632 235.77 L +s +P +p +0 setlinecap +180.205 237.508 m +180.205 235.77 L +s +P +p +0 setlinecap +194.779 237.508 m +194.779 235.77 L +s +P +p +0 setlinecap +209.353 237.508 m +209.353 235.77 L +s +P +p +0 setlinecap +223.926 237.508 m +223.926 234.611 L +s +P +p +np 220 239 m +220 253 L +228 253 L +228 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 220.926 239.668 ] concat +1 w +[ ] 0 setdash +p +np -1.926 -1.668 m +-1.926 14.332 L +8.074 14.332 L +8.074 -1.668 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(2) N +P +[1 0 0 1 -220.926 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +238.5 237.508 m +238.5 235.77 L +s +P +p +0 setlinecap +253.073 237.508 m +253.073 235.77 L +s +P +p +0 setlinecap +267.647 237.508 m +267.647 235.77 L +s +P +p +0 setlinecap +282.22 237.508 m +282.22 235.77 L +s +P +p +0 setlinecap +296.794 237.508 m +296.794 234.611 L +s +P +p +np 293 239 m +293 253 L +301 253 L +301 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 293.794 239.668 ] concat +1 w +[ ] 0 setdash +p +np -1.794 -1.668 m +-1.794 14.332 L +8.206 14.332 L +8.206 -1.668 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(3) N +P +[1 0 0 1 -293.794 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +311.367 237.508 m +311.367 235.77 L +s +P +p +0 setlinecap +325.941 237.508 m +325.941 235.77 L +s +P +p +0 setlinecap +340.515 237.508 m +340.515 235.77 L +s +P +p +0 setlinecap +355.088 237.508 m +355.088 235.77 L +s +P +p +0 setlinecap +369.662 237.508 m +369.662 234.611 L +s +P +p +np 365 239 m +365 253 L +374 253 L +374 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 366.287 239.668 ] concat +1 w +[ ] 0 setdash +p +np -2.287 -1.668 m +-2.287 14.332 L +8.713 14.332 L +8.713 -1.668 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0.75 10.5 m +(4) N +P +[1 0 0 1 -366.287 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +384.235 237.508 m +384.235 235.77 L +s +P +p +0 setlinecap +398.809 237.508 m +398.809 235.77 L +s +P +p +0 setlinecap +413.382 237.508 m +413.382 235.77 L +s +P +p +0 setlinecap +427.956 237.508 m +427.956 235.77 L +s +P +p +0 setlinecap +442.53 237.508 m +442.53 234.611 L +s +P +p +np 439 239 m +439 253 L +447 253 L +447 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 439.53 239.668 ] concat +1 w +[ ] 0 setdash +p +np -1.53 -1.668 m +-1.53 14.332 L +8.47 14.332 L +8.47 -1.668 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(5) N +P +[1 0 0 1 -439.53 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +70.6 210.041 m +73.497 210.041 L +s +P +p +np 61 203 m +61 217 L +69 217 L +69 203 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 62.44 203.666 ] concat +1 w +[ ] 0 setdash +p +np -2.44 -1.666 m +-2.44 14.334 L +7.56 14.334 L +7.56 -1.666 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(0) N +P +[1 0 0 1 -62.44 -203.666 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +70.6 157.941 m +73.497 157.941 L +s +P +p +np 61 151 m +61 165 L +69 165 L +69 151 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 61.69 151.566 ] concat +1 w +[ ] 0 setdash +p +np -1.69 -1.566 m +-1.69 14.434 L +8.31 14.434 L +8.31 -1.566 L +cp +clip np +%%BeginResource: font Mathematica1 +%%BeginFont: Mathematica1 +%!PS-AdobeFont-1.0: Mathematica1 001.000 +%%CreationDate: 8/26/01 at 4:07 PM +%%VMusage: 1024 31527 +% Mathematica typeface design by Andre Kuzniarek, with Gregg Snyder and Stephen Wolfram. Copyright \(c\) 1996-2001 Wolfram Research, Inc. [http://www.wolfram.com]. All rights reserved. [Font version 2.00] +% ADL: 800 200 0 +%%EndComments +FontDirectory/Mathematica1 known{/Mathematica1 findfont dup/UniqueID known{dup +/UniqueID get 5095641 eq exch/FontType get 1 eq and}{pop false}ifelse +{save true}{false}ifelse}{false}ifelse +20 dict begin +/FontInfo 16 dict dup begin + /version (001.000) readonly def + /FullName (Mathematica1) readonly def + /FamilyName (Mathematica1) readonly def + /Weight (Medium) readonly def + /ItalicAngle 0 def + /isFixedPitch false def + /UnderlinePosition -133 def + /UnderlineThickness 20 def + /Notice (Mathematica typeface design by Andre Kuzniarek, with Gregg Snyder and Stephen Wolfram. Copyright \(c\) 1996-2001 Wolfram Research, Inc. [http://www.wolfram.com]. All rights reserved. [Font version 2.00]) readonly def + /em 1000 def + /ascent 800 def + /descent 200 def +end readonly def +/FontName /Mathematica1 def +/Encoding 256 array +dup 0/NUL put +dup 1/Eth put +dup 2/eth put +dup 3/Lslash put +dup 4/lslash put +dup 5/Scaron put +dup 6/scaron put +dup 7/Yacute put +dup 8/yacute put +dup 9/HT put +dup 10/LF put +dup 11/Thorn put +dup 12/thorn put +dup 13/CR put +dup 14/Zcaron put +dup 15/zcaron put +dup 16/DLE put +dup 17/DC1 put +dup 18/DC2 put +dup 19/DC3 put +dup 20/DC4 put +dup 21/onehalf put +dup 22/onequarter put +dup 23/onesuperior put +dup 24/threequarters put +dup 25/threesuperior put +dup 26/twosuperior put +dup 27/brokenbar put +dup 28/minus put +dup 29/multiply put +dup 30/RS put +dup 31/US put +dup 32/Space put +dup 33/Exclamation put +dup 34/ForAll put +dup 35/NumberSign put +dup 36/Exists put +dup 37/Percent put +dup 38/Ampersand put +dup 39/SmallMember put +dup 40/LParen put +dup 41/RParen put +dup 42/Star put +dup 43/Plus put +dup 44/Comma put +dup 45/Minus put +dup 46/Period put +dup 47/Slash put +dup 48/Zero put +dup 49/One put +dup 50/Two put +dup 51/Three put +dup 52/Four put +dup 53/Five put +dup 54/Six put +dup 55/Seven put +dup 56/Eight put +dup 57/Nine put +dup 58/Colon put +dup 59/SemiColon put +dup 60/Less put +dup 61/Equal put +dup 62/Greater put +dup 63/Question put +dup 64/TildeFullEqual put +dup 65/CapAlpha put +dup 66/CapBeta put +dup 67/CapChi put +dup 68/CapDelta put +dup 69/CapEpsilon put +dup 70/CapPhi put +dup 71/CapGamma put +dup 72/CapEta put +dup 73/CapIota put +dup 74/CurlyTheta put +dup 75/CapKappa put +dup 76/CapLambda put +dup 77/CapMu put +dup 78/CapNu put +dup 79/CapOmicron put +dup 80/CapPi put +dup 81/CapTheta put +dup 82/CapRho put +dup 83/CapSigma put +dup 84/CapTau put +dup 85/CapUpsilon put +dup 86/FinalSigma put +dup 87/CapOmega put +dup 88/CapXi put +dup 89/CapPsi put +dup 90/CapZeta put +dup 91/LBracket put +dup 92/Therefore put +dup 93/RBracket put +dup 94/Perpendicular put +dup 95/Underbar put +dup 96/Hat put +dup 97/Alpha put +dup 98/Beta put +dup 99/Chi put +dup 100/Delta put +dup 101/Epsilon put +dup 102/Phi put +dup 103/Gamma put +dup 104/Eta put +dup 105/Iota put +dup 106/CurlyPhi put +dup 107/Kappa put +dup 108/Lambda put +dup 109/Mu put +dup 110/Nu put +dup 111/Omicron put +dup 112/Pi put +dup 113/Theta put +dup 114/Rho put +dup 115/Sigma put +dup 116/Tau put +dup 117/Upsilon put +dup 118/CurlyPi put +dup 119/Omega put +dup 120/Xi put +dup 121/Psi put +dup 122/Zeta put +dup 123/LBrace put +dup 124/VertBar put +dup 125/RBrace put +dup 126/Tilde put +dup 127/DEL put +dup 128/FractionBarExt put +dup 129/EscapeChar put +dup 130/SelectPlaceholder put +dup 131/Placeholder put +dup 132/Continuation put +dup 133/Skeleton put +dup 134/LSkeleton put +dup 135/RSkeleton put +dup 136/Spacer put +dup 137/Cross put +dup 138/DblEqual put +dup 139/Grave put +dup 140/Acute put +dup 141/DoubleAcute put +dup 142/OverTilde put +dup 143/OverBar put +dup 144/DblUpDownArrow put +dup 145/DblUpExtens1 put +dup 146/DblLongLArrow put +dup 147/DblExtens put +dup 148/DblLongRArrow put +dup 149/DblLRArrow2 put +dup 150/DblLongLRArrow put +dup 151/UpDownArrow put +dup 152/LongLArrow put +dup 153/LongRArrow put +dup 154/LongLRArrow put +dup 155/ColonEqual put +dup 156/Diamond2 put +dup 157/NotSquareSprsetEqual put +dup 158/AtSign put +dup 159/Solidmedsqr put +dup 160/OverDot put +dup 161/CurlyCapUpsilon put +dup 162/Prime put +dup 163/LessEqual put +dup 164/Fraction put +dup 165/Infinity put +dup 166/RuleDelayed put +dup 167/ClubSuit put +dup 168/DiamondSuit put +dup 169/HeartSuit put +dup 170/SpadeSuit put +dup 171/LRArrow put +dup 172/LArrow put +dup 173/UpArrow put +dup 174/RArrow put +dup 175/DownArrow put +dup 176/Degree put +dup 177/PlusMinus put +dup 178/DoublePrime put +dup 179/GreaterEqual put +dup 180/Multiply put +dup 181/Proportional put +dup 182/PartialDiff put +dup 183/Bullet put +dup 184/Divide put +dup 185/NotEqual put +dup 186/Equivalence put +dup 187/Approxequal put +dup 188/Ellipsis put +dup 189/ArrowVertEx put +dup 190/ArrowHorizEx put +dup 191/CarriageReturn put +dup 192/Aleph put +dup 193/IFraktur put +dup 194/RFraktur put +dup 195/Weierstrass put +dup 196/CircleMultiply put +dup 197/CirclePlus put +dup 198/EmptySet put +dup 199/Union put +dup 200/Intersection put +dup 201/ProperSuperset put +dup 202/NbSpace put +dup 203/NotSubset put +dup 204/ProperSubset put +dup 205/ReflexSubset put +dup 206/Element put +dup 207/NotElement put +dup 208/Angle put +dup 209/Gradient put +dup 210/RegTM put +dup 211/Copyright put +dup 212/TM put +dup 213/Product put +dup 214/Radical put +dup 215/DotMath put +dup 216/LogicalNot put +dup 217/Wedge put +dup 218/Vee put +dup 219/DblLRArrow put +dup 220/DblLArrow put +dup 221/DblUpArrow put +dup 222/DblRArrow put +dup 223/DblDownArrow put +dup 224/Lozenge put +dup 225/LAngle put +dup 226/Diffd put +dup 227/Expe put +dup 228/Imagi put +dup 229/Sum put +dup 230/LParenTop put +dup 231/LParenEx put +dup 232/LParenBot put +dup 233/LBracketTop put +dup 234/LBracketEx put +dup 235/LBracketBot put +dup 236/LBraceTop put +dup 237/LBraceMid put +dup 238/LBraceBot put +dup 239/BraceEx put +dup 240/Slot put +dup 241/RAngle put +dup 242/Intergral put +dup 243/IntegralTop put +dup 244/IntegralEx put +dup 245/IntegralBot put +dup 246/RParenTop put +dup 247/RParenEx put +dup 248/RParenBot put +dup 249/RBracketTop put +dup 250/RBracketEx put +dup 251/RBracketBot put +dup 252/RBraceTop put +dup 253/RBraceMid put +dup 254/RBraceBot put +dup 255/Wolf put + readonly def +/PaintType 0 def +/FontType 1 def +/StrokeWidth 0 def +/FontMatrix[0.001 0 0 0.001 0 0]readonly def +/UniqueID 5095641 def +/FontBBox{-120 -220 1544 923}readonly def +currentdict end +currentfile eexec +D8061D93A8246509E76A3EC656E953B7C22E43117F5A3BC2421790057C314DAE3EFBFF49F45DA34B +424A961BE670A194E7E4BF0FF295DEE23134A14A7C08B6602621D885EE631B1D8D3003CF2093E039 +4D77FCEFCA8BA8965D1783DCAD9EFE6C7E420CF7B898933309A89F84721ADE7F3AE4443C5EAE8155 +759A9EB6F738F7BA81C192EE45DAD00F398FFD6904111BBD91BFEE328004F2A18BCCD98DCDB2CE23 +961B00F204E50EA022D00CE2B68E653626D4BB5AFA334A0D657307416FAF7AA8C43ED4DC541F1B7B +B7500B3F423D9D369F8192FD00A59FD5E6ABC70F788FB70976CC1907DDC309F4B690AA2D2BF12CAE +C493958CC0E76CE9EB5FF8BD1F1650F659E5C123EE455B7D77C39952C212AF319BF19A91E36DE52F +0EF84B602704BD6C7C94E1B0E067210DB919F6231755A2CC5D5FE129279B43A2E2CCD27F56F00B05 +C8AC10AB07FABBEFB3509088301FE78CAF8B440C5BA2FFE06BBFCD066046618F3B6AA2E4B17B296E +1F3F1560E19CBAD54E16E4B7A65622E468F6BDF97C50277E355D1DD8843D0A449A147FFBA071BA99 +CF70D7576DC18B96FEAF8070BF25F3A3141F873241EF4A07F332306B56F1D5909A4F233A9DB3A08E +E43DF38DD6DB2D6DAB908967A907303EE1FA04F048FA6EBC531738C170B8B0F095FF3B05D14C2BDC +272F7EDA8926A77D9CDA49A90AE1387A51A24ECDB2E4E287B0F95A83AD2EC0310F9B6F396AC10479 +835035FD5D4C84D91917FE8A8755C976504AB1A830ED516B5F325EA4ADFD115900D23039A2BC84EE +D21CC21E2BBE29A5E0CF28BE047CBD515DF7785F37DDD4474042B102A1F28193BB8567FF6FDEF811 +25CE9A504BE5011C0010DCEBCF321824C9DA249D8DB11F79298F7B674CEDB6F33C111F8B0115E407 +99E0FE1B6CE9F6B2A3EED1827A9CEB453D643FE5CE2540DCBCAF7B2EA2C8F0AE9434D4BAAEAB3488 +FEC7541D57179BDEAA0BB6145EA783E73E70E0AA71A4FA58E81EB510E9FD2CF4ACFBF28E48CA313C +CF5ED2BE032B7C9A07ABBEC9CCD8D5AC9F775D36BECC252B9FE01B6AA79A70E22478904DADDA08BB +7CA3B66F3A83AEEBA37406127353790348AE5FBD144ABD8E1B32D1BCC70E2BC22A63E854D38317E6 +BB97C52A6C9B0C6AB5C336CE2D417A714825DCD237F7486C33925A995CD8AD3B359176C4CA775FE2 +2C6F8A7C7343F31F39D1B9A5A744973BF65D655DDB59E3495B28DBE2A877EBB32A22A4AB8EB13C67 +02B239F6932E2DC8B4B88915923B1C2AFF2F876A31F765B91747D5A858BD632B0BE4F135AC484016 +AE1BC01E44B6653A58EE1380B6DF24AEB73220A36FA8FDE9A152C16E049D2F247A9AA71CD2DF3D9E +653054FAF518BBC1EEB43200DB0BACA93FEA9445FA5F32A99926C4F1F2370B2E3E03CEFBEECE5D5C +DE299FE1641A9CE0C90E42E7C465DF8C19C153EA76C11791F8E112853E708CD0F6EFC22E44808A44 +3686442645D643E7118D128BF34188FD313E53B8E951E605E96825738C4DC893D942C145C3E1153F +CDED16D1EE10374626F45781461FFC94C77D431BCF167FD29136A0B369631E139A59B06AC5281B3D +52470B38237B469C0E49CBE74C82A940F8AAD10E05C5DD2A8F3218C4BE40DCED0982770869190845 +D2E8BA2A1A2F9CF16DDDE418B6E2013C3258FBE9AFCDACCD57B01C4FEF3526C06FD5BAB6F195C557 +23A96FA3E5CDB2ADC9AA8989DF78F163F19E26F3B9EAF60D44B745FCA49B09D9CE5CC16D1E2C78C5 +A28F14D3374956E9721C0542A01A731A3D5A771C7B51FB72960BB22C99BC8F216530AA86C1A0346B +3D986EF1DF68BCC46EC4C809C6D54FB9C50F225ABA2C96965F4DE77E2C5D131318231C6773F0976C +DBD016B803C38B8923822BDF160FB87BBE744D0953EDEBDE87394282B9950C89F58824D731355E8F +A3CE364F742E3337A983BD1219CE8787CFA6925B560001F6F78343647821D408B60965966C16C454 +394D33268483F51984614FD9964CCE5F1AA4AB12144874A72D3FE97F4416ABE4213B4EDCA9ECF73A +937B562F978E059705E561A6D0C8B89B59B4CAB2248BFC4A6D3D98F89FF38A4D1C3E5A4570E2E2E8 +D328772E11DEA2116C260B13328045139A819F5489A2C302F311898230CD26DD03E6CE3BE75EDB86 +0D982FBC8E1E24D1F83E8CA64174495B2F32BDF0505FC96E9C65FDB0EB0C4ADA410F0A1C4BB4D551 +93B1AA5EA1F988882A56413F77CF24FF6516CD5095077BA566116797895FD7EA616D10510957025A +1DA05064FD9176303A248C94CE2A9875C03C7D8755A1A8010B712B64BAD73BEA4195D51A328F076D +12C0E52E87D98A5269D157D544CD7B4E1CAAEA6BDD59F08D0C5FBB84A6B099BECF8BEB721A874BAA +1BD1F8CDB5ED5CD3B49A763EAA36D33837667A6643B83537EF5515DBF7659E28692E0ACEB48FD051 +45534A443092E0A510B05B5B51B402CB3372A4E8BAF98A7FEB6D8BEF6B364A5EA0F3FDF301A44EE2 +3485D4493E7B4C33E0E352202931188D156983C40F7A4B615B6F5281B33FB32A2E6A0F7AE84BEA2C +5C3635D7DA17371D608847EB402270B83293EC09E4B1D85996C1CC81196BE590EC9EF0F258364746 +BC4763976FDDB3707D37AFCBDC0F4EB2B1502F137EBBB1541B992EAD43B5D64CCDF505FF2F127115 +4857C69B689A11760979F6D2BB032CF0DCCBB33D2B6F6D6BB29A1D0E371AA5869A408225C0AFF523 +CEFB08A7D3D17DF9064010363196CC569C436E720B1B4573CDAE1CD93A10FD2D4ACB14E47046B5B7 +66B75B40BA97027FEC4218980B002FAB60A9F1F5A37861B9A63F696732F8127B2C6F036BF32311B8 +FF08A489E88218092D57A99C8981EF8CBBD09DF49CC836B9C1968692D1FB551F47619F020289F1A3 +D3898A96DC1C7D39A21B49C2D0DD9508CB8A8BD6A5EB40FF86F555FA32017B67AEC07BECD659E8C4 +8DD1D43E8D1EE08A117479015F78BF20D3318224572D9C90884856E4307A8AFFC83EDD317B008484 +3BBE8EB2A4E2D70077A639FE3637C3DCF87C67F1BE62E63CC67BCEF8EBD07E030524A53DD440F2A1 +3A019B7AA89E155AAD1497974258A01DA45DE1D94BB9F925290FE9BDDA29EA3F2BF1E64DF7EBCFC4 +23AB2C7310D9D87A5CA673EE95189135E1B134B431B231428FF2BF64C8F155CBDDAD17BCB524CF7E +ABD66B75705BFFB1DDB27B72D681D7AA19FAD0FF23EEF028B606DD20D58588AACB299A3CF38372A8 +E7494A65227C1E4A231AC7843962B8A4A7BDD90F3F8B83277E696F3514139BB8DD207641B62286F0 +0E32C7FAD7029DC0E092564C7CE1BC5240FCBFB00A06F075D9536F0326CFFBA958BA7A1A11AA047B +B14E7DE16761BB0FEF19ABE85154359807C339961B9695CDED59E489CA4D9BB75D86D9EDDF0502BC +0B4EC36B1D71FCD4E03881ECEC703E5DA23B3F5DB98CB8DAED81D5BA20B844A92A4B92FE5B082952 +6837237C7F994786878404BE744D0953C676E52CB05FCE193E8827F977B31929E36E320E770A6F1E +972B84AFA21E01719759DF0132C5CF9692BAA487E86E8453C09FF97642600D1024ED5D6C7D5B387D +CB5E6147D20B4A14D7D485B5747D765C5A1CA40B9416DC4EF5DC08F43F0706B27B1C90E901556E1C +EFF304EA8DF8D727E4B7E7CEAD14E4FC7E76002DBC37B92BD0E58AF29DA7DA91E6F99DADF1A9CBDD +04648E2F5B811F2AF0729646B17D32D7EF25AD47E267EE53A61A7CD744C9ABFDB1EDB71C0705412B +15769188CA1601AF0395796FAC0E2139EF90FAA65D27AAEEEE319D2831A56CE82203523097D0574D +22742D17E519D95560B8445B5B29033BF3454B94F20C23EBE8B90DDF26D903F1D66CB39D3A64579D +39D136C537CCD9FF2D6ACE5F79DE696C7330C0C4EA179D7911B7B67858D86CEE0833AB1E105B1993 +2A9BD572C41E78FB4A2A2421514DC2066E2F56101A9027F4BBA5D48E5DA9218E81CE46B95B50380F +560C67A5D547A8D32610AECECBB6F5A09DF44994F6DAC64F7399C07E182033BC1B9A32B69C41FDFC +7E1DCDDF273F045F154D727AFEE3CDB816CF2ECDB6733C579276353DD59C9D2AFA455935C1FCD0AB +7D57F9DD79FBCC7A45E5E5A501FF17EE1C5FF0823C6FDE29C60F85F6786940D8E3540E891B5BF7F5 +D36C57AC3AD359BFAB12B215E7FC94B933386C250E6B236506FA3D151ABAD893A817772071C09144 +6E5FB23A17591A4CECAA46DD46E3C41B4578F21D8894A59B72FAF6F88EE6E514FBD2FE69447D2B59 +9A6AA8BC5C1FD5C25E50BFB5CE5DBF5AD5771BC42FCC3706B6E9F7E4FAAFF2E63ED1684C5A4C136D +609D03E31EBCF31E864AAA74D8DDBCA52F85CCF14AB739CC55617EFC964D6CC6988AA45245B19CE2 +B63CB865DF1F1DA4A200A4A043C5CB706CD303EB31C32866ED92077AB11BF136D158840EAC67E7A1 +1BC2BFDCD5894AF735D677E1AC98BF3F19F69AF75355F168632037F6EDEBF61BE5854057AD05972C +7DA8D37AE65D35738547A9D835C6351D8D732F8C0DC49D7605F00A6A6045559D3A0B0CC21DFDD75E +2FCF25522001741EBBEB5CC97DDBD9DDCE490FE3CB186C101940DD02CACB685ECCB8C1DEDCDD4156 +F5F9F6B3CA53EC6E7E8A2F8159451CD5479D91BFBF91C6B32A1E0053017369330EAD2DDE323BCAC5 +EEC91A595473F447355B1BDFB873D0B538BF54AFB8EAADE5309E1B74283218F59272E59619D66405 +E74B5A9D6323CB76AF00FB27FD984F740601248C7206F59EF7FF4E95AF95327D12C47D2D34CBFF33 +29F28236831D7F0FD9D633B940D8282E1F1D5D95616CD839C6B41A7195A22B7A320864E48943CE99 +C68E3591A97689986A6536C3A1C37DA9838FF71A8C1A03094537C95D3585DF5AD9292874D8D05720 +030133B02E2754DA24712B52D6D8871422C709532B09D147EC8ACD861FA5392A82A6C2981347E327 +1986017B8315822B5FCB19A1B31BF6909E0D6490EC823434BFCE031045D20FFC675A73EBD7A39A50 +44C3B40906186CCF6E8502CD7915F053BC8CF6BE9FDD9A0330AE943D5C9A11D60D43F5BBE8A045EF +CDB7D19C0053F4F082303442C35C432E0DA4408C5917D24A6658DB807BD754AF140CE84A34F79851 +9323060D60F4EAC0E82D3E17042BB6729C69A8B8774904C6214C2EB016C528DC1428DB41075AA6C5 +4E4D849901838C6B6DADF7B67CD0CBC6EE1B415B44654D89397274D4A6AD2BA69DD81567F5B802F2 +684DD3516ECA0F6D66046EDA0B2B38F420A238D67D98676925ECBE6711D64DAE5DBE8AC5473A2EE9 +97AE65F0715A2E5DB2C75F20D9939EF58692EDA3AEA81F25AEC888327CFA6CC13D496714E63A1A87 +11FC50250D6D23FC7A8017038281F659A685ED7F1BB1ADBF8833ABC6DBEC0D96D08334E58C67E0F9 +0046132F0D4FBCB9CDF52EE74561498B42701AB826A6DD77F46C14753748E1EC66F4BD3583FCB4F1 +DC91050CF18B0D51BC366549261282779FC9694A7B987973C0D59F65CFF3CDB88D23952E46F5EEC1 +BDA0DC354188C11B0FA191F5C11A45BB9093701B33B8E7AC1A8621793D5326E92CDD3A76FB9B67D6 +860567B9CEE7B9F9056C6E9971F642DC0BCC1D2882B5BDF1B1CDCAA3FC61775A74E70CDFC128DE0F +9606091BB8E53905108EE77A1D3C506550FCFCAE454B020379BE3A7D6C62D162697AF1383F7BC8F2 +34FD616324C1E56E3E440277862CAB2C02395D9937816117F71A6828344182E6B5AF2799E29A6611 +9C0543E135300E44746EF2EBA57C4EABB1A15D8AC6D037F4BA2BE1EB4D1A7496F9711FC67E56D4D0 +FDA4D810B5383A72ACA15872DE9F3A9890E33759CE4DA493691BCA47A92E42D950DF588C3574D6FC +72B5AF7DDE915E3F5925B3E97DEBE6A4208CE7337754045607679296C4EEEA3A3EF1915E85EB1A32 +F1BBADB2904B09806631E20BBF5E57AF11BC784C75BF049B4BC7E479F1D4AE7754CBED7B11ED80A5 +2DD0006FAE5CC23A7C234CF628B42F4104A490E7D59E8B1F2A1395D37641C478FF8CB9FB602B29FD +3E48D6087CAEE48B523887F27F69DB32BF80B760B030A6766F8F9B19DE70704DAF81D3DED2BF663D +89B5BD8AF253BB8FA59DF84A52FDED83D2737532B6D66AFB9EF0446ACD44BFAB797AB9FDB47F2E18 +8F0A55887853772EBFD029B5FA0AFBAF10A88D464BD6F634C5839B230D583E36671A51DDB1EBF471 +8ABB84D057729D514751B0EEF4F756B91DEDAD7D9AD529B3886008F1F32645F3A205F304B2A8D808 +D37A38E389B8587E8D3654DC374568FCEBBA160395BE1D132B1ACB434164525FBF250A5AA30F3520 +F0F2A75F9D1B7C6EAB0223C209A120D894D6ECA336B57B7F2AB0E2A94E6616D7539010C700311966 +7B4A9EB3A812FEF4D100AB4C036401D21DDF8AEB393074638D154418D3A7AE51CD1F0C2D5CF4B475 +3B582D5071F91E6D3AFBFB09EAABBEAB2A60A5D388D16232939B5483CF7D5C1C9D05DDC85B266F39 +6F61E179AB9FAB644040014293EB53F2B50E8751F9D92D1DAE8DC89594905A39357C0098265FBD24 +E407F9A22027E36D5608FAF15BD9E354228E3BA943EC30301ABB2CB105F3B6D57C001EBF8328A8CA +318A7B1B999AE8BF9E2FD530B669640116149B622EB3C0A8CCDE62449DE9D39E0F5E5E01CBBF4F5E +52B33A2BD60B52FA4A5CE98B7313FE4C3FA30FA07DE4516109C3EAEE97ABE430C505647DD8B3DBF2 +BB4B3A806B14A9E0F629863F15D12A1CA62E8E055FA6ACABDE1926D3231CAC271C30A3F7AAC6084D +D88628B943052B618E4E39148600AC1EDB57B0835C291F73D29C51FCA3C5EFB1DB18A5CA47433B45 +C57EB3CB28AEBC68E4171E6DE357793B0FD2A1F4E76180248017112674DAD7ACA6ECAAF209CA174A +5603CEA5CE528F20919443900636E2FC79C303EA7B467A3F001EA8CB849BCF28BF40A3364C08ABC9 +B5D935CFEDA827A8C3FE2F6ABA54017D2AD0393A05AE21F7AE1F58AE1E992B5C889219DA157FA7EE +92E291DE2F9DFC244F2CF0FDCEFCACC487F0EA830985B687556D5AF53B81814DE9CE0C6C430DCBCE +51EBC6091B4E0227732E07DF20D59E9EED4D8A83761CED84CCE244BFD6A39103A2B48A684AEC9210 +5C94495D54FD6690AF857E5A3915E6A15689F1816F75F1FC8D5769B240639B339EBE54BC6D84794D +50F4EBE892530351984C6F8BEBE097CD46F4FED7D46E32830A16F77C13F13D17003737482F02D1B6 +F04C57B4C2B1929AA315B4BE1C7C9CB55F8981508546B67E4EBF84B6026C355C5E4E07CD98C95F07 +56F6643FB1DD7D8C77C4AF4C4F23602DD3F61D1C46583071AC460E74084F7F7CF812BC53975CAAF8 +B3C1160B5D6864AF08A1733FA893CE4248C8F8B46AEFCCF317DC726BC1E5F2445E462E0591BEAAEA +49AD8E4E2D3CF07F900EC46D596E9CDB3A8710A0B93CE5DA9D35E612596A6374F35AED0EF55DC46A +8E14A91163B87417259DE926BBC3FC5423FF0AE2AA6D740BFFD26981A57C8C1D97FB04A90A567296 +B07437F94C8FFF4709213DD5D8862A777CF3F97723F43A4F913F4A30F7554ACDAE34713654E21731 +C594C3718C0547FCDAF7BB1620B2D6BB19E65596A585290CC43F50B34A2FE6EB1E495ACFFB16DFEE +8784B66FCB9359600412969E2BDA330C421C68345A797C49728450A6CF41C926AE8EBBE80BD97863 +593C3E8AB5415A8BA625957F242378948F5C7EA9C2641312E8E46955FE5C1A1824C71D3B0C9FD211 +7CC965DA7C21D257587F291AB7C594459A11B977A278C74CF5E6598B0C75ABBB2FC1B3D167D7E31D +B519F4A0BDA650A3FE7F1A281DB9B69B57A23656BD820B22D0E30B07361FE7C90933E24A32B6DE2D +F240635D543315226E32487F264AFE83EFEAC74117321A82A92F4FC7B259F26DBE74C966B4CB3F4E +3499E30B9B326F72D73919D9FA9024AAC0B95B6751AD4CE569CC3DDFC399428DF1298FB1733FFCE6 +240FB3BE3A2837E1A66E08B784CDD131B5A61423508000785CDC5610CE4DA1DD314131767C8857E3 +3D9741EF3FD7B8D0AF0371CFFA3DCF74FF2D3B421AC0339EBC05FB4B61A2F46D6BD1144747AD148B +7968D7DF4D0F742AB39B0A04A88D66F5CF9876970879879872BFDA0C56C253DE5C72016B4990CEBB +2455DCDEC7465EE7C7E1C557B570E9F3EF3E31A22DC8AB9B17F34F48E135BE7820ACE383DB7C5B05 +8A9DC855C6850E49AB7D7D00ED2D23C50AEE671B11752F0A682EFE179CECBFAB47B76676AC0E8FD1 +0A47353D3AC3477A59B0F3CAF22849DE97AAC8B79935F7C57F3052DE7E13BA0FE7CEC4685C86E841 +EA8C7D6734D0FEEFF538CC2AA1414EC9126637E169FBE4ECAFDFA29A08451B25954F0094710195E1 +69E0A6004419E432E9319BE2AEC8D93407B25B758336946C6E30032657DD857BE9C0A05F487423D2 +0C2772D4416D27FEB5CCC971DDEDFE9B8C2D7DF9DEC90D0868D5DD18850BE567A04D08310883D9B2 +D50B817D0690F8550E238C1139C0A117B48199B1B4D489C9E52C58E0CA75E6C386ADD447D8AE52D1 +D979FD52A50D82BBCB8867B5D49C19BDEC414929DB67A00AF7C6482A87E46BD11D8E4E52783E5845 +FB2CC7439F99FF6552C7288354B436B1C361AB8C2932F77B14A50A7407FC0BCC29662D710248CA46 +AC42A03FBBEF9C972783942F3721BD32BDA3273D1E353D9E013D4CFF630BFE4E7C2963FECFE350A2 +860421D17D6ACA888FA26403726A0E16BD10638D8511A2C8A36C99E9B72A422038E1149BF88A7CA1 +B2DB196333F3AD32D6FE28F18FE1ADA06FD25E4597936A924E71C188B9E8779BDBA928A182042C96 +F6A14A6FAB5C1A819DB8E9374E32F77393734A65606809E90D188F749180A3E6CA7AD8044E168677 +15FDFF350D70B14454B692715DC3AE2CAA561FB953B478E873EB19C41A8D83288451E4E910D5988F +33D83C5974DD0EE4DF2E6C849C500D0714038ECB1E9336D71D852889F2FBCA6769949699BE6BBF19 +A9318CCD7C845D0EC7FF3CFD871328CF7B45E6BBBBD16094E8ABE4D3789DEAD2C930AC8D810C911C +03AF2BDA4EBA62810F19B4506592005ACFF16EB58E33D6A71EA4DAD28A2D7B10FF89ACAB4BCC0F96 +E622EBA20347AE04C62A298257D1623977D185BB46B42CCDD733F72B37072B8DFAA5D7FF77E35618 +3F6D25EE1D951F7EBFBEA8FA17A859B8545BDB212A6BFE9C3078C32124C4E5D4DA09404E40498F76 +7B7164C13E12BF006EE8DE408070296EF0A08AF759206DB3C445BF49EAB18ECDE1FEDEFFAB653FDC +B13FA4649B35D12266FD42D0498331560E96F54D4238678F70233F56097E1944FC671D6BB1AB66CD +E0E9DC24349E44D67C36B3E3A00B07755749A597DF31C25F28D55779841BD3F6221BCDE389852DD4 +590E075C1298B36181D9F64BDCB54A556C05A9EF661EA1CC7C7549A3E1CCF79B87A6E71C3ACDECC9 +C4EFB05B643744407029258B8225DBF960DE529EEC262D1D3113F6CDDBCF4BDAB706BF085C0FF2EE +E8420CF755E46B053B774DF37C5683F8432EEC183C96176BFB24B3F478FACACBF1FCB73D83D4D857 +2D0F15783C8AE95D7CE959D794FDE1D973867D8F30A301598BDB7F2824B2629D64B88F0FF4498B6F +3728CF8916EA884C5930677E7481F90C85ED41DD28AA00E714D3A4F2CC136E417A4C591C7944C409 +70D2BCBE410A940937C3CAA118FA32317444B401968B8ECB2F0B3C8DAF6D4886C2015000A71FDAD4 +066B82312A4CD1E49A9ACFA05C3E7CA5A5CB3FA713CA0AD9E66A34730A36612C72D1F803D4CB1501 +9184FA2FDB3E5D067BC64B29299D5531565264B70FFFF86F5A5B03848E55D73337650208D21F35BB +D5C14748CBE17EB3A7E02BE53136DC69E8740C597CE28472CAEEB96EF2A5752CF17CFBB82F6C104F +2BBB50C216C49E8AB7E661207E1742B35392752051A1E409BEDCDA2654CB5155B770C8C5E4A2968A +A35CF1924D0C74A8D23AB811B7DCE60F1EBC1494A295C8D670E84B7B886A6064151065BD96F2D364 +7BA049A444CF16EB6432CAFCC70FF2E8B354F55A192C94BF08D156856A6512403F5140DF4C8D254E +DA59B2B3ADEE19A35E9A61202B711193A7E7BA8EF427152157DA5A51084EA46510864E1CD7B4FD11 +16E74D7BA769ABCFAC556BBA7CC528C18003A2AE059CC97C18263500667F6A9A8C421F2ABDD73EAD +82D9D08940DEE1688D4AA200ED80C3AFEF6A4979154D99478D4F4EB3738333E8581A755190D87BE3 +09A319ED170A107512F056E4C619D4BB1615BA6C0680117610A26462F267B76F9DBC9A7A3AC08C9A +512A44697082A2718B62FD1D565010AC96E1006F958070AB7567A33E1FF7BD450681DF6BD4EBD265 +CF12726A7EFDEFBB9BA1E596BC5C2940A4FC9DE60116C9082138F1C672977F4AA6B7986ADABBB2B0 +651A7B0C06C4BD405C58B8C81BE075997E118E10FC008AD1F9ACF9F6AAC6C9E3F6DC7FCB838720E8 +D0D9BB99F203EEA2566F18232711E832810F10DD4E2DE44A0A81603EB1162E1BDB60AA1E2D771EC2 +E6E2C3B39508E5CA03A1E2A7F2860BC6F0B57240880DF54C325F4729EEFA1D2753D57F055CDFCA5C +E9C6630980C7121FC21E2F7223E6111C18FFDA0A0A7643A213DE9525AE138814C4183BF03A26A36F +EE9199821464C845A746945413629DC53F5A2B0D8CE9282B580ED662F2F07398D6A8B7093CFCC6F5 +E0F5A7926D2862AD1CCACB84D85902038551EE3EAED02AC7754E3B65818C530A0252C049A68862DC +A459DDD937F7BA64DB16AC86885B68AF46422D7C4923070E20CBAAC9F14E43979C5D5AC4C9321016 +7CCC52E7DA272146D68D8F61DB4D60063E74F6673B41ACB601DEEB1DF73517A8C5388F00E8A9D236 +9C85DBFE4C1E9864AB52E09F465EE98C194151E66CB98E6981EFFCADBC48532E9795290CF745FDA9 +CB7FD40BB77C148A8B90C0CA50176103E2ECCAA57030F7C0882F1E891F9EEBA077AA4992FAE38C75 +5470A4C35454EBAB45271DD76E4DBB2D9147817F7D3FB800EA136D3F27C84B4D45ACEAD13D3C91EE +BD6836AC04F95E578D1B7B8CE5B718E42FD1BBE91EF9A7873F0C6DC59AD231D08CEB4AE312F83F1A +9310155D2C4F216A6FC72385C899B5390EBADE7CF7BEB028F73DD76EDEEF639E9EDE034ACB25BA57 +8C7BEC3581FEE0B221F7340570489596FC60EC805405E0D2ACF0687A62A75358B3878972D4C039D9 +07D868DD00C5F7F3342384C09B01F1926F7D37C2B862FC516361253CBBDAB354898B31B1FE58F773 +61517F2C2E106708AB659D95CE3E9E7777B7327DE01AE2D1E0C84B9EE3887C094C149700CB095D5D +A5FEAF1AA9027AF257576A7370D79FF4DB98511AA27C5C083FA0CA60A3A5E66B8DA9A22FE5DD3DDF +C529BEA71E83881D8B010D140AD686DBEC9AF4B331086F6556B695CAB14BF24B5FE64382E6F9BC21 +5E9EC40C0914B1E27BC25F43E6F555B551726A8D9CD590D5AD3732777EF3B00CBAA89F8E8E0E0347 +7D45B00181A7B196FD4919F110C0F2C0F1B41231051AB5BC8F8025ED00C10F2C2F51E97B622A4E84 +E6AADA59F896F83EFADE779B37FACC56BDCA1892A3BD965B65D51B55AC1D73ABCD16D0EADE55C0BD +3C1BE9FDB323E9FBC9835C63F757051E29E773D3491706DEEBAA0F1182F0E02D0CB701661B30770D +94E240E1358195781006E18CBFC2D83F229A89C3066E35CAE1384A0266D5A37824A089E855F11209 +9F723AF03BC1C8B1C0BCFFDEBE3EF73A40BF83F5E038B63267DE5413B88D30155E62EDCFA35C0047 +0178E5558CDA2B30C4EE2A9854C93E0E484D4363E3614E5BE829FAEAE51935386D20DBFC00B42952 +7F56FB045EC4D97B3D649415045337AF44BCF4AD9B9F9BF3EA72151DB616FF8F6B13EF66516D9640 +67460FF123C7EA525A97F1D04BDE9D3D495602620659F6E5DCF1AFC5360D1C371BDF9984C4A7B462 +180A3CAA7098E0FB0BDCE694806BA466883BD28D77DB4CFB6635BB7DB45B4D83AAD4260A4CA0D411 +0E251AE7476A95327BD6AC1AC88F85CCB705FBD09993B9E2990D51C37F1110F78B887C54E4EFDA80 +4ADAE5D81477913B6938FE1B39913C6582021A1ACA834500D9D75C9942CE2375D0A2A73805751EC0 +970D6FA62D4354337A43D85DEA6C6F3334F40221FC473DD79344D028FAC689645963B371A55CDA92 +F6BC989F4F1880AC11D8A056DF7A0EE60471800126526488B95B785698D2AC488CC027D31190ECE2 +54F80757DC9B4FF18171409C457F5FC58DD1D43E8D1EE08A6AA2F374B7C245B7A21287DC333BCB1E +EB498A2BD723EE6BB30B76E574773F70A61F1E163A25941531C049ADEDDB59AE75B7A6725D997752 +10ED356DD93D0F0AD7EE6495F07869C570125125BC84946F6AA1152CA18FCAD3B68004A7F7AFC6E0 +9EE6E1B365A4DA15DA95AB980A566DEC7394F385FE041B3B7F46D62424F4726B55DCB4BD3625CA68 +1221CE01DAE78E24F8045EF21F85C13D27D1F0F1B589F936F62F699B93EF05930C6F5730B0AFDB7A +7A829F2ECBF0DD196ED66C7E4E4E7E05B565BB98385F017D01B1305856DB747C3634BF05DAB08836 +029674ED3A4053CC3DC696F758F86970F94034B4F0DFEAA4DBDE449800DB040B55E7FC83E6B5584F +A00F7C70ED18524037CCB48A22A5A69506A825DED81D98FE93177DEEFD42B485013D4C4184CD690D +714530B56E355FB01BC92DD90A7AE8095748A872F79121A28E965E6149F37C9D5F4DF64574F605E1 +B929174AE0CF533F77EBA0E10862BBAC46BEBF583B951BD0BFC5128B08CD998DE897033E7CA8B198 +625D865701394543845CDB7A45BF82DD8B6319658E28B34FD319E38E650869742BD2672623ED9810 +8DF14CE5C911AE0AF2035B4F9CC0D1D84176CF3AEBC4834E8BBF07B7720521C4E6C081A962FE29E0 +700C4F4ECFE92C39BEDD64C3DDF52959A4102CC242C35F56139643F22613D675F970CFDF8C6A57BE +9D168C9CDF93915F66A5CB37DDB9B8B729F8131391F9D6EADC8BDD5C13D22A0EF967622F3F7C08DC +C422E3D41B3BDA6B4520C98FD08262303E042DF37B9911C1447F3DC8A854973323E1C380A02DACDF +8A53A0D1EDE9BF34A50E8B76C5AD5B15F8E25B2783BCF4B1247309E9B70CC951CF3D98C1A25E9CB7 +11235352F3BA91FABA828F2D7D91F0FFC50852860C531C20A5FAAFBCE1197CA367F0F84DEB86A8FF +A9FF4C252EB2633AA2BDAB30F2094721F482CF926DA3299452177B79251B311AA60D4CC82F5A9F50 +E942703877AF1C10CD92DCFD16CF19BC7314FDA5A85284BDE964DE2BEE782F4D52D17FD2084E0A95 +59EBD5AADCC74A6DE64C1F799385F5EC2E2F5F869F78F7289A26B03A9FD906934C3A6BA4A7B36E7C +3B77A7581BE9CD62B49C34572A870053CBA85DCDB9FDDE407B29CB3D02AD1C752B54DBB995DF0F8F +CB117CF8500B97849275A4625EF9937AFD7C8751B9B021E778F7DE9A9B191BFC470823FB8EA919BA +DB8B68755DD83C6905B118FA18FAAE216E2494FDEE9C1125C3941092C592DEC7A5B0C763E7E0D3CF +DA02AF9FFCD234A01D75C661603820C37E9A681356A6DB424F5F991FACCFF78EAE3518C0747C35E0 +8EDEA2E108CBBFFA0B2D3BFD612B5743AC95CC4A0580A6739BE4EDE6CB1E8B2F4CB5C6FA4572329A +06080E0085748067462F3EAEBCAD734DDA18BF206EAEFE9F882807694B80A63AF2F40625E98DF55F +BE08AEEEC2C1BFBC16F1BB4D391B17122EFB5FB539005A613EF5C9F154BD50F05862F813F2083CEA +149FEDC651191259BA4FAA658A42AF151B03A7B553AA79726A44AF9175A724E0D65CE84F41F3B7B0 +E0B684656EA56B4E7E654946AEFABDABCC4F3876B3C3A67373F4133FA8498DCFEBDC27476FBB28C4 +003FBFB063267FEAB2B2BB8DC647345803B2723DBA8905AB0741137C43768B434CE118954AE55DD6 +61AAA1BB60B87ADE97D4197211A0C70CDD7855783B7C570FD742AE5A5940A3D90F9EFF687C4F1E4A +D3C4F5C3B9FF20B61C0076B9FF060EB99860F31C41B5AEC0D2C4DE626C6059678DFA35BAC5F2F171 +A3BD00065AB2007EABA499E4E7F30EB994F8BA757FF9BB0C9A2E3E384BC1DD4B62036E2D70819AD0 +A535816761800CFEA50681AFBF0057E69A0CDBB4BAAFB8B15F7727BE1169BDD4FAF692F9CEC12893 +E4E4425DE4CB20F330951EB5A7FBB0FC10DE78A7F9B0EF6FA629CA13E8C2F616A1BD42B1E26E2A0B +533DEA701AB7BA93526E2439755FB9BD5CB076C3309E73807A5935AF6CDBBDABD5DD7B036324130B +8BC40163FA0999364553CFBE351509F8A5167F205E228ECD66EC9375D0E0713563AE75B6C23423AE +63EB67167F2F4512BEFFE96B90913E6920F1F8B7139F1CAC9E38482B6CD26576776A01103FDEB164 +A176023370D08906E2EF27E1E8B6F6C27EC67A86EA36A6C82B681385F3A60AD53A8512E0880D7ACB +5567F2654731CCC1796C47E18DD6CCE30F65E27DDC5A3E56BFA0460DFC3F9FF1E98B7BDA9DDCC615 +718D7C6CD8DC1270E70FDD4973B287396E2B85ADFCC83C71DBEBB97346E858CFDA78713C0EDEFEF6 +B84984D719C4729C0A3F2A7422DFFBB2AA5FE61891D3D16CDC1BA4A84E7A74B0076FEBE0C2C74F4B +B9314E5246D7E67DE23466D47C8AA93AC4859B347D8CE31FCFB4D41137B51C41BF19D626A59D0999 +FF2A4FA5FE6FA60E029494EF99C8B00700E6E752F4F9ED52F2AF5845563ED8AA5D4E19F82DC0F87E +255ADA53AC62E3D7BC788EAA135431DFF49F2D3ACB61798353B27409D149FD635690F8AD324804DE +A99D12B02F15D9C6DAA01BE2C1512BB8DBE86EB31D7034866C10558C008D69DAD8830745F2BEFC2F +FCD957D0FEC30BFEC54F3C440F3A99BFDD7C6D0D657402A064F2656694E5F5A5524CF4A7A2AD4625 +5DE9D2E9916DB9DC2C39986A221C31F89A1884ADBF7DD62D4EBD47957E7A359F2ACFD38E073E8502 +5F907941ED233EE3582AA955CEF67A8ECE6D8B301EF37B7D40ED84FA9DD604C74C8E870F9C26A2D4 +DEC8F03563D29E1DFB974CA191D4696D877A468082951B02A88884B9B760961D9C37154F32D54512 +4F0E4357B68547CAE9CDB571089752D7881613E7FD8DAA8CFB98CA9E930B48B78AE13523E43A3568 +7B42DD2F0A99034ECA1DD782DA692EFF6AC99D6734DF1AED3616B198E6C242EA7A9954B7337ABA3D +13EBF06B95E16F19047AB0EDBAB6A8928D81003E593C1F134B0E2B8C250EA251B59CD04905F57016 +1662514225C393C42BCC172DD23D8871908522CFA5CE113EC05F39E4583EBDEB5DA425E4627A4A2B +D5C511F9C9C155BC81D0EFAFB0D0F2E96BD49A5C942933336EDF9AE0CDCBB159761DFC50F6180FB5 +024D2E5C2A20233AFF282FD3B1AAE9B65D2989BB8176AA8C7A1F58E43A9AF3A6D0168CADB6930706 +C4F974282D4A23F71B0A41C75086DC1C45CB98ED68ED0E4FC62807EDEF13C6C85741B11FA957D791 +D92B750F3B7BDFCA7E148149E55EDED66700483C4D5BFC3973580F7199FD99CE6B358B508FFF5DF1 +78A5E495977D851B0B06DC7F6B38388D5C94BC8934584D8EE2F4E0CCD3332A737BC066F042B14931 +57BE93622E346FC6B293B8DA0D3EED02508AD2183454FD4D5D21235268834B257EA8B06117F67589 +3E0505E64709FDE03F2D5C82B163C29629EEBF5D408547AC363758D8D134AD7B9A55AD9C7D90B67E +6DF3AAE6867759D2A75993265118BF6C5A261C6D80EF039A0163BCF8E6B40E643D1BF87BD2592BFD +6A41EFDF9CFC2E6DDCA83FEC28B5EEEA2E363C20AFB4A13B5EEB2CA3BAEB2CA6F4824AF1A59E4CBD +884CA4C62852A095E4B13CD018669AF3898DFC4840C874B97408C811E431163887E5BB8CEAC12FA4 +A1EC50E8305137437441AE0FDF8FA4DFFFC77C05FCCC41696E40889A02DC4B1267D5B892647D6AFB +BA549F737543652D3C3A051E36BDB692DD7EA0D8964EEC27BCAE8EF5FA853C54D67A8ABEF7B01FB5 +AA473DF48CFBD3D697C9B55C852773A35B9A7C97908DB701AB66DCFB274A824B60E275E1CB341147 +36B26E50EFB1DF01F01688E09E096533E95B3AF5D09D7823DED38487C58B4F10D6AC76EB48731CED +78AB098C452AC83CCEDFE4E8E4AEB4A93A715306A096F262BFDE5036F38A3B733B1A0134904A3EE0 +8A9F7C2723FB5D0535C5B57CB80C29E292A49AF936DAC66CDE5C01640490109E679FBDC13F892438 +D70CAFB12909FD2ABFEAB23ABF6D129F5628B36FA00548ACCC39C8312030DBB87364DA44FACF3818 +D4C8ACFE3302B1487D5CFED16E17B05CE9889219C13C9DEA28C9BAE5D788578C309CB7781244E30B +7DFFFAF5A9F594B8781F849EB20B1F3A346C2D697CFFEA6AB4134DD46C86BD0F89AB3EE9DBB2F87E +988D906C21A43E5ADE58BFE82D4D4009A39EA3D1E779FC374FF21B86BE48AA4A33186DFA0F97BBB3 +218CE963643EF2A35788D613DFF934139B3EEA36377E67A713D20BD3DF04720AB49834E3FCD78908 +1FB726CF642A5B87D5D23609661F7D694EA0256F3EA0DBAB7C2CF79CF89CA0FC9D25281EE0FC16B0 +D089DC5B156462343A375F1EA2682A53F70A1F239D58080F4F884DBFFA68CC6D63365A8CC308DC5C +BC2387BF3D3107C95FF4DDC3A902B31C3F414355748B10518EBE00C92682CFA413FD071A16B8D129 +4021B0ECC5025E33F6116C89C7B983C6BFC31C5C8D7FB5E5E81D3AC500123CC05B3C8DE01357E192 +0DCFD172EB4B488CEB9E1ED5FA1D235C96FAD22B319239FDBA08ECA2C5C1192B4D7A797ECE135228 +6BBF1E59AB3B54B8886E67A82AD971DFD1EB21CC5E3512CA922F9B870A48E6DC94F94181E422D274 +2D3A14FCB3939FC8C1D62CAF79033D6EF4DCC93751BDABE588BF5D97B52AFC5084C5BC17246FF977 +7AA4D738BB9B15E534ABFD68848B879A9840EEF4774734F0BACED5E7B6177DFD430E0497E36D1077 +7654F351348BCEAAA18C3B362B2791A006782C25C9D544CF1594EA623BB4C782C6AABCA95F9CBB1A +8C86318834E1430376406D2B6CD5AB09644361B83AFF66C96CF549C2D309F7439254C6C3A5B210E8 +23F83647FF420BE66901C317349C1B305014EE7E9F90DDA917E3F853F1A8AF3DF1528A81C50B76A0 +F02E933229C2743BFA639003025697612BFD8575DCEA0BA5FFF805EEB4D9FBFA8D2014BC239E9D5C +4C87E36D1C83E010B92F43C06733976BC84AAF1C05C0A0CF45CA7746ED7E1DF5A12F2401C0FFBEAB +EFA199A7299E4BE5089C2CD83E7838F163F6284FC299B213513F803E93ABD8D759595DBF513D68BF +96031B9FB95A945B7C9153B0B315436C850FA5F1415AA2C9565F6FA39E9F5C5FB265CEEEA8C98E4D +00A72CE7F9F6677DCA7E58C1A8C111A9C6C44781867AA5FC71F36486AEE73FB81C03BF4EC728E43D +75564244AACA3D66B6D36DFD38332AD05F150D4972FD475FD087E13C9312D5A17A83411B45740153 +81CB568CF85BD66428FF9EA2C07E7BF8D0AF4469AA367DAC0230650036240634AB81766E832EBA8A +2D8BFA402DA804D264757E74B3465EE21A1EA1C92929444DAB2EC83050AD169F257B77D3F4B9BA61 +B11361F5DA6DD2DD933E101B64F9DC82945A2D421807F09F3E587D4B13BE0FDD6D7133CE890C3AC7 +1D0880418880362E27635986795E2E8426A0A7D7E8E5C41317209D957B53B6CB9E4EE7C3EEAF3315 +E006B7FD90E7A58FAE5289AE513D7751201459BE029563B58D967AD24E90DE5E96357D37E86CAEB1 +6059CD8593F92617AF636C7D32E2B074A40B6A1C40828313C8DF1BCBB002DF276D424519EEE2F234 +FF9B9B27126996834BEFB6E05A7BFE958B4AFC810E8B77F0EDDCD43E81549154F81E282276A7133C +23650ACB159EFEA53ABABAE1C1CCEF5642898A5605A285205DB40DF6C768029D8CAF85C520AA66CE +5BF1E0A0520CC94917FCC3118953403A1B16312096DBD56883F03E78A14315A5F97345E13C363C06 +3A01D49492349D60A9114A77BB9FD48FC0C3AC76D190204DC69C3ED4A265B8148F2C5F2E147A504E +4F373637C065FB894446031F78C4BDEA68088681E0C5099CAB1D13833FA87AB96E511013A9B3D806 +E71EF6E0A1442C91FC2A1795A13145ACBBF5D18880695EF11832ACD328BDE6E0A7308B12759D12C8 +6B558CDA038590787704BC1BF49EE8C788C41594332624D56082ED8627EC110233CA328D2A0BEDAE +3511719EDAA726F338D324D1577593948A8B9F0300F27FD4420638C6972EEE2D6248B87643275DB7 +69F72E8F86803184035F6A539A7CDF43A79886ECF110ECD7053FC04EB5E51B3C7625B3BF95C0F5EC +044FB7226281BC723988AC2498ACC0489DF0BFD1DA82D04FE3ACF6B63EB269BA9489F8D5D07DA9A3 +AD04BA924B99B9C1EA64AFE7BB2886513EA6730462D4FB5DD82659C7DB7687F4CD8E006581A15EA7 +715E274C9B89F66F1ACE9C2AE7698FCF7479A04F2208DEBA6DE801A6D184A8A9AF6BC1B0E37CEE3B +323DC4EF93EAA8219F946DB9F4D9C133C6CE0FCC6884F9C2F3A816C4ABEF44DD6256E7BC4574600E +1D825080660BF6858B415648258399839118F3C11410C1C29B3C208A3B54AD5D7484708DCFBCA04C +849F2AAB79E4D96328D990C63DE05DC8E804DDAE255F94FC3D56270BAFF6F86190796F91BFA018C4 +FE4FAEC3F1ABE8ADE43D0DA18E710BC1F419F77DEBAACD3BE011BB93E111B18CCEDA8EB0352934F9 +690F3E73D71655191F150BC3788677D1FE46070FDB354BCCAC8C179009553A7D67C87518131A4D8B +4FFF85FB9485C9F30F4CD31EECBC4A44CC267F6C57AA05A11C6FCC09B5CCC83F189F6A32F8EA56F2 +2D20DA4D4008F08EFB1487675CCAE22BC9494441682F4E46839F0F4D2D16AD58AD0886C60C925DB9 +C7D9AA1A7FF41C94B6289E1B72382789006F40B99B78B05ED1FB1F715CE4C0A1078AAC02EFBE6306 +F53F5F7E73DAD249995DAEBF17E5F55082CC6885A54F93F1A935E0389FE54E8B1B6C5ED19D483620 +A697873D5F18CE9A48E3C2C1C871FD4739A78782D8112602DDF8D4FC497C459067A6B118AA998740 +6C8DE97C2F09CB9D388D341EAEC0A5BB4BBBED92BE59B273C77F3D6965418669BFCE0C43D5C86275 +D8E658BE1893DA8E698DC858CA459711969B2CBF4CE294071EA572496575CC35CAF57ED49C2FF1AC +CC21E19D189B7C2A1ABB1AEAD7185675413C224CC4C0E1AF4EB76BA9F44148A95D8609838B967784 +2391DECB30BB0FEB92CC890F224FD2E9E9D34466D6091443CB69E845D4419F9A04664706FC8D2D15 +9002422367F39CA1B1CB1A6C32A65F46230CA2376C3E5125CDFD367514E087E59873EEE569B7F376 +227DB126060F063662F118C7BD01946BD04172147B601BABA952A01E0AD31AB1147D48FC2C3F52BB +75D9618E6F03F1F1EA393AF0E8474025F451616C4ED236ED831E14F40BEF5B86806B73ED64AFE7B1 +2A3C1F5036BB9AE79862EDAF13BDFFF06C94939AFDA1A749364BEE73449520111CF56181527F9568 +F3189652FA7FFDF4BA1086DC5992C6E0282B6F88D7CB73F485F8A27A77453C151B0BA40E294653A9 +73298EBECE8132B440ED4B4437283356B79CADA8198512A45044A7AF04CD02CD4DF7F47D5D1E7FE9 +52C346B01D03D1F69904D1AB5E8C433B0615E88CF4D01B3C96361F82B5CF7CB4D92FF3971E44F0C3 +317D3C5B0BC8DF4BCD4DC63474D0E0B5BEFF3177E2722D4AD4AC4B4AB6269EE948BBAB6019ECF2F5 +846A3D215F6C0D999D489215D4328875DE21F2CA243CF184280B229ECA4B8A9C5290973503AE5883 +03C67DDECC577F12B41F0D4DA266772867F9D93E1863BF76C6AEA5DE3FC6567EECE93D96F717E39E +DF536ABDFEC14DF6748DC90A2CDF6066246DF69D2745D2944123AB3A6ADCFBE7C74EA8E8D712AE86 +F76B3059178E78FB2FBE8D1F25831C70F58FB6B5922B371A27501E7463E01C844A2226CBA263B570 +4E5C4D3E50FE31435437E1ED39E6E3BF47B4E2C4588274F044B3294E7B2BFE302E76EFA3CB74108D +A6CFBBBF383F5C456128ADB5E667E1F7ECB4C3E00AB8937769E5A2830520E9FC0A1DA1662F881ECC +DD7967647B5841D8FDE1DF7C9F5475523F236005EEC0DF307BFCEF379355C30A83EDC96D6086E224 +388DC7B5E951B819347AC5A1F9FBE7EF1907726EE7E972AA1DDECBF7F72658C20FDE99FEE686D7E5 +01A7759DEF55169938F34978A6BD4DB49E494883F67E868A9AB177FA8E6F81157A95A03B4D9DB572 +EC1CFF33B450BC13E00830BBA20AFF928CCE04B4F79F3795DB54A4A8B5A2F3CB323194990050CBBB +C7CD32103E0911160FF4DFF135A77DD0CC15867B994CC88E1EC10E3A097D329DBB90FBB62981FF61 +C2521F9AB4B9393C6764E5B4361D0FBB1938456CE437142F0AAD9341588BD15EA0F6EDDACF12A62E +C025F3294AB1AC45719C5EFEE94067390D579AED4D1D36041D358CF1A24446176DCC808CE2D6CE02 +7A2C2F6E517A5ADAC722EE94A1710BF61254DB4693B30225C12B9C4F856E1D24075327017D6B288B +B52EDE713B3710778A565EFF6C89656BE3C5F590F6ECA600390C1BFDE9B0EEBC2E4FBB9E0E2F405B +39738F7969F64E8228494B298C3FFF4C7DED00B0EEC336B7EFEAA892C4A80CE9ABAAB4318FB34348 +93AAE6A90FF00B892D1DEE254DACDB268A6308E91FB628A98989956958C9634896B878CF93F4E0AB +A0170C1B7BE2A0C4A0D514D7BBBC4CA114349D4D4985E96DB7E2ABB752C7828A9E36D9B0B4551DCD +D878C06C3C68D2C214EC8121F6675D8D03545606B582B09B76B6D8608DFCED5C4A721F7008FC2014 +1D877353E8BA5DEB1CB61F7C956D4A9F8CFFC8C4FF81B2660AE4BE45F7A63141BAEDEA05C43CAC2F +A04163ACECAEF90F61E0473E5CBF1F1994076B6A72CB5C33B17DD57E2632F7C6DEF7837F8A939055 +CF357795865D86C7745DE54C6079C791850B20C0C7349472FB6018521DD5924ED1505A1B8C8F9CF4 +C892CB40795C4ADBA3CC11C8A52A1DCFA8FD334D7F3C344CB4057E80A4B66AF6A97799F8DE817CDD +0202870BBF42E76C9BABD2D9B66D10F1A68388AF1A511887FFC50EC7D07581D4C4FC3F6C4F7EFCA9 +4799D74132B5EA25DF0C9557902C7EF1E04E612D9E40DBA459513E584C3A3EE5614ACEBA165E07A7 +CA3394969C2CF1FFF28B1C7DA85451270DF0FB71DE22A03FC2F17531FD59B12B55DEA7F5F56B0DF9 +34C96E26124342571BD04FAE6A12C6EB0E21F06275605FABE91C6EDCF55B298BC4CC52891DD90360 +AA5FDC150004BAE65225FFC42D13026F9C6FE343D7CE2F52229B4846F6E23BB2BBDB6DCF60F07A74 +8F19F74A1168DC5C67BEF840A3C68CA7A4D8CE7F94610F4CEE989459D0CDF1B194C63A2B82479746 +03A89150C4C6AC67AE3A1341F9516887C6BA254F81C5B552C527765A52ED5C4FC45D575F606E465F +7C2EDD2F5927319BE737D48099C333BBA84486F5F8CC0B32052DB6E57DC55A68019788DEABF8D649 +A531C1880D07E425D55D4DD4F3966B2FBD2A0B55E5C429051DCF0E3B9CB1DE6A5B3DA05DDBBDD3CC +1C81877AED2BD272FC7A10707C07DAA54FD6CD37E15C247E3100A1A0C527727B73D45C8E02798C44 +1864A2D1D30FAF94121F0CADA24753221C85DD5B43E5F00FCAF73A1E531D9453307581C6FA28BB5C +54D93F149D3871D2E017E6E7FFF7B0BEC71B83AD12AF353CD13311D3A6F16C51938986C9B6A24EAA +06B8BE6DF27A5090B3B120D1D1E064C6C1745536C6B0AE5C899C3DCA91BC38B7E900D9614F291B9D +BAA85AFBBBB57D58C3E1FA8713CCA1BF4EED469773EB4B9605125C08A8F7E998E37BD893F3533232 +ECDF47D26E8B2A0437220FAA760DC8E90FCBEF59AF6C1C55FE1A28A4C98E2A67DD5A7E55BD4FE272 +15533A56561F0D80989BFE15B321976CDEF26FD6530EF7A368A7239CC55D7AE2B8F0E980DF63CFE6 +F562F3ADD0AB906B60682BA447CE4A86E6D5538E13C6847791D8A16F5BA29E5840847A7E33AFF57E +BBEC1A5B329A461FD0E858DE5163A2120BA12839C3A216C44F364452A2D6401EB549791012C4B65F +4FEECA2B73B2D88CA49B44493802B01A23321470A2593A8F8ADE3F88D87247851561372137E11D10 +11A733C671C71D33EC939B05060C73697EE577A8F2BAE08309585887E5F314BCC642BA2715B51E0B +4D093F6B11CD37BD9728EE90A0C92D15BD1105637052F89B417F6F36340588601C9C2BE9526D01B2 +E88EEAFA300E38B0EB5E2B54341533B31DA1193588974DC054FFBDF374960D28F0C8C1AB8505CF5C +64988DB86E17213EF0D9D6D52ADB1BCEBCD02B5E16F0866D21D7C0FE108D551E695CEAFBB83AADC7 +362727C47BF24C482EAD6F122F1F35923DE5D6A248BB36433D044F73C944E6CE4D9FBDF0417ED53C +8F56B6D389519E7A539D6BE9444A4747957FEABCFFDCA5FA54BFF46F637B3A3299988929CDE008E9 +E3C4CCA97054A822C4AAD01ECF9861AF6238E6643358B0EA141B0E161C6ABCB45F38740B344D4112 +9D4E898DAEA8F2065263D68D97966FA24BF88E61BD86CF2C7BFD1F058FCF04AEB88F3A30C9D446C6 +3611112CCF30F163C103D6C7C5AF946ADD33B50A58FCD8EC612268F7E119BDF387104F22E4C2CAE2 +DAC407F206186F12D93BA87711FB05E6E96F17DE333305196FA7C33DC06255828744B590FA0E67CC +E4B0375276FC7AB9B324978B15FC228DAC17F955C7B3D441C540192145FBD002EE20FBDF6F397F95 +336C0A056609E28430E123A432EE91594E20A8D9E5774FE8768C84CC040A706D8D3590BBC9AFE373 +4A5536162846B6B7BB7B248924F1DAFD768B9546BD2A2CFA1203D6A3FF45C8EBDE2355F01744484C +DA9FC337CF7D9D54106407F54FEEA81B77BFEBEE088F344CA2E537644B615D8D6B6A79D03F6CBD9B +573FBA87EB00410B5251A29007CFB60E711F642C19847F58333B48F66B6758E4ABE524A4671B0635 +C491341B3CDEE650CB9F774A5B6FB92AA70FEC7D9A057084214D81EA5A36A7F8EBE9922A70F2B102 +121736E0BFB178A08ADC0A58E2A9DB347FB9B0BE707C038CC19A5519C3FA9AAEF660653086191E59 +320D0E696218CE1E8EA7DB5FB3318EEC98C130F3F1C45D0D2401223421FF99BD6E1546873C6F12C6 +F45D2E1C3EE41634F5A415FC8338A18EF2299EFCECEE00F9025E79610168548BF2A52623A479EEAE +CC55BE5172B0CF07F9B04E2B2AC08A2BC839DFBA9680F2BB2E438519BF3B434C71B4AD9E64262C76 +9F6C1AD174FCFF3F41B5BD7652AB296C543F323F0A16E88C6D1FD3025C33794551DCAED303B57A87 +CD1E7FA46E16ED357CE0FEBAAD2678A4D84E4D6B1635F412465C0F3E7246407BDDF934F0E2E0F5D3 +EFE318D7D63A7B6BD0CD5556B6DA811D521408EF336CCE2D2B6777AA472A9EE0DB9FE0D6914059FE +25EFF5D7E3D2A6EB96B23669118B9EFFEE5B7FB8F1EEEA355998BB48430156F14766FD77605BBF80 +CDEF19879E8F8C07B6998AF145B0AED86FA94A952F2F49D2891D41AB0184EFA8616B139E640A3A9B +D808BDF79E283B0CC4686935D0D96630D590A5F4A7C71C05E110BF3CDB5150D8CAC418AB25419BCF +DD5EF0A2015305561CA26494E267BA89892AD21E0BFF44D48E330694A1CE12183B9A7E4E25D78EA3 +498EDF9A22DD7718ABD06DE2C28D7762F609A1E9A5C40A878FB8AB33A60383152A119B9FA077B109 +90AF19C261CE43AFC116593C30BC27EE4D8CBCA0C0298BB84327FDBDD93F073B1E06F71933C0323A +6E7FD2AABA783E9BC995C4AED621434B82B34424B768B4427EE65228E612581B0B8A7AAC3149788D +FDD106A6AB93E01771802AD93B63EC386B057690E5C34A409421E532C6614EC61A0197C13EAEE438 +35ED9BB38CF811E39CF8F154684E3D8B12E3B673152B82DD9B15A2A68A6163D2CCD72D3117F7C24C +F1575671832ECC4AC6B912882C0231050D60ACDC7D6F36C6BB4E32B6019B32D1DA08C7ACFC1DF451 +3A338FACC16D297C56C6138FB02FC7FB5BF7B9DE4C61B63C6B37B0F9CB42E6FF86693BAE1CEBB60C +9C15CB6222F547E0D0776BC5545A73A2ED3C7799F0CA3C2D5EE17849DFB15C6ECF7C2846AD10870A +00E0810F35A57770EEE9D49D05B54500DF164A02FFC324CCFDB5F828AF7307DDFBBE647E98909C73 +A3CC6BFB360042823C678EE6ACA0D658C12776F2A573656073E4F40505F5CB4A3D340B034B0FBC29 +C3B6B055D23F0BD47E44B441D430B8703883AF8EBA79081D528AF5646340A27291472B8E1F19C8BB +B4AD17C7EA1FCFCC7B52E6EBAE0BC8204AA52C08B3A63B2F07FBFF20092139143A24130191C41D2D +69077D71FC204C5FD41275DCEBEF7E5701BFF6C0A4217F6F60C2E37697C7F1C35D2451B040BA0D28 +0C9D23AAEF592BFFB436165C314C3CB75223D15694B6EF312CBCE8035A1A9172365FCCD119CD5DCC +569B84C6BBC5AC9CAB6942096034523671156FAE2012F6A24A001DCB2F35A8A031A2366CA98F1E52 +944B58FBF1852710CEE0116BD2C7D68DB956B15FB6AAA147155E9F179E67357F231F8252D728AF12 +49DAFCF6AB4EBC8637E1BA10D27555D2FAAC9EEE5E51C8BBC793ECC6011C4FEDDF7116E719147927 +0BC11D5EDD9215A4E8087A6A16A591BF7ADFDB69C4A03361C0DD078017DC5851BBF60E06A86C6C0B +E08087C99F4F9002ED5206534913353AE16C4F358BC1564A442CAE506A107D1FBC0B9ED99AFC6633 +209BEDBBB681CCE475645E92155285C00FD6985216CCE60064946F94778F7AA85ED87F5762C20FE9 +DC007954281BD6FCA8554D2A0CA5B76A3ED42EE5F44F3B276E574F64B20E1AD489753903ECD20B4B +EC88ABD1E1CF5D06AA1815AB771E350EB6D04078EC04616B977CDA8CE88C483DDEE9F28D58366D3C +224C41D19E550B5ECB9775F569C2D391F61C4667A9BD11C69A88473AA7884B823F762195CC403823 +690A32087893C29C63100A5935842F6B612C95EC9B5F07459608786310C8AE65DBBDCFED21B43191 +874E20D08F12E1384DCE1A990CA5F07DE36BD012DC9EC558FE7CC44494F48CA3BCBF1F1F11BB98BA +EBBF8691F8590F84AD849923E656860EFC0EC27496FE6D6C185791E3261027DEEE4C57032547F94E +7D593F7885526AAF054BB850C02E863D831ECFEE61A781B89867139889A362F95F48A837ADB955C9 +939F609D2EBBD56775151D2EA4B09D38FC1D824A952EE7E52849260DE61F07333076CF887A3AC2F4 +CD088C29E47715C5242E2366CB493B2FEB38C19FC159EC50EAE88409CF0A30C0A6823C45D0532C2A +72E45A17916F6CEEB475C4D4A19372AE271326697CCCAFBB43D92C25A052797186FE8314FB41FD94 +2A4B24548866BAC19A83BCD2E6979FB3C7B70B075DCDAB3EF6B6181A00A98AE73B6D968EE2DDEF07 +503A92E4FE1E0B67A90A2F351D600DF960101A15808AA99B3C680A8F50D5CBBC96D98A3A2AD5F14C +43857F4CB9DF9372FE74B5D6CA9CD801667399E778B56EF702A56F0F393137B20BF11BCFF9DFC0F0 +4E67649D07A2A5F4C42C9A929231D5BACFB6B53210E9FE311642D8BAA7358E6A7370B6CE921765B3 +68A354B42C8877E59227146409DA83E407657BEB475329F228CDFDD11BC73123234649AEF0E2E9DA +76C12AF91713828368FB778905A6B7150258695D4D9DF6111E1B28A9462002D7C476FE44A9B13F32 +9FF84930D213787932A1BA01EA608ABE7054FFDAAE2176EB960005E5407D7C1D39AEF8EAD8683A50 +D93398C584312EE4A12C07E9D55AE9981D7EF57D66499CA93EA653EDDA1BECB494662E54CD7ED8A3 +0B2A8522CAB12B2751F7E9B3B66CA0B5C8905E0F3A51F68C96E9C02F10FE515FD6133D3ED298D15E +8B1649F3D13341BDADDA7FCB838720E8D0D9DD92D0A241A0CD8E25D7B313DDAA2F25B543BB0E7965 +402ED0F24AD146E49919ABD9604ECFEA5A7A49E58664DCEFDC893F5D722CF24A44D26369F5D86569 +9632141348086A80AE528EDAFFF9D9986D5665DBCD375DF221D5EF1757D79361E5A5BCBE333B6C9C +1CF46794A7C7C776477023BB298C970F6DFF9AE6C7ADDA8CEBE73B07117BFED6CED2A36548081C79 +BDA9A70C8FC5FA16D0EFD3E9F869DD5DBDF6E70CDA4217935FEFE577B64DA1DDBADB9A092B9D1E3C +E1FD3B6E0117DDED155313A5DB7D8742C3409FC18B9743F09ADEC49BFF2FDBBEF9D5FA7110266287 +7C65E3A1421BA56E258A49D76C436C97AE2116F772CA5EEA5726BF17AD5D5CE37DC5F45235730E90 +A1A1E3087132C820EB9E0E311500F2CA193C72E2ACCB4D77ADFA34E268B014FFAB5DD1BE7187251A +A69B7BB3A517796BACEFB9ACE0114FAAEFB0D9BCA028A52F5037291232A04C2353B9663BFCBEC2D9 +30CB007954B8C6396003214262AA9CA9FA876B4191313FD1831D664863C4A19946AEB8F4E21C468B +9C94B8CAA407A74AF418DFB60D46BEE5029C884950D3BE8A58023DA864E9AB34005337D335B0B35A +91DDEB53A54C3233150E27225B0800C841E489E9EA6C12F5D95112BB723C3E88AAEDA3A942D06242 +2CD80B04DDF024ED79224E166086C5770EB10C389D0BF0327B2F753358D9BD552C04EA52BA154EEE +5A84C51FBBFC79055AD0F1243E489F82FD3D1A3D2CCB24A02DB3F306767564A2451DA405BAFFED98 +20241A82C6F4F6E1D2A36FD954540EF3C091247120F5E1B362D1EBCBF80951A3158B1547E7A0CCFE +40C03F992BAD00496C32BBE9B0A06EE9554EA3FAA247A7399A37D05329CADB006F679B58F2E5969B +13A5516A9F6949505A64B7B3FE5E748B9B9662C05E6C5CB79D64FD72C54F557ADE72880B3F9D8718 +58D5EA3FEF2BD3FCFD670794AEA6144D13782BA89648C98E7CF5160089DB9798E49BE2DEBC2669C1 +0C42AE1AD7C1FF4921EF465300977E057C0AEE61579460F56E51B6EAFC1F7F41D669ADE9610EE777 +055F927444F971121CD76A55A273CEBB481B89D6B78D0066ECE31BE3EEF65CE9FD22D22EFD6E3ED4 +536E6439782AD53550CEF1A903125F228A3E2F1AA3342B0551C59924149358F8E941969125F6E54C +8B2C068F57FE0DF91912CD71BE9492768191F4F6B70BA45C72409D4AC6E9619E8A34398E6A66E984 +55F1793D8A2C0DF30EABE2B19679318AE09014262DFBB9AED5D637653FBA1C10821908ED6E088910 +AA033E2E0798A630966D29F84845A9C937C27A268CAB8BA1AE9B32E12B52EE4B64BE5388AD32035C +7F98904F052B31D7C4819E27BCBC597E8503A5614E0DC2BBE5C51922980E3F492B61BEEB169C3EB6 +AB9F1DA82EA6BCFBCC16199923C8399B0495EE9A9E1749DAD9FB289FF3FCCF34F55DF7D94F91EF31 +B3B4C8074C567C4FAB337A337BCC2F459075244F665B079C159AAB83781E465F5259D41313183EC5 +F5F53BFB4797942F93EAD6CBF9255F9B4DD0748A3BC6BF36ACBE0127AF68EA6566B65430D0595FD2 +A7A2FF74055BF2E70BFFF2B79131F1871CEDEFB495FF914B88770654F9E5556AF535E1B9AF812004 +99288AA39D1388ACCB5B11B13596B550F2746B2A83B076F4F606F7580156E54FBF6976AA4CFC9581 +0A7FAC41A1635FDCFD6F7CD6BF738190D7F9FEAAB8C0B7CE38DD1459E2465C3B75625C8BAFE3B60A +F66ED183965AB9681A8174C44311D8EE36E468A8FFE2035B7C5AB6A372FE37FF627F4697C0D19F7C +8A1E356F829BBF2B8F4A95A49F85CA16ADDCA5C0817D6A4F7C4EAEE908DA13E0C89C9AEFF6D1D7B5 +BCD1BBF672B46C4960720CD1C74E70C78784CBFA8930576AD4067D406A0FCE9248DCE6D610D7EF0E +97F3F9CEA1D08DDA90DFF0C484DD32265FD13A2571513F361DE3061F2EA76886112AC7589B290220 +E34610277CF81535CD628CA688CA812275D7B9909757DD519F1FC89FE4D0AFE5FDB999323A470C6A +A0D9BA9CC92BAC24514E7CB3A3EAD2A70271EA8B02B2DDFEC6CE803F1B761D9BB7099FFCBF918D8E +B602946FB0DC14F0FD1289037B15A4A96A4605BBE53BCEA9112BF9746F3BDDC06CCCD808C62F2B41 +D4F508EEFB03AD22E2E154888DC63C6BEC6A21D1851A5C82397FA49BD163BA8A72527827E4B6F50F +585325219FAA9B3A2CE14A0134C19DAE50373A0F9E6DDC8205467242D11A3989D17730C8169964B9 +CC9549BE20A84FD9137DD9C9F7DEBAC3A41345DFEB0AEDCD7ED408A909000FE8D9CAC85D93052256 +7A2C9312769DC85F902D4A5F5766EA3C561549F1F2B4C5269276946809ECA26B6F51DB4CBDA9E668 +BE1023D8962C0DBCACD5BBC9E5F61C459B825036E1322737C0F196C0DF93DA76011DF2CA06F7B383 +8C672A802EA24A474ACF7A51F2DE867ECAF5674B1053CD5419F5FAC20584A3F7565D7CE584CD395B +1968B622FE3C68DAD2F0E33274DFDF03B5A8EA047B077DA1316DF487C91ECEA84E9B417EA25EC9CF +1F1CEDD7A1C2CB0D51A58BD8F9772FAED8D553141ADAB148AFDA200479CD04C0FFD1478EBC618303 +5437A5BC1AEE3218E9B27D21656EB9F31BD4E7D3186C89DF251207E8B67265585083111BC1AFD4D6 +A2773629147A29CBB4BBC3935B83392487858E0D18FFC96E57C83C4C6744C8E0DBF001DFD64B660C +CC8064907AA4BECC12376A1EB55EDF655CBBB4744C1A6166590B9572073A2AB577EF446B80567241 +389F990AE6C90B286EB48454166BBF264422FF2A387FA0B413F2295B6B188A64927DF702C232CCAE +59A2CCD1D109840A464BAC74A45B865A6667C901D86F771C4A36421308551F532497990BC15DA648 +6E322566C210C517DAADDBC24DACB39CA41611B9F961E4696D1FEC46E71C608DFDEEF19ECDD88724 +24E1BEF7176B0AFA4888BDB4C56C8690AFD03428F32740DFBC4BE22B7583DC47BBA42DF4E83C14AA +29F4E79397BBDCD2EC43338BA1F0D2CA9DA6E64A065FCB0073ACD0D86E46EEBF1454C9C172C6BAA5 +0F37FA19756B05405763387237794E9FF74E6749BF6CE5EE9145413950D342DBE5B0EFDE57163127 +2A8D060E935547E1FB0FB1FD60400FF856D027671CCAECFF7E215BE61E8A77E9BF5C72C2C1E4501A +A11B2F8574BD823574EC9598D579A9C5C525867F4BAAAA78DDA0E5BD7AC832DEA41328A507874A85 +90B7F133E743471D4FA27DFAC39C6F99E233C913183B2A039CBEB5CB3BF8825A92D83D266246D5B3 +CC7B11469E611E260C6ED16D17C9693B13B78E3F0DE2F0ABBE73FD6AE1ED25F57B4894254FFDB332 +0E65ADA53669B95CA28BB4BD166507E9D8F12727E46D2B9593186C090764FE8A95F1594291FD551C +A96E4CBE1D6FD42852B2B65E7F10C4F17707F930DD934E2A2ACBDFA40E04EABC1E54632D67AB7D5C +00DC103A3D11DCF78B6771D98ABD5CCA0B253283C67B0863C80D1A78FE6D5422568207509FDB1946 +92706C7A211B29955F6354092C9732DB2ABA8CDAF407FF25B40BF9D73317D5985E19E6C12B6AB5F5 +DD59328905F822E1EBEB87C4E386EF20CEB7EAF842700F09BDC50D7AE6442D1C93804D56FE194785 +968373A154E1F426BF76692CE3E4474360ACB9FEBD3191B8EE909E7C224EF90EEF36CD660AC9A642 +4DB5BB20B8835D365D35A442ECB2444F30466C3323001A087639B73848E3EE275406CFFB495ECDB4 +52F2A357F45D2D32BB22CAF9F9C2C44741A5341B29BB6C4322287A2A3891E1E853B976E17DD9306F +D98BDE8A0C97ABDCC7C708BD1BF49B524CD0DCECB40DDC557E9C90A1EDDEE57BBABC4C338F08A625 +E03C1C1A20DEA709051EB1A3264D7002D6DD2BB7B622AF93C7DF54F49EFA5FBA58F115A049A91875 +A2476C19F9133D0BE1B07C8102746163CEA0A98F9B62EA59C9CED7AD808DD52DDF8687933AE52A7D +9D7081150A812801D3FA78F90C4933E5DF09C991325324DEC6ED364ADF73DFC7B78953B51FBA2F81 +F264C2A94139289CEBF7ECD6D8FEE9E579BE231FE1ABBCC07B4BF1884CABDCEBFC56610A5E2B7510 +37FE804511FB5443F7B21290E38855F6DE5EE38960E02481DC57F83CFCB87C8FF1CB196CDC19B2B4 +6C7C72B64D7D45C678851ED7E2860DE7DB772BE7B33C84ADF7E6F9B2063D1263CDF55E6220753ACD +67C4701EBDC0799AF47AC58A0CCE796384A50DE6A814DD994B372630E64E5FDFF57393522F689DF7 +81C44599B21AB1C214D4ACF94F07CFEE79C628C85378B96D9007ACD4225E14DAD8C70984DC596DF1 +96BC814E7346217C94F172FA40B3DFC73C30E6E530DDE4A91F6A5166AE19EAA3D2CC3D9D6917BFD3 +1EFAC19C236463AF8D7FB7D9B3A4D6FB6D962C59B296E988561957A3C3D11F33ACF69F5768A2DA30 +462BA9AFE67D3C41A1E8368099361F50D4F6368963A48C75F1590804E7918A02EB9DA5F60F49828C +95AC60E9D86DEB7699C35037669C02D408269D6BF481EC745E4EC630F68FF5168BE5E0BDF875EEC1 +5EB9CAA9CBED1CE2A0EB4CE97C14E10114087DCBE5DE1DB6A070CBC6B68EE141B7D62ED320F7B74C +07D9762E0F5C8AF0779D38B7677ED65B5DD2F83ED06F041B5701228EFEAA0FFC031E44D6D68B274A +45FF9EB62FDA007CDC98B86831E9A668098364B421E1FE2A45D85824612A521777C6177316F0C6DB +D9847A2F61326E74A3114EAC3ABAEE456156DF125E4EDAD6BDB66E7DA6370C90ADC4AF4EC1958170 +C32D5AAF3B45BF749EE1DFAD8A5D0C03973B7C1589EAE4E67F6C87213C721E4A33466F3F54253545 +5014417CFFC9AF461CF695F5E1D7A37E4C7680A5624AA11EEA65C59BB38A96734E4A4A238C636638 +9EB8083D5AD753FA7F74EDF10054315EDD53DE61D48846F70AA9272626E23BA3DAB7CCEE819B0A88 +9CE775AA7F5205C95A19AAF475729D2C0BCAD9AB5459380A5F034C108A64B80621820D779582A198 +14058DFDCC6C2D6461AC6F98C64AABA53A2EF77CEF612EA766FE15498F56F54106D70D0F8DD4134C +8C64B282E79A96F15D4BBE0297885ACF775BA5009006315637D60AD86D25A3CA6AC5BF5B4DB95B1A +672A8C589D89101809D838236560F7690E329784C1D335203BFED3A258CD79467D16B6DA06A282BC +4B9669986CB50C54DADE3D5B20FD5CD9FDDE60AF4B5D8D9C50E77CF50DF3B36F37F381FE7E28A719 +91C8199D18258C09BD0440F5E258A5D2E22C6CFBFDC5F91228992E86532F4C0A50067BCB2D4387B2 +9FEFB8043D7BC44A322FD3629D7D92B6EEBA21B035DAC884110BDC6B22CF59B08C195BBA1C682E5F +8CF32F479937D329FE8EC6E9A3A3AA63AE95EEB766E0EACEEAD4DCF46617888AE687009112732A7A +8FA8419A4652075EDDF83E291613B66BA793D3858CEA093C79B89B0C93E11CAAB86AD1980C401713 +0B6A02545DE484FDA6E4DFBCEBBC848751B2A8821CB3F7AC96D2EA9B83B575AB60CECA41EA567EE0 +8ADAC8F1060255057527485D6B12A26D2E7EF6892865FD65FEF28F8B46249C4F19341089D59DAF82 +582CAD1A3B53C763A0E57134A57C4A24AB4FD358592ADB01BE8F1D9FAE9CA97A3BC3A0E2939913E0 +62960CC1D29ECEF9E61BAC20E92F13C49078E7F1562C9D7C01C2B300ECD6ADCAC9AA0C1F1BA30401 +0573F79D158C66D91A8D987C5239C9D4FEED1DC5C9586C4129C33E7C737C624EE9BD1EDE38E9F72A +78DC19E144B5AABF3B0FB72216815E371A5D6452684965CB7208B87CE5A27E444820E03F80AD2DF4 +E280D25D9BDE41E719BB2C939E25B7965DB162CA8665CBFE65DFDC7992A508D54FE4EE8454C11482 +7186A280D3D9EDF397640A809E3646675AB6621B9CDF42808E2F19859AC975CF70F41D2B3D22F8E0 +C180126F4A12122B207A150BC8E1819962DAEBB821DE59B7EEF8FF9219474597AF859E353B4B713F +5F1350F4049DCE99627492E396038B10A2F8E82AB679F65BE14A166E4502921BA44CCFE04E5DD3FE +B04BA47C3F7A3E7DBF7D55328AD1E96092C73EDB16C3325537295DF768E2633D01304EA0B01379AA +DAFE6CCA2AB3E246768C5AC841A458A2F69141A9A9D716D129B26643F1BBC95D8CD0ED44148F7C6A +F370201E454F4AFF7C11A8080508FEDE242343E7ADDD5A8AAF079C50765A79BD25FBBDC59EDF980F +FE0B60435692C49D20AC22F8BD1FCA3F3D8200CA26D61A30212558B54671E63BC09776A3AE7A906F +CA63281F080A00500C42C02B8F82357A3EBF572CFC4BEBA3FE86537DDDF23A861C240A31778A131E +F638C1DBB7E87A45487C092D23FD9FB2C162C3253636ED263B141178FCA9F2D5BE2D81F99B2872C4 +B3C08749CE18C9C00E75DA6B3E4C6A6DD79EBA731618EF5B5E767BB40429D8FEEC6ABFE9975746FB +3A82A03A07B7D0DA8BAECFD1F72AFE17EB0C0CF100A2D48F4449A42C482BB1D64C19F8FEA73F5BA1 +8FC9BFEE3F640C39C081669A9B0EE3FF6B2BDC0A726301EF0E2259E65BE59275C76E7A3F1C76C3A3 +3AD0E2F79198DCA39C482B4E1196238DBA937692CF319436830BBF441C0A8A04161DC0814B5DF049 +83099439EF421B6D37D351D4A988A8DFE93D796B2E8B2564C602F79E6162F56F933196A0DCE91051 +600BAFFD9D439A91F17C20CD6A9104B553D823A0317E3C8344E9F0B2560AD583BF876AE307892500 +2177AF61BDBD745CEC2B2A7931D27423D2667584AE0D6CAA2B281EB9271ADE86C493791AC9A2265B +8324F764D20DE65F6267439F1574DB02C600E771D3C743D420FA6F8BF01A977B91AF035E0D5412AB +0B85D6ED3ED9D0345ED434F9B95FD911A9D3828CD162686E6ECCD2B6A5B1104F0E6838AAC7FB3FC7 +09F08F8E82B4BFCB55D8984771F9F4D339EE7FF391448C7807A436A78B6A487D3A5A86F314B302FA +C5BE1C1DB9844F975619D615D1C7A20EC2B144797E0648CF5C044C8DD1699EEA3AC9D7E3BAA54A85 +11A932623D5ED3F0A9C3028CA439AC395F58DEAF1C0354A169D9AC7F380900D9828C3ECBB975F6FE +6BD79EA4BEA9B71F3A9B1D2EAF8F1E475B4FB99758192EBCDA21B47EA33F57C58E904DA260C801AE +CF457C84592B81CFA96C10E448D705D24871F3D1AD1FE004406C8070A54FA3747788C5B55E9462E6 +F51378BF3F848360542CAD2D5FF9EDAC84C164DFEC115A2F3D873760EAE58CC8F361B37E6CE076EF +325A1C54DFF84DB95DB7DB5B56C48ED15C5426E6153D8A94B1A35B22EAB1D7B871097DF12C093BA5 +538957AB7D0AB2E39A2D1AECB91F0A693B8F6A00601929B6C55AAE8A227CAB6404FEBCC8BC4EEEE0 +ADD4CFD7DEC225170A0063457918B9875AD7F022A8F9932ADE316E4126EAB75C1D0B2B9F44E85F1C +54755D1301345665EA630C8E885A8D9AB069DEA2CC6D12E4A0BF6E80C3AE12BC7ABE507422A5D92B +65F70B4A472DD945EBB960880EC4C34E4C206C260FDF86A997D7D399A0219A51E6F8BC1189EC0ED0 +B8686F0243BFAFD829979747442AB1DA8282F10F4C37C4E6C88D231460DA3BF34C23A4755E2F8F21 +F6D138C86B6091CB3BCE5ED170242C4CC87C010FE63DA9A2ECBDD85311DA8572AD67E40F744D2EC9 +6607701D9790574F051C859EF29291AFA301C809E0A5513DF9A7FC2DB1776DBD4A2CB622BD17811B +C0E584A8D89D4BEB17C2D0AA8BF31CD7E2B36C0E1888409788E30EAC2E46AFA1CFE457D3A19C28C5 +80669CB82CF36BF40DFAFED78F99A5D5DF189CE293413FFBA9E8D2457986C68483242AD767D5F026 +1CE303153C35D5DEB1789448088B51C48A9B8E903BBC69ED7FB0129CCE33A0337198F11DBA94FDAE +AD74BBC5E519F559094E15B03AF3EFA89DC38B4278C68D2AEEEBE4B28B351D7B72F52D94480D8CBC +06544645F20800B8284A4E7C8FAD59D0AA8EC8EC0551912CBC62694A5DD15C469BBE614FB187C084 +838D676E5A1CC8DFA8676758EB7D53FD2BA8F1C434F4F70AED8F2CE27EED2F6A82E20E30EACF9BF0 +CE28B4BF7D556815A887D777B7D75F60D2C410AA2D378B7BFAA6C3A5BF0A5EF80D3E35A0CF393A5E +BC27A895D712C6F004F1A03F9FCF493973F33E6FB106407DFD992AB0E9E36EF395663A6EEFFD71BB +2DAD9DFE6AA4685F307B8FD69BB2F74E672666414A71FA479B37D3DEF6AEAF80FA14821C65F7ABBE +5CC6DD49002216E23B3D22C282F3DE0077EA81017E79D8C3BDB61EC031CFB13FA981120A6C518F4B +4C39C9DD5AC6EDA29ABA527C9948BC3162EF65E8251C9D4E3494C6A51A41D489F8BC89250128EBD7 +0EFD01B9F03C40B6E0384364F2571F7D969016A959E4243D7AB728F0F030B8E2FB456D8E47A85898 +313B0E91C68C7A8A7226603427AC6DCB5C69924FD026E90CB0EE89AC42D874BB15AFC981079E9DC7 +1271C3E461265AF327D4AAB4AEFAB6A9DC3F84DCE7C7868D4719006344B9055C8BA952A2250B8041 +E5EF519BB788201124BE90AECAC2293A310D9565E1EA4AEB99F7ED7106649169E8EBF3DCB210706A +F2FA65158D3059DFBD7AB5B00DBB06B016F73FEDB3AE1C1CC03BC2D94B143BFBD759338EB181B1F6 +841F7B539D8D126FC5B31B244A3A00ABBB121A98F73314D1F1683F513C3CCC9A0C2180824ACE5D45 +E454062CE1217F7AFBDC60A6022B0134EDA6C5D787DE8A5925BEB374F64D612329B9AC17B6EC03F8 +56CFEE502C1FC3AAC7B6F251E8DE4147DCB1580185C406F38E6B729307EADFF6D7A0E824C9830093 +F0C4FECEEFC7FF839306FEFEB780FFBD8879F22AFDADCA4C0767B8EEAE5DB869D7BD91355F0255E1 +CAC68A81A276967F233624DE0D2082B89E2C2D1DB0F8BE0B2BD7051C4DE9B1564AA6CBA03E50386F +D685943D1EDDF5BE0EB34418C48E5EA645CBA552A0444B254E82E9E84A5B7BD0064D5EA87B1716C1 +4EF7FD14E2187FCE16FF6ECBC0A6588B30B4329626F024BDBA65457028668BC6750A4DD668D017CC +EF35CD9CF45EC73885FC91B8AD78920919B4747A710D25BB850B558C5CCF7170C20012DD0F2874A6 +699DF53EDC6C75716A5B6B06C98F4904BAD22B18C77B3EEF32DE3F45A8AC12383DA16DF6F06CD923 +D344CFC2253ECF97A52F2DC6A22539231E29A324F851265BA82BE566E42167247CEBD914EBE42EAB +E189E74FE1FF17ADA8A5F47DC9866CF86694DEE28EED333B0A8AE380557820E3F1D32B05AE27FFE1 +68267FADD310C4EE550F71704EF71F4C669952F30E485AF37561884066C0E3425C748BBAC77CC7A9 +87EC336B8BEBA44358BEA4ECFEA32494A8006E9852C4BFCB1C0F00ACC247F1F8E3F3B34539C67638 +64527F9F885132DCACBFCB1DC80EE3B2A711145DDA1D3B5F83144E2CD698D43A02B0DEB706C78D75 +A24FA39B9374E1155969FAD7D5FB477F4C3C7440DBFE1BD1DC877360E8690A6D3A64EF0A1313A520 +C49320295302227CE5625E134EA442A537505D206EE46E7E218BDA38F7B6BA124ACA0E949725CEEB +961EF8B0727E3A78287E61E65A84AC14D1014A213259A48F04C634C1F04C73B4110D0DB491250CCD +7DAE323CC8BD29BEF605DD92AA5480009DEAD1256E5C9FFB18362D63B022C2A378D15ADE2C6AE603 +1DF385CF090226F0AF889464584A9EA63B884453DE231058C5661C4F7DCF7505D7B1E48C78AA5799 +8FF39084FFF1CD18CB2989D867F2E085B291FE52367B4E8667E5CDA4F41DDBC7129DA5BDC39549E0 +8B67D9C9E4C332D91FE261728521245CBF51870F24C42F5814F393719A9E126C5693C0E776E4D8F3 +0F0288537B8E9A7C5DA4F931FC83F96A1950B1DF26C0448B646D3BF42C0429E2BD0164703FC71833 +19DDEA75CE65D9DFFE8369787FE664306D843421C2C0C3A5B210E823F8366161A96CA88D95FD6486 +5F9E5B0D53DCEE4A98AC10A8FF1BC34A46DB6016E973C9FA298D3F58228E3D9FCCCF9632556CE2E7 +F9C81E05FDB0FC1071E69A4C158E742D0B8939CA3DE33A4BE8D01D679C81C6BA9CF396A6768CA626 +05D796AA5C323E3A6208F2DA5000FC4462ED81AD0044D9959E40E4764562ADA76E33EC63271966F1 +7A13AD376A54FFA40FA247896A22C1C4A4CE51094CA1892A0B9904EB762839AAE1EDB5A0A5A69343 +CCA45F88F61926B23E6C85688862B1D10F1ED827FCCE2A427E49D960DBEE0D39EDE08D2565C6F5AD +66CA83DAC1467C38F742132F715DA7E9757A5D067FDB4A8859D148473F302BF6ECB9B7EA5C0DF851 +8F58D4D076625B92BC8201028064950D70CD40492DA83B98FFD79BD3DCD9CEE95178B6DEA9AB10B3 +4C61A74B49C3697CF714F56A9B1E366B04500C9A67704AF6F721851EA4566B96E220E7B526873F94 +E6013EA8C489639D291167BCAEFB661C96C27DC7BCD5B317C1F101CD8DF2FB29F6162B5ECE903C0D +5570C8C6325F05218850E4A5B5F94C7CE334BADDFC5DB98EF5333B4A92129F41D046D0F5EB6F342C +F362BC8E45511FCF328E2CE2D4C579516307AEC400BA207071C887F5185AF536A6827307D7698095 +E8A6F8C19812A11C32AD207FBC353172C8DF626C5C05CD24C171B3C8DAB270A74AC48E35F0A8849C +E0934B93D56C93599F5235EC441B5232257C4C6767C7B5A35830010E60E56AFBE9199B42E725A216 +C49D45A3C554767DF192F04D1A192D5CE196831EB7A56201A4A96CD44DD084729774334F698E1902 +614405347A37A4C753C0211D7F24D5265CF033E3C4D30E61886CF781D2AC30385E6021A892AB116D +1F5505635BF5C915D0300ACA8AC32CAA0F3639550C4AEB390DA7B86E53B6BB0E4819626757BA9986 +BCA4B4B18F6BF3706577A3BE146F2585025D5A36EB14E587C5469EEEB622D31FD32EBFA812B9CCDC +CFF7D17D00D9F21BF8871661F20E89A25A4543068EC509D0DF7DEA2A24FF68957943BE0FC7BD319D +92A575E12E6FB8112D72A100FEF3EE85972304BBE07FC1026CCC068E8A414F641116E51F76009C5A +F9E664BCCF93C943CFF2115235BB3290F935AA3FAB4F2E73FCDB9FA5E1866EFE278FFE7EA9DD6F89 +38CD5E5F4F31F6C659AF7EFF5B291AFB86521678B42C99275024EDAD04E5929D201A36F4A6C4DCD7 +2E349D6B9A1F90B123DEDE3DA50EDF929F9285747CD504C8F4A73D522F312D623BD65A35419C0729 +30976129B2D99B32D1DEDD81D64F7879CEDDFF04BBBF1286E07BCE651E2DEC190DFB9D25F3C1EEC8 +D7277752BB7CCC313A0FEEBF0EFFA2A189861AD4EA13AD4FDBD5160DC273F12525815F0D693C1413 +AFE4FBE09BCBD71E16C33E6CF8D18732447FFDCC2E4AF9E270D725C1D262D96E72538CA3309BCAF2 +35A3FAFAB27D265C583E6F6F062B555C97DFD89AC46E6154670ADCA13FD99F7A4B9B8BB224D43764 +8CC9DB76E177F562862C912D8CBC248A4C628FF2D0C9688611CED3DFB89064B988F16655633B7CC7 +AFAE34B2937528EA0814ED245D6B0D2AEF87A5B63E2BD7E4F7D9CFE372C295A0891F97C3855C97EC +C03C7C2A231704BED419612255F8B2C9262D48CB4FB46D63CC9697A210FA9D7BFC3BAB7E46172D3F +52A4AF9C4114BC72A5C7CBA042FA633AFD5E404F29408D4485837E55E6F9702F04C7AB410C351039 +6A78C8672F8EAD53BB9CBCED63FB9E72E7238CC88FB4E7C48C3DE3E4B80E277B952727916A4127A2 +5CC1413C390F4DAFD5253B07BC96DAE8CF4DA08330DEE580CBC1E12B75A661819E96B018D47A8B71 +B6BFAFC5E3CBFE68E5193417B6E730E6A2820838D22049BE6BB64B74AA13779D46519965ED80D5FC +30B0A6F73D26DEBD8150B3D27B3F135F608D59BB1632AD9C2E11177FAF54CBD1C4E58D58C395BAE8 +AD7C7AEEB0E1C3F0C0B7A5E7142E9A1DCCC8B4EF56C319D4A4F750857D2FD180F871772B9CC69FB9 +B222F83C3ACCB66125AF6848B2EFDCE3D2284FA5844641FB32F701FBF32F1D2F2E2233B66E36CCD1 +49FCB3FCDB6EA04367D11624717D73D9128EA7D9AABB8658BE9E9986E532 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +cleartomark{restore}if + +%%EndFont +%%EndResource +11.52 /Mathematica1 Msf +0.75 10.5 m +(p) N +P +[1 0 0 1 -61.69 -151.566 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +70.6 105.842 m +73.497 105.842 L +s +P +p +np 52 98 m +52 113 L +69 113 L +69 98 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 53.44 99.467 ] concat +1 w +[ ] 0 setdash +p +np -2.44 -2.467 m +-2.44 14.533 L +16.56 14.533 L +16.56 -2.467 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(2) N +11.52 /Mathematica1 Msf +9 10.5 m +(p) N +P +[1 0 0 1 -53.44 -99.467 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +70.6 53.742 m +73.497 53.742 L +s +P +p +np 52 46 m +52 61 L +69 61 L +69 46 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 53.44 47.367 ] concat +1 w +[ ] 0 setdash +p +np -2.44 -2.367 m +-2.44 14.633 L +16.56 14.633 L +16.56 -2.367 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(3) N +11.52 /Mathematica1 Msf +9 10.5 m +(p) N +P +[1 0 0 1 -53.44 -47.367 ] concat +1 w +[ ] 0 setdash +P +P +1 g +[ ] 0 setdash +p +0 setlinecap +78.19 2.952 m +78.19 5.849 L +s +P +p +0 setlinecap +92.764 2.952 m +92.764 4.69 L +s +P +p +0 setlinecap +107.338 2.952 m +107.338 4.69 L +s +P +p +0 setlinecap +121.911 2.952 m +121.911 4.69 L +s +P +p +0 setlinecap +136.485 2.952 m +136.485 4.69 L +s +P +p +0 setlinecap +151.058 2.952 m +151.058 5.849 L +s +P +p +0 setlinecap +165.632 2.952 m +165.632 4.69 L +s +P +p +0 setlinecap +180.205 2.952 m +180.205 4.69 L +s +P +p +0 setlinecap +194.779 2.952 m +194.779 4.69 L +s +P +p +0 setlinecap +209.353 2.952 m +209.353 4.69 L +s +P +p +0 setlinecap +223.926 2.952 m +223.926 5.849 L +s +P +p +0 setlinecap +238.5 2.952 m +238.5 4.69 L +s +P +p +0 setlinecap +253.073 2.952 m +253.073 4.69 L +s +P +p +0 setlinecap +267.647 2.952 m +267.647 4.69 L +s +P +p +0 setlinecap +282.22 2.952 m +282.22 4.69 L +s +P +p +0 setlinecap +296.794 2.952 m +296.794 5.849 L +s +P +p +0 setlinecap +311.367 2.952 m +311.367 4.69 L +s +P +p +0 setlinecap +325.941 2.952 m +325.941 4.69 L +s +P +p +0 setlinecap +340.515 2.952 m +340.515 4.69 L +s +P +p +0 setlinecap +355.088 2.952 m +355.088 4.69 L +s +P +p +0 setlinecap +369.662 2.952 m +369.662 5.849 L +s +P +p +0 setlinecap +384.235 2.952 m +384.235 4.69 L +s +P +p +0 setlinecap +398.809 2.952 m +398.809 4.69 L +s +P +p +0 setlinecap +413.382 2.952 m +413.382 4.69 L +s +P +p +0 setlinecap +427.956 2.952 m +427.956 4.69 L +s +P +p +0 setlinecap +442.53 2.952 m +442.53 5.849 L +s +P +p +0 setlinecap +450.12 210.041 m +447.223 210.041 L +s +P +p +np 451 203 m +451 217 L +459 217 L +459 203 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 452.28 203.666 ] concat +1 w +[ ] 0 setdash +p +np -2.28 -1.666 m +-2.28 14.334 L +7.72 14.334 L +7.72 -1.666 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +1 g +0 10.5 m +(0) N +P +[1 0 0 1 -452.28 -203.666 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +450.12 157.941 m +447.223 157.941 L +s +P +p +np 451 151 m +451 165 L +460 165 L +460 151 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 452.28 151.566 ] concat +1 w +[ ] 0 setdash +p +np -2.28 -1.566 m +-2.28 14.434 L +8.72 14.434 L +8.72 -1.566 L +cp +clip np +11.52 /Mathematica1 Msf +1 g +0.75 10.5 m +(p) N +P +[1 0 0 1 -452.28 -151.566 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +450.12 105.842 m +447.223 105.842 L +s +P +p +np 451 98 m +451 113 L +468 113 L +468 98 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 452.28 99.467 ] concat +1 w +[ ] 0 setdash +p +np -2.28 -2.467 m +-2.28 14.533 L +16.72 14.533 L +16.72 -2.467 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +1 g +0 10.5 m +(2) N +11.52 /Mathematica1 Msf +9 10.5 m +(p) N +P +[1 0 0 1 -452.28 -99.467 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +450.12 53.742 m +447.223 53.742 L +s +P +p +np 451 46 m +451 61 L +468 61 L +468 46 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 452.28 47.367 ] concat +1 w +[ ] 0 setdash +p +np -2.28 -2.367 m +-2.28 14.633 L +16.72 14.633 L +16.72 -2.367 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +1 g +0 10.5 m +(3) N +11.52 /Mathematica1 Msf +9 10.5 m +(p) N +P +[1 0 0 1 -452.28 -47.367 ] concat +1 w +[ ] 0 setdash +P +P +p +np 210 257 m +210 272 L +310 272 L +310 257 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 211.235 257.578 ] concat +1 w +[ ] 0 setdash +p +np -2.235 -1.578 m +-2.235 15.422 L +99.765 15.422 L +99.765 -1.578 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +10.08 /Times-Roman-MISO Msf +p +0 9 m +(I) N +P +p +3.75 9 m +(m) N +P +p +12.75 9 m +(p) N +P +p +19.5 9 m +(a) N +P +p +24.75 9 m +(c) N +P +p +30 9 m +(t) N +P +p +36.75 9 m +(p) N +P +p +43.5 9 m +(a) N +P +p +48.75 9 m +(r) N +P +p +53.25 9 m +(a) N +P +p +58.5 9 m +(m) N +P +p +67.5 9 m +(e) N +P +p +72.75 9 m +(t) N +P +p +76.5 9 m +(e) N +P +p +81.75 9 m +(r) N +P +86.25 9 m +(,) N +92.25 9 m +(b) N +P +[1 0 0 1 -211.235 -257.578 ] concat +1 w +[ ] 0 setdash +P +P +p +np 34 75 m +34 165 L +49 165 L +49 75 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[0 -1 1 0 35.28 164.105 ] concat +1 w +[ ] 0 setdash +p +np -1.895 -2.28 m +-1.895 14.72 L +90.105 14.72 L +90.105 -2.28 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +10.08 /Times-Roman-MISO Msf +p +0 9 m +(S) N +P +p +6 9 m +(p) N +P +p +12.75 9 m +(a) N +P +p +18 9 m +(t) N +P +p +21.75 9 m +(i) N +P +p +24.75 9 m +(a) N +P +p +30 9 m +(l) N +P +p +36 9 m +(r) N +P +p +40.5 9 m +(o) N +P +p +46.5 9 m +(t) N +P +p +50.25 9 m +(a) N +P +p +55.5 9 m +(t) N +P +p +59.25 9 m +(i) N +P +p +62.25 9 m +(o) N +P +p +68.25 9 m +(n) N +P +75 9 m +(,) N +10.08 /Mathematica1 Msf +81.75 9 m +(c) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +10.08 /Times-Roman-MISO Msf +P +[0 1 -1 0 164.105 -35.28 ] concat +1 w +[ ] 0 setdash +P +P +p +np 70 3 m +70 238 L +450 238 L +450 3 L +cp +clip np +p +np 117 136 m +117 152 L +155 152 L +155 136 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 118.485 136.705 ] concat +1 w +[ ] 0 setdash +p +np -2.485 -1.705 m +-2.485 16.295 L +37.515 16.295 L +37.515 -1.705 L +cp +clip np +p +np -0.485 0.295 m +-0.485 13.295 L +35.515 13.295 L +35.515 0.295 L +cp +clip np +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +p +np -2.485 -1.705 m +-2.485 15.295 L +37.515 15.295 L +37.515 -1.705 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +%%BeginResource: font Mathematica2 +%%BeginFont: Mathematica2 +%!PS-AdobeFont-1.0: Mathematica2 001.000 +%%CreationDate: 8/28/01 at 12:01 AM +%%VMusage: 1024 29061 +% Mathematica typeface design by Andre Kuzniarek. Copyright \(c\) 1996-2001 Wolfram Research, Inc. [http://www.wolfram.com]. All rights reserved. [Font version 2.00] +% ADL: 800 200 0 +%%EndComments +FontDirectory/Mathematica2 known{/Mathematica2 findfont dup/UniqueID known{dup +/UniqueID get 5095653 eq exch/FontType get 1 eq and}{pop false}ifelse +{save true}{false}ifelse}{false}ifelse +20 dict begin +/FontInfo 16 dict dup begin + /version (001.000) readonly def + /FullName (Mathematica2) readonly def + /FamilyName (Mathematica2) readonly def + /Weight (Medium) readonly def + /ItalicAngle 0 def + /isFixedPitch false def + /UnderlinePosition -133 def + /UnderlineThickness 20 def + /Notice (Mathematica typeface design by Andre Kuzniarek. Copyright \(c\) 1996-2001 Wolfram Research, Inc. [http://www.wolfram.com]. All rights reserved. [Font version 2.00]) readonly def + /em 1000 def + /ascent 800 def + /descent 200 def +end readonly def +/FontName /Mathematica2 def +/Encoding 256 array +dup 0/NUL put +dup 1/Eth put +dup 2/eth put +dup 3/Lslash put +dup 4/lslash put +dup 5/Scaron put +dup 6/scaron put +dup 7/Yacute put +dup 8/yacute put +dup 9/HT put +dup 10/LF put +dup 11/Thorn put +dup 12/thorn put +dup 13/CR put +dup 14/Zcaron put +dup 15/zcaron put +dup 16/DLE put +dup 17/DC1 put +dup 18/DC2 put +dup 19/DC3 put +dup 20/DC4 put +dup 21/onehalf put +dup 22/onequarter put +dup 23/onesuperior put +dup 24/threequarters put +dup 25/threesuperior put +dup 26/twosuperior put +dup 27/brokenbar put +dup 28/minus put +dup 29/multiply put +dup 30/RS put +dup 31/US put +dup 32/Space put +dup 33/Radical1Extens put +dup 34/Radical2 put +dup 35/Radical2Extens put +dup 36/Radical3 put +dup 37/Radical3Extens put +dup 38/Radical4 put +dup 39/Radical4Extens put +dup 40/Radical5 put +dup 41/Radical5VertExtens put +dup 42/Radical5Top put +dup 43/Radical5Extens put +dup 44/FixedFreeRadical1 put +dup 45/FixedFreeRadical2 put +dup 46/FixedFreeRadical3 put +dup 47/FixedFreeRadical4 put +dup 48/TexRad1 put +dup 49/TexRad2 put +dup 50/TexRad3 put +dup 51/TexRad4 put +dup 52/TexRad5 put +dup 53/TexRad5VertExt put +dup 54/TexRad5Top put +dup 55/TexRadExtens put +dup 56/LBrace1 put +dup 57/LBrace2 put +dup 58/LBrace3 put +dup 59/LBrace4 put +dup 60/RBrace1 put +dup 61/RBrace2 put +dup 62/RBrace3 put +dup 63/RBrace4 put +dup 64/LBracket1 put +dup 65/LBracket2 put +dup 66/LBracket3 put +dup 67/LBracket4 put +dup 68/RBracket1 put +dup 69/RBracket2 put +dup 70/RBracket3 put +dup 71/RBracket4 put +dup 72/LParen1 put +dup 73/LParen2 put +dup 74/LParen3 put +dup 75/LParen4 put +dup 76/RParen1 put +dup 77/RParen2 put +dup 78/RParen3 put +dup 79/RParen4 put +dup 80/DblLBracket1 put +dup 81/DblLBracket2 put +dup 82/DblLBracket3 put +dup 83/DblLBracket4 put +dup 84/DblRBracket1 put +dup 85/DblRBracket2 put +dup 86/DblRBracket3 put +dup 87/DblRBracket4 put +dup 88/LAngleBracket1 put +dup 89/LAngleBracket2 put +dup 90/LAngleBracket3 put +dup 91/LAngleBracket4 put +dup 92/RAngleBracket1 put +dup 93/RAngleBracket2 put +dup 94/RAngleBracket3 put +dup 95/RAngleBracket4 put +dup 96/LCeiling1 put +dup 97/LCeiling2 put +dup 98/LCeiling3 put +dup 99/LCeiling4 put +dup 100/LFloor1 put +dup 101/LFloor2 put +dup 102/LFloor3 put +dup 103/LFloor4 put +dup 104/LFlrClngExtens put +dup 105/LParenTop put +dup 106/LParenExtens put +dup 107/LParenBottom put +dup 108/LBraceTop put +dup 109/LBraceMiddle put +dup 110/LBraceBottom put +dup 111/BraceExtens put +dup 112/RCeiling1 put +dup 113/RCeiling2 put +dup 114/RCeiling3 put +dup 115/RCeiling4 put +dup 116/RFloor1 put +dup 117/RFloor2 put +dup 118/RFloor3 put +dup 119/RFloor4 put +dup 120/RFlrClngExtens put +dup 121/RParenTop put +dup 122/RParenExtens put +dup 123/RParenBottom put +dup 124/RBraceTop put +dup 125/RBraceMiddle put +dup 126/RBraceBottom put +dup 127/DEL put +dup 128/LBracketTop put +dup 129/LBracketExtens put +dup 130/LBracketBottom put +dup 131/RBracketTop put +dup 132/RBracketExtens put +dup 133/RBracketBottom put +dup 134/DblLBracketBottom put +dup 135/DblLBracketExtens put +dup 136/DblLBracketTop put +dup 137/DblRBracketBottom put +dup 138/DblRBracketExtens put +dup 139/DblRBracketTop put +dup 140/LeftHook put +dup 141/HookExt put +dup 142/RightHook put +dup 143/Radical1 put +dup 144/Slash1 put +dup 145/Slash2 put +dup 146/Slash3 put +dup 147/Slash4 put +dup 148/BackSlash1 put +dup 149/BackSlash2 put +dup 150/BackSlash3 put +dup 151/BackSlash4 put +dup 152/ContourIntegral put +dup 153/DblContInteg put +dup 154/CntrClckwContInteg put +dup 155/ClckwContInteg put +dup 156/SquareContInteg put +dup 157/UnionPlus put +dup 158/SquareIntersection put +dup 159/SquareUnion put +dup 160/LBracketBar1 put +dup 161/LBracketBar2 put +dup 162/LBracketBar3 put +dup 163/LBracketBar4 put +dup 164/RBracketBar1 put +dup 165/RBracketBar2 put +dup 166/RBracketBar3 put +dup 167/RBracketBar4 put +dup 168/ContourIntegral2 put +dup 169/DblContInteg2 put +dup 170/CntrClckwContInteg2 put +dup 171/ClckwContInteg2 put +dup 172/SquareContInteg2 put +dup 173/UnionPlus2 put +dup 174/SquareIntersection2 put +dup 175/SquareUnion2 put +dup 176/DblLBracketBar1 put +dup 177/DblLBracketBar2 put +dup 178/DblLBracketBar3 put +dup 179/DblLBracketBar4 put +dup 180/DblRBracketBar1 put +dup 181/DblRBracketBar2 put +dup 182/DblRBracketBar3 put +dup 183/DblRBracketBar4 put +dup 184/ContourIntegral3 put +dup 185/DblContInteg3 put +dup 186/CntrClckwContInteg3 put +dup 187/ClckwContInteg3 put +dup 188/SquareContInteg3 put +dup 189/UnionPlus3 put +dup 190/SquareIntersection3 put +dup 191/SquareUnion3 put +dup 192/DblBar1 put +dup 193/DblBar2 put +dup 194/DblBar3 put +dup 195/DblBar4 put +dup 196/BarExt put +dup 197/DblBarExt put +dup 198/OverCircle put +dup 199/Hacek put +dup 200/VertBar1 put +dup 201/VertBar2 put +dup 202/Nbspace put +dup 203/VertBar3 put +dup 204/VertBar4 put +dup 205/FIntegral put +dup 206/FIntegral2 put +dup 207/FIntegral3 put +dup 208/OverDoubleDot put +dup 209/OverTripleDot put +dup 210/OverLVector put +dup 211/OverRVector put +dup 212/OverLRVector put +dup 213/OverLArrow put +dup 214/OverArrowVectExt put +dup 215/OverRArrow put +dup 216/OverLRArrow put +dup 217/Integral put +dup 218/Summation put +dup 219/Product put +dup 220/Intersection put +dup 221/Union put +dup 222/LogicalOr put +dup 223/LogicalAnd put +dup 224/Integral1 put +dup 225/Integral2 put +dup 226/Sum1 put +dup 227/Sum2 put +dup 228/Product1 put +dup 229/Product2 put +dup 230/Union1 put +dup 231/Union2 put +dup 232/Intersect1 put +dup 233/Intersect2 put +dup 234/Or1 put +dup 235/Or2 put +dup 236/And1 put +dup 237/And2 put +dup 238/SmallVee put +dup 239/SmallWedge put +dup 240/DoubleGrave put +dup 241/Breve put +dup 242/DownBreve put +dup 243/OverTilde put +dup 244/Tilde2 put +dup 245/Tilde3 put +dup 246/Tilde4 put +dup 247/BackQuote put +dup 248/DblBackQuote put +dup 249/Quote put +dup 250/DblQuote put +dup 251/VertBar put +dup 252/DblVertBar put +dup 253/VertBarExten put +dup 254/DblVertBarExten put +dup 255/Coproduct put + readonly def +/PaintType 0 def +/FontType 1 def +/StrokeWidth 0 def +/FontMatrix[0.001 0 0 0.001 0 0]readonly def +/UniqueID 5095653 def +/FontBBox{-13 -4075 2499 2436}readonly def +currentdict end +currentfile eexec +D8061D93A8246509E76A3EC656E953B7C22E43117F5A3BC2421790057C314DAE3EFBFF49F45DD7CD +91B890E4155C4895C5126A36B01A58FDB2004471266DA05A0931953736AD8B3DEB3BCB2A24BC816A +C1C90A1577C96B9096D6F51F9E21E625ADF6C3A49867A632A605C117E6325C820121799F412E226B +EFE61F2813676F172CBD7EC10FF1EFBB92DF3A88E9378921BBD00E6024CC08EF057CECD09B824E0A +CCDAA4644296DE34D19D779A21C30666026829D38FB35A2284CAED23C8B913E7B28BB3DA7C8CE390 +4C0BAE30B0287680CCCAB2E6D4CAB2E7D786ABF54068028FD7D94FAC236094761B62B7E76F68D2BE +58C23AF85001950EFC1A9C1BB71520B78DDF6AA0058D25D041E86D01878DF56A5C48D74DCB2BBD68 +D75C94A3CE878484D28049331CE3D4364B40FAA2C754E8F443D244C5BC44B1C7868E36EAF4F7EF1F +6CB81E6CF63FABD65C29A991EB7D724DA06535AE43F3D0E2D04F6113B493C15463B4CEBFB72AB879 +7E4645F9CC0BB17E02A9626BEA4B4259F798B53B18DF2ACCF2B86BF2209CF0265DE0A46869333F98 +CCF70BF2C9239A0ABD2E97923AA5695BAFEA31E27B8F532BAA45F2980A11D069265A5312A260A627 +A765C4A08897E5C500990AE6FDA4CD6D575905468417297380EB6400CB2CF001C4B8EB79811CD8D7 +C173A337922B99DAB1048D5D03C78F78F36FEE31673D8C5FF8AD689A63AEA055CA705DB47D3AF965 +73571985E62F63866018C96EC4CA7E735C9294D8C81C03806D23CB87C0C08F86F5FA68CFC9AE48F6 +958AE016DCE4D60EB64AEAAD59D8A2592BC398BCA479FBC2F0C20C3E7F730481494C88781A6A9E0E +4F47A94619A3841FAC76A4FB252EB6FB43628AE1A4944539B1DFF672940AA5E93FFACFAC04624EF6 +7ED9C691788F0004DB7FFD1C995F2C52C0042F02F5C789F85D9E51716F3B4EDB21D4B9E660E4A892 +B747201EEC6DD6A8881FA3039664061094D1892108A2AD068D7F0251BFA72D874ECB2F42D27CC343 +052156F6A3A66D2CA6DAEF046A433FD54FEB4410315690971D0F43363EC0119B21F4BE3DFDC8C28D +BF5D68F145D3AC932EE62C32DFDEB1C48C2455392193268C893093BF911645986607DD13D8285895 +1854A7FF81FC98ADD44742907818B3C8E3187371BD9FE6EF5B4315E82C359BF2EA91D2894EE7FD9A +734BF2745D7FE8D08D29DA2A03176C992E11F1AADCE219D5E33F325BCFF4521D6D04E61B913B6C41 +740AF8BD9EA83F3AE7C4E5402366A812B1724431EE78D2028317B975E91941B591CC6C97740C453C +C7D7AB3CE97AE2F4DFAB6B9C8810A52A276BEAABD687BDA0971EE623A40B7AD6A8E69ED8BE63959D +3DCF8799E2505AC7F7B0E2FFECB0B08027A6266B7F96311B0AD78B7B6C78392AA062A73FDF179FEC +7F748929D060289F899FE417D4028FF332204FE04146BB130EF05FB57AF4BF9337EF71094DC5922E +3EF2D6A9F8257AF242019C349B65C2A3972ACA842D14EAB6A8D287456C9D7D295198F4AB7632EE43 +7D006B8124A80BBF26C1B902379D7F90080B51F982630E27B6031CE20930C273EA5D7FF0FC7E996E +67B072796CD96A7739444D21FE10DE7B57D143E4155EEE1B89F32EBCBF4C64D6D3FA1C46E06C9E9F +F99BC9BBCC61A400C7DBF812C42AAED863FE9EE3B02E731D1809F3CAB941252DE486BFE641F60F60 +C788F68519A6B04A4D6F50F02F93C6B8774B960C1FE893373D2AC2D865C487CFFE7669E1F1E73630 +7D34C15835F0453C8B0C1AE352CE1F27065F1082E6C84F86330F76B39C246315D6996AB69F81A020 +30D92CCB4C2A5AA4F4B3CAC88B2C8C9C621294A5EAB6AC3778DB99BD2F411735DC1421861417B4FD +399B365AEA45185A3D9D687546E36BB73994FB7FA3EE890AE3734BD9381B0E7AE514E8C517B87268 +7364C38D0A8379039F33336799205F2F54BBF7C2E9B30B27BCFB9FF2CD64F5D700F2455EE66B6252 +6E79ED2B0E5FF9732281CA50D27A93F259B6D4B5C7F856BB7D4F2E0F7741FA2419BBAF86C372E34D +59BC7AABC4CEF4F47EE40E118AB95A4863E16C55824D34002D59844D1F51B5DC6FB6BB5D595C4404 +1E05A84FD453A129279F894D726F6CD53BA3E234518324C5F715DAE6E7B5695326FC0C9B6CA2B53D +B25EC76BE9662388058629E70DC7BD861E188F1FF32A754A455195163CB4754D116D24E6A6300B67 +1862625E260442DEA2505E36D5F7AA4AD1FEB3B42632E382959C7E84569B2A790A0D0A208C2D4816 +AD28046B42C7A4797D424277AD9425C04DB87DCF112AE431CFFF6E4FFA979E947502AE5E1755C112 +0AE2361888B956F3F513A5975680E6E8374D8BF26C32AADC826D729E9026B14A68BC3D065E11C697 +D4D474CF963AFE083DD7D9278A0C27447E25AD70DD40B2EBAB8040164E11CD75AE3125C29806DEF4 +AD1B989F7001E227685DEF6EBE3287DE43BBA5FE2123A0EC835AECF879C13F7CFDC409901F291E89 +06C26E42B189862AFAE029F03D86D35E44E318FE16537E2B73E4F39F1E6945A3A6432438DCB6D2B2 +09814B5228D08165568617C279499ECA1B88C90300F1C88C45D4BE3DC726A59E24B46C5B2FF228C6 +E6645819C6F1D5B05737BE7353F4787EE52A21DC47A44F3C44406E79BBFDDC164682B02F4C39300D +12EF37A58E317FC1B8CE58E04BE666ED5DA75DBF752BEDDA4C7491E4D6922BCCA9CF421CE6751002 +8638EF643119841F423626D6B19A5D2CFB193B093D7646913F64614C098E5F5FF9422EBA75FA9AA8 +4F8ED114AEAB011E6F0727FB96F26BECBBAFE3AA8D0ABC5A8E9906B6CBB9E03F8CC4FCA97C884B83 +7CC33C619CD3195C55633B72D3F2D45561CD226F42B859B8099812D591886FA851107A185169FA7C +944248DE28642FA3043FF3B60236BFD781257C6FE4D56174AD16ABBF9659C05F08673A70496A0787 +C187D4367CB0CF48BD9A4FE0E481273E4909A1092626A13917DCBDE920028B06E094F5B28887B990 +32521E1720B75EB284AA6FFE53FA5CD5B903F951FCF7B33CC981FE7BCC4BDF4907ACC3AA60B69969 +A9AF204C84EC8C0F5DCB8A85E39EA9F2D4B67095A44CA0C8B072D7A61F3015D502B1A0F660C22221 +3231986D5E8C04AECBAFE999D1735A80051C06CA279D0FF6215633FB7A706454DA7236DB17AD72EE +1F6044A26A0EB77AB3BCE823E3F5E0DD31ACB029A1D17665FF16E5D1ACDDFD83CAEE1D48666D7BC6 +DADC34D317C335855D118892CBD32412F5870C3D2E599A46AA997A5E2BBDD3001C2957D81345DBED +583B72C4FB356F0C872A31A35087775EF18601930C0874EEA1ACB3ED3690EF447926439CC383087C +C9D5C6EB21EDF5941CB4E99FDA434A91676D76DC1A1BD801EECA6B0A88370B7005D41A1253CF8217 +1285986DC302B51123DBA9733BDEF0361AE0580FE6FBA5F29CF1438801586559D7436436CFE33E6A +D6EFA850BB8C9382E1A068246F370388186DC278F31F770C2C96881AC6E40823B24D6D536514A2C7 +AF3D159621080442592CAC03D6967BCBDB38FCA1A45F76F1A10027F1DCC076533C6AFC097FBCF0DA +A0078BE0828247F938AF76A87EFC14D9F5444CBCDCE637E2325D86B9D99D1ED70F44194E19F6C7A9 +9E415DC8A6E484DAAE52AAC1307A5353E8F649A35214B3F43DB5F3DB3ED06580A570E60B3E52679F +F90A3B00D4EB4DFBCF0D2F9C5545A6DE10BCC849A0BA9E18F29C6F09ED0F0DD48AD37B7925654433 +A6D02965A3813BA2EAB2E6C2004ADD216DAE99471EE518BD0DA0F534F1512A8E26286B07FEDE71E6 +0A5A057A22AEF095A8B4752F54C04CB8BC170F3D6B725B83A6780176194B21BA906E37B7923E2548 +604F8DB18E0A3E1B5FF04D00898C29C6033DAC54637CF5B068291559D8526D5201F3503FBA4EE12D +D7A6CF6271618F41FE08384521CD771FA80364D747430A071EE3D3ABDB5400DD36A0285430D537FA +F6EF8ACAF85C250D6652F327B2BD3B29E1C64E6E66C788FF1D9C3AC6DD38691CDECD0F3FF4079BAD +A2BC0CBE14AA3FCC38E3F31B3298A6995C87B34A7245ABA2C968F908D8337860507569C370654635 +570615F987531551414B5CCAF7F4D0B38F701619C553E746BD90655294270560046A925A021C98F9 +3EA8FF5B5B8A0D05AD483E6DDC5257635308C6C0FE9182D0E4FB011A00981A7B95DB5BF5A82F8B1E +B68E8822F8B1B7CF01AF11302B44307F3A71D5EB3465F793CAEB1E72D2C63E3D264380A75FF1DDA5 +00B5F82B04179EA9DAC10731FDEDF5913EFDEDDF5799D2A86EF2D16C0B52D99FCEAD392E9226AA6D +3D11D29054130C8602F703CB1EBDAAA089A02C0EBD53343A7B297836CB63E4B2C33C8760ECEB15E5 +6B59D4B296B8B724244D37651C3CB862C7135D62B2692F5A27B9374C5C3C5399E5C4DCCD76572294 +F742B1F545B736BF4C82D8F4E2941CD52C0B944261DD4CCF8A968B646662F3D187557206FF165F3C +0D3D5CA1E428D61D7936E1D00C5377A047EE80E0A5612F7FDEBB8B224270ED23A031A049E8676516 +BF66EBAFCF3F9D4975B0F212FB7A914EE45640972B61AE8E60E602DC7C20758BC07A159B08862F16 +6302D8CBEF03C4B0C73BD8504EB5B14DBC64FBDDC867FE51F76099769A7BD4FA4CF4096EAAAFD55F +9A20F6D4B84D8FD139112A43204026F15F9FF5AB6729537CCDA60369C24D7EFF4B6B971EBF0BD277 +A9AD1BF1066508A0A7DD9A8D45447A39B92D6B0F7DA4BEC2689D25F35C8C490891F053F90DEE5E2D +8C9D7FD1E23D0F1C5F57921BDB13BC9B8C3CED4FC42C4DDBF0706985A0DDABCC683FF5EA8416C225 +ABD219024432E3972C3314C30A672FD21523C83D93B2AC8D1DF023EEB1BD74E825FCD19873E63A45 +F6935E6685CF5EF472191B976F9EED2A4765E1B21B46EE1C4CB90AE89DA48E52BC4EDBAC2C855A67 +CB0BE9160F3126151CD171081192D0D6CB27E4EB2D725F31AE95FB283149F53F22BD8E414354D4BB +56724057601FE4BF34A5B188C00B0E550639CD796CC66EF895AA5315BEAD49B5639EF0878CDF2CA4 +271027678693EA212D0C11A6EA52F748AD0F62A0336BEC8497EE933EEC461E461CCD2F5291B980E2 +8B7D66690B10EEBE22B092179396EEF5D00E42E6CB73BAD4485F2063AEA6B6207E136ABB925332C2 +60F12D8B60B211A9BB15F37F42F53AC2559F5A8397DDD491D314B6DB0B50E41F0AA0A42FFDD5B9F3 +FBD8EFB7E343C06F793DA6BBEE4FAAFB233C31EAA3AD701B1F1C4F2FB05A7647439D19CC856C7D98 +EB3375B3ED2255FA33D9ACB87C1937E0B7F34C9B299C8D2A7F85D41141C598F9505C72B5AC2DE9BD +E24CDAE1DEE416786B93D4EE713E437D2C4A3251A296B785C81A8232F98ADD04B3D2B41394F8BDEA +7B602A555EDBD51A088C2277D8E86B08A0D05CB6142E33E973BB3F2CE841D323ABE6FBBF83B84272 +220F569DE23264AB672C2590D4527A29510E7F289DC2193E66FF23D83703E27E9E81572E7534B1DA +510BB846576E8A39D9CF44483F297C92317ED8E46237B3D73844B3B19D37831B44EC116CBAC3F75B +B67928C4D4E741EC95E96FAD74D852220F4F1A8FDCD273E0F6E77F09EFD5723CCA1398A021FAE947 +9CAC5922DAC8E2F46704BC216C7BCC1575A455CCE2C2A080B9FDCD0758D9D921EEB6DF96C64A31D1 +C9BEA80F48461857ED7DB635A3BABB3BB6155E835E605B09E06A2AAF6BF3EA70B8C0E098CD1A818E +30B9A4AADC284EE2B87E945B717FA73AFF5FB788E51827F6FBE319ADDD059614B493ECCE718789A2 +EB0F117EC811EC38A3F4EDEACA660612BD247425A3FB2E6022CC14FDF69B6660B37FCD4359F1BA54 +D12B1F478D76CF898824762C25A026B01C94752F6F3724C31AE788CFE992D9CA152902EEBC4AD8B7 +A5F0E68A5A0408A2F0BA71CE0D94B4A905B35F2781D7E7A2712DC62E87518BFE72A5BC4A9F36A4B3 +B1494B0C4C14705203762E0CD0B28BE31234449C7655B5D6165D7CC4A16431F7A8ECA58D25711E98 +4FF2CE123C05AF9A65D478B73739715DE62A199D47BAC65785EE1DD25AF91868F91D037C0AD754BA +CE3DC4B67F1FDCA8FD9FA39796EFA9C975DBFAA99DB70624B3563408D0048E3AAC6B4F228DC0AC08 +B9C2B63657EEDB53B46D157426A3B4B4B8CC5B4F30BC24CF9BED442DB51F3C7A0656DFBEFA401E1E +0823065499C69D51C477360FD13ACA8896A8117789C4561F3D85F3A80D18E39F1D6BF76C7876922A +1038ADAFD53F2D092369B356D0CA3FE6A27D7B9BD3985C78424C21E60F6BB46408013DFD7A30D320 +EAD9AC6E5FD36655AC6706666A76F1E426640C4B0BE692E4879991EA9EDF4596C0DDF43D4E370360 +D91E8B2839D368DA2A910AA6092337E2E20DEECF43D583CF164881079ED5A492B5EFCC1CAF91512E +0FEA8140CA3E2553733D6F743728ACAC3E643394015967DAC8839D5A804503A45DBC539FB8656D75 +2F00EECF73E7EC8746CB13F438CAFD554C01150048F758573903B0B3260AEDD78BC2EE87D201E219 +486315A4C01D95DAAB54335A4F2CAFC3F43F12A9574CD2DECCBC1858406C701EE62C281A55B729DC +EBBE74FDFF275A0A7B8B22C7490187D1839F4FF271723C140095F28221C4145C9061F9A5B3EDF8D2 +9E0DA04D9A8AF6ECD42DB897DD5C9318D636FAB698554BD9EF9B0902BFD8C96CB958773A3C4A5FCE +8A334C673206C39277E45AB50DA2661F89D621AF057CF1A7ECDE344DC7658514B4C655937E7BE010 +B0694B069FF64D5582E3A4B7F6AF6C96D056ABB20CC883AB25A9BEABB18A84F0258CA3E4F33FFB77 +9841F5970DB447969FE9C6BFDB066ACBC040648D74F553EE434BADC353450A3792EEF9CFDB2FBCD6 +07153F2EF73C1BCCE3784609F26C80193BAEF766E7CC7C33A4CAB862E6E01FC1CDF11E2FBF25FE1D +308CFF9CD924893861BABF78F762F3CADD3E0BEB38F6157CD08F1B8767606427C4A631AFC9488E6D +4D1A8F4B51ED48582BCD3089BE037ECFF18DF6175EC317EA56D4FDE37288F089532C13F7B3C1EF7D +333E7FAF8B49D95F535F60889CD7245E5CB0BEBFDAE8F7A0AC1AB7DA18F2BC06267B27403F1BAD9F +DF5F13254E96C294C4568EC7154479570E514A55208D19A4538959F7C5B2A0C9CFE4B4A40300F248 +5943C6AAB753F3F0E551727B8DA8E75305A9CE623757B36FB7D34D13CB14EE561C404CDB2D88F375 +2BBFD9FDBCC92CF110D6C35629E3040D995CD25B90BED2CE79BBDC846EAA321B1BC46DFF7845380F +BF08782D6A31EC7D41F251786FDE403A39626D6052D5189CFBB3DCFF09E81C09D0CE7D571F979509 +79B26AA4F6D07F97A33522306AD692D4D960CEF1CEA3D251A841E2A23C7AE3EA44B3F31E79808F22 +B6ED20DEE4186927394624E22D37E873B660BB8DE6FFAE058DD5817A3BBD68963D055D406F253701 +D940B5E58DAB01FDFF1C626D0D35E8E7995D37057DD07F6D4D45F152A141269A7FB81433652209B2 +B23D69BB5D8D367D95D4A94B2C2924FB83F50F651458CABCB65009912F00F485A924A61A0008DB44 +D478CAFDB7D9524204947703B822755A1301FE163A8248C8AED7073362D918D097597B24A3B579DF +FE00F47776428D2B992E1F5FAD6190ADD1C789BB9A913BB1B938EDE357BB436F734477F7BF332E36 +7C380D049AED8687B4C2AB6EB813E719718C9CE62296C24F783B16E9635A7E4402840BD26D3A0DA5 +78050C49E6D8239EBE9E053719BE94CF6D8C7942AE1660F28B21B3C1E6E15459C1FEEA4FAAE64AA7 +165824F7B939E28E51009FB778E370C6001B3F546EBB76454912D730A3F8C7F6F9EC01B9F90A2E5E +58EFF9EA99BE57A0154147C2B7A7C7E493E37C9BD0ECDEAD4AA4DBFF95D7E451246C9D4C30E71F4D +76441A297A1223FD2F8E017B10C108B0F0F67385060E9C18133A6264AB2B445F6DBCE86CA803357D +749244A6FFD8FF8AD37EBAF3787F3702764C5EE2CA7547D7A9FED5AECDD2064F7C767078579DE13C +F135CB05561B15BD9803646611422353774984D386BAD640C5EED157569356A17BB6233EB298960B +8E34209562AE170A08D15F3A29967DE067F6AD183BA1EB49A99F4899031A01410D7311BB9B7A984E +BD6A303D44CF42B40F12769D44160583BCD68C68F823DDC0D73150083404B12AAA68E97206053C6D +23FF0620231D3570A089520E928E980F56A273092DF94EB3A99FBFD877B58860231B2A761DC91A41 +A427778B06F07D4F78924FF986F837C1437B44EAD5E7C56B9CE9CCFC0F6ABDBFDBDE4A32E3FFF336 +7F7194DA20D038CC44C4413B2CAC15C05B22758569D1008EA057DCDCF4A324C924021B35B10ED632 +BBE921BE2E34795951DDA394FABF3EDCEB99B3CA15D6579559F0BBECF8DF6E6DAE427DF75657AEDC +FE180A88DDA445A5A5E239B364B8884714B0ECE259F32F1742DBAC0BFA9A1052E2B14E632B56A474 +F2C9DCA9B5FD8D62A39227CA8C208DC69E5F543A745A913641950AE0DCCE02D20D374B652E2CC41B +F0417F99C2EFCE1C23204261FD1BCED5A1E8AD4736C5F23E14482D766390B1C62A320F751CA13454 +8DBA0B08E4BA0A0CA5F6DC765F9520D15D895792BE580133B92EF3691B95331DC76A551C4AE9AB10 +24D7EFC4A02B5654057224C3433A2AD6859E3E4C40F8F0B089C846002C75ABD667C68606D7300B7D +0569753AC074BE6943AD20018835A6EA28B99C983BE3BEA9B742A76F2D7A2A26B35664D24FFBF656 +EA28D525A78298C898C0BC2DDB56FA37151AF5A08B28226CE6BF09726C37F1B0BD39DB144CBB5494 +5DC48C374BA8716F6C9A4626C95B6993DB2CCD59A7430E3A3B2E6CCAB9A801760B8548C8447D129A +01EDF187435162EC13A65C943CE9EA547C3694F88F9706AF29F5357EE59500EC080E7FB844E8021D +907EE02C344DDCB78559AD7FDA31A3031D5CA80C004DBC04BE54B38752D49DFD19F1124C059ED68F +6E85E1A3A848F98707D7413ED3DEEEA823D958CCE720B85303CF208AEBB9B799653EBE4DD16186CB +F8C0301AAC6271EF9E2CF6732A6CB8548B7CAF2399287D6AEBD5ACC7C9D0DEB85BE38150072A0184 +51D3F1A8ECD784AF8149BF932E0422EDFC608B20B46D80D3EB68D746E1EF40423CD6FA218C9F569A +3442B0E70A2D464DC59CAEBC9607333D7B8FB90349676207AACEEE5ACE8E0E9F9C5896751ED4DA00 +95D68C421D8754D665D3D58C682AAB1DD72EF9050894EB680110C3E7F02C3551D73C63CDE8B45E5C +453BC9AC1FB3145CB6F0141B8E4928351FCE66F2A5AD534E5DD8BD901CEBFEB963DE2787095D7755 +81E588D3A165BD55B51F039992567B85FD3AE92C7526E33B44B8149E57BF7E57579E37611AA29DC5 +9EC94F583181201638BD4BBEEA23BB9EF067CFEC2E436D726F401EBA897480AEF7E38B9798C6CD42 +182C43B2BFCA7D8B6B696544F6B00C7B7D0D2C70D955304A4FC8D97E317C01113404129D480AF8E8 +EC0075A94859D5A79DF5F3FDC2EEF4F0BC1113D2C92DAB9859E9944DFAF557DF43AAF42B4FADE1BB +F5AD6728F884F0D4E7671787F1A3500B00E62929147C72FED37CC222EE991630EC9AF614160872D1 +BF4666DF3B682373AB1CE36FB87C42597FF1F96D3D7B07DC8755C2304AE69B955FD2547D316E16C0 +458BEEAD37B904BC27DE764240E67A68ED5FB57BA1F9C7C4C8F2BFF745F3E6FC732FD5E37CC1DED3 +6EDE20B06FD832673AC78DFB7655D7358CA03332A18241D96BB0A45D16BF2A2B80D0A68C74C8DAB3 +F18936EF73085EEACA9B24B18EB7DFFA75C707C947E17736EB7B608C4AB90ABB53A53F44D8661485 +5D60E36CA31704053CC453F2A50B878AFCE0361EC30444F5D6009ACB5D9673E5536B11A02B659252 +A64923E738F494D6264824392234FCED4D66E0342D03189778D41AEFD907272A919AAF40066A304C +6D7831F90B347CB8EACCAC6A726B40BE4671D6A0A591DC36A30ABBF91F9E780137D80EAD43BD49AF +690A3789F39D3EBFEA9CC64B82D601B88805B6FDAC3C84C61638DFF1E391DC74FE4D08A0683BC5D4 +E6634F82F4DA357742764FFB2B8D264275F82052921F7647BD8709857BB1C98C205D13EE51C14E9A +DAD1324562267D1931B5143A2ABD173C745B7272A6FECD532B5F189C8749DE0ECD3A6B1799C1834A +414554EA6972309C48DAB44A9DC41D8B28361E89CCE4DE8AD6058469D2F603E7AA62631E80C01535 +539580E124A24E9387E3E0E58A63AFB29944207BE5929455A150AA58E27EC885CCF019CABE1B8769 +0AA7FD1F4166DF820A324FA0FE3B59F8B767BFE029A7E3ECED513A6CC622AA8CE96563219EE328CE +BD649EE99E5F108FD57646926CBA30BE3AA8E00EB4CCA282AA35C0742410477E2E5771DAB74E4181 +D91DBCF25DF36BDBDFC5AB6C73A982A390416A23C8DA10655906878AF714C3478C8A0C7534F6022B +80925A069F63834539B18D9CBE67844520A195019C15F8F858E91CC50DE001EDB52C89F06035473A +022A718893BF7F6FC0B2E6CD4C1CB26E98C1A191EA5429BAE831F464971180F5EC2CC6E6F8F9EDB8 +2E2A7CA8C5656BFBDD618F7D31635A23C73F330EC76F4543C9795600F8EA45DF62BF4E071FFE3758 +2DADBF252F2A3EB399F61BEAE10BE0FEA537C272CE850026872F4BDFE5921245147B71DAFDC8EE88 +C509B6D8AC249557725FC5D14198A2DC889A4A9ED045870F767906A226826AC93FF1E09D75B4DF79 +8FD690C5146175EF2CBED8F03C9DEEBD95AABA69E98E25A98CC96820CF1C684F02E7739F525B12C2 +72613012143FC26919B800342903624AB29219E6266716E451C9D60A4FA010B8D26B56A4C91AE1C2 +ED778E35E66B49C4DE64021894C2B39E7C883518B06E04D198B7D056A24C3E65BC9E30BF2F73F2DE +21E676A37E2AFD625220831F598E93BCBE098AD73FB6EA5CBD9D17EFBE6EE35FE4EE93BD3A75A2F7 +118EACBCCB82216DF70F56C2E82F77F072093824C6ADB800C66F0F26BF7AE513A490AC3DCF648DF8 +2E54567ECB9D6FE66E908748B7D5090910EC99EB9B222E92B058F3EF34A11918D6FCDDBE2B9C27D7 +DB19AD215E7B327689E0597963E6EC907A23A0EBFCDF86ACDC349CD8983EE83144B5B8489545AE2D +ACCDC8E553FF4A1601F42CF82A90D571E36193BDF4A7893B2637DDC0C73EC0C21BDC4BE1D02BD257 +F4C240DD6AC53A321E13FD2EF4343B11B9C8152EC02EA6C4DBF7F66C9356647A948CA9E26672BD7F +9B828FE109600B1638806DBB95DA6AD0F78050FB13AA580139C7198B1673D4AF9BB610A83A709D3B +7D7B01AFFC0A740F03A5E2E3EB7AF145E3338D46D03A83FB82DD6487A52F9494A89717FB500A26AB +C949C51FE25DEE8F78E199AA53EC1DDF9D767D8FDA77FA32F19200BDC539F00A23DEF166D97F0DF6 +64489D932D115523CED7460212BB35D887FC3507C2592ECF622FEA49AE2F04299A547ACEF75EB0C8 +8ABDFA7A042D5EE4C88D716B00E79C40173A3F7044546D093B988463623DC1649FC6CD645D01E102 +1AAD11911190D0B07C0A66AE7F9F9CDCD0D841A976A8298C26A0850FF4FD42EDECC37B97A19F7240 +3413098A81025E4451850EAF180299667A392B7D2E96C4068CE110CC3CE15C6E58CBB11CE21A3E9D +FDC88ECF14A8F2D422E1CFCDDEA320DF5CAF93E6F9AFACBADCAEFBF542775D07EBF09A96F0162438 +62662AB782A464DC7A96BAC2B0F0F043E83690C3B24432F61293A1C5B3699605EEE8339AB079BA1B +A7C65ED392B6E94FF817CC25AD32E89C95A0667F124F26B11AF5B45A9AEDE4F443429ED30130D7C4 +68C940A7C538ACBDEEF77BC084F8A24FD0060BB9CC12A710DB9DF03CD381FB6E76F79D3DE40DEA4D +FEC56ECAADEAD68DF4492DBAE69EF1663E2CF90614871094BF6F0E1C9FA0EBB2D34923A19A557BE9 +54D914F35BA044FC800D822D88B5E70CAC27D6D56C66AD6CC3C7647DC679C8D3E1D39AA8282BCD27 +982428F5FAAB76EB16BCD26A1685C044E3C7B87B3A1685279DED690D76C0F1C52B76FD13C419165E +754BDD7FEA75E26DFE2B916DD0CD40301CCC945683C8E1F49A03A0DCE1974A76B754BF04D36C2693 +969FE4C6C39D60D995738F1DE0ED6A7E0B80B40BC14B440B6B8F1085E83995E224BFF4EEC6F67EAB +103B4BB6D21F9741932DFFBE85C0BA3D2AF925D670318D1157FACAE9C09B3AAB5B1FCFC889348207 +8D5A3F7787C699C420C9BF0883D3B8B3D7753E9A146175245CA9E2EE04FBE258B6E42334EF141A41 +D68ABA286864E72F0E4ADF41C1C96E60E69320E79211984A734392C870D72B8C236AD631672AB9F0 +FE48EF2611740799DF5B3339BD49697C4DFC0557C1022AAF15C24FDC54FBDEE2129EC70473A17EEF +D202EE43A1B5C7B78A299B6EC8BC7595FDA6BD0BD22E025E8FFD89115448D99FD27BAEB680A22C59 +7295E33201199E9E1E38AF099926344D1B7CA626166CFFBA2F3D1C15AD63F0C6035A5B9BC5AD644B +3D5636C2FF3B9403AFFC3AF0460B328C390D3B416C7880A0DFF10BF512BBB933081FAF4B2E06C093 +E80950F08BDEF07D56BD042433CB0A0C72E1F17692C9F5C7AA97C31AFEFA39233C62D5430F11DD31 +478E86A36A8AD3817B0AB397C8D6935960D905994ECD2AA8299D248AA274AE0FD1F1377F0443B37E +67DE63151184DB5EDDB4DEB9CCAC76896BEBE32E30E0F3C2557429FBD4B85ADE7829428C5CC95CBE +018C17BF38FE4B26B0AB736FEF35F6E3DACF0BEBB3B795D7982075B75D87324AC28D7E5B446F04F1 +0A001FF191A5FDD10B4729E57578FC42C26D473F22C70D5629AE05FC33E50A4EBA2C5D4D63B1D147 +9ED7B8FD7A0D824413D06437118C788543A21520653572608F9172CB1D1AC529280AADAEBB5A4E30 +AF99A58EDF2952BEEA29F366FB6FE7A804DFB1D116B73B45033E9E7E9767A9F41F2FAA76F97411D6 +420FB211B4BECF6C785FFEEBD90AB932E82EB0AEC7ABFA4A7AEE53E2482617576EB28BB2A3C7044E +15F0B6521F3B073021C3CE55890951E041EFA38937F2C0683BAD1AF255CF3747AF2F0B8A92BBE44D +88F7768D35B8F4EAEF0AADA3A42E26E3E3EC25E286C40808665B80C6265716DEEFAE3A86C9B54D34 +74285F3BA2946302A065B800EC7A9694B0B715BC86D3EEB649FAB8A47D170550D9213E8B8E9367CD +FC8527955263AB2AA55FB7ADB7DA9A4E727E3E69D9C7946369CC078DD7751DCEA1C0601C57F4B5E4 +48BAD7F5F8A919632178C77B7B5F95E402DD808AD59EDC020D82399DBD3A9D9F3FD089B0909C171A +940673E361F5728A89DB3E2CD0AE2009A6D64FD85ACEF62F8B42F009BBE11EA0AC14525C2ED72E03 +0DDF4F670D18B94C07C027509F281F7B59D854A5E46F4DC544BB320C427642D84B914A2E5043B1B6 +FC94802BE925FF260775F6D17A5C55C5A5D5931F88E3D7E8485D8026545CDED1DC96D1ED7E0088CA +ECBFEB99F110A5CCDF7EF3B4190F3DA6ADCD5A46DB27C0F076B558667A1B9ED967363799307E77B0 +9859909B4E86A27639DF3A8C0B193502FD22C0298AE0398E7D33C3039D7878922AA9997C93F29655 +5F5D7BF3A34D46BA5592FE2DAC5A32DD9945852962457B81DE4B6B9050660EEE1A0D95785D824A5B +DEABACAC8A8A90E7303093F0DFE57ACDF0EF2843DD7497B1B80AE7E883735D9BD689C84A61DE8127 +3E2DCA2F64B00B62F0FA3D3B5359241525434847763059927565F4D98CB8AD1842E31488E4C1DC58 +4BEEAFFE1D3E76AA2E6C81CE2DA9F00DD32841412695C8EE17EA60499E99B97D30C32DDB0B9E473C +E65C259949129A4682DDE5DEAC0611464650236934D7C57D1EF7E8B5D9E5D7458F0FCA9795853710 +F37B5C24E39D7EE92B2D4066D533A662AE2B063B741559B24AACF24DAB6FB6786F639ABD8B34C7E7 +AF20E5FC999BA74AD93CD821B545C2531C506719605A64FC06DA8907550087A4599EFA621DDFEC17 +B904B6115BF94AAFDC56F3570065D75DADA1AB177F4C333A04A0119A89BD209DB0CDBC5DA0C8B99F +EFF54B2F4FB4BF95AC573EBE6D5CC8110E6387365CCECA5630F5105C887DD5803DC1376986456634 +C3B3BBC235A72AF168CD5B350E0A8BBC303A2CFC37FF627F4697C0D19BEAE28FC3996E967CEAC4FC +8D9D309E2FA65172E899285BAD3F8B45B38C9C2BCE94C31911079850A040C08789EE377B9E652A10 +01EE4F44420757358E183D48EED67C3008E6F05C3971C732B98ABC24271527B267D8B10735CB1FBE +773E33FA51B5B472E402676E3590C7BE28BFFDE77AC34544718A20833C9891A176AA3A62D686E072 +7AB2150A1E77FAD5012D0299593B0222CA38CED2B9953B1E5893F176132F1197609D04F2F1D647B6 +F44B2EB0AD94211733F226B570E9D963AF9A6DF04FDFA26C0BDF31EDC41DA521F9D0090A9FA5DD13 +B9D977329F6412815A8C52C3290DD42EDBD312592DACBE0BFDEA209F389DE8E4B5D8ED51B46F1557 +C2B50098C2262D3DB298E12C0AC3E03B82CD2807CE04E109ADD00EB181D701E4BC3622DE8A86E977 +3D6C4AEB830F3283BCCEA663AFAB740B546C3577E9182EFE660AB380F0504590CEEC89313A608A29 +9F9DFFE22DA6296EA3E39857D7229885C78F097E7E7845E6C908A0570D4ED0AE320DFADB7AF87E5D +F85AFCD1B271B672813C0A0E2EFBAC5275807ACD3A2F09EAB95DE6F571E3F3C827FB3EA9DE89DEB5 +4B8B14770305B23EDE569571D0BB8BAF4811E12A0DD5BA4809818D2FE088DC1CD4BE72EECB36C819 +AC25B41BADFA27D5839D548CEED7DD18F5F2BF69EFCAC0ECD4FD942995E930C147E1A7AD93628180 +E62F20F3779824324C5A1C35ECEF68DE30BF5DFDB5DDEBF66CBC459B2C7FBCF0ADC0274D137BE67E +B22FA117C85CF7D52BBBB4CA5F9A6F73AFC23BF2E23B4B1EEACD33DAA3187F1D104843876EB44322 +67EDDDED02B6A507D13E3B9F012FCB8C9F0D14D951415BCFB20B3211B5B9860B0C6826BE597F2F9C +94DA2788E65107C5CC77CE1265E33DDE9378AF9019E7E68522997470A52088253FDCA09AF9F071C2 +988CEBDB46B7F7C8D08B5467A1B3FA0EFC552C5E20D4B9D565AFEF9B520FA2F544E802EB8E8E0C76 +21FF63F4A5C0C59F94E9E1731D8F3C80C772805DE264C7501E705BB21EC37A1379BEF8B8A9E50EB5 +6FE9CF9C10C9D25CBDC68124D2133B2DB6348175537EF872CCB8B1787330A5ACFEA87E2BE6615DFE +442EC74BBED30021A0437ED3E9DBA6EC49A824F0374B446271DE6E1B16AC74816F6216BAB7329725 +8CFBA83C178F5EC009C57404391898BCC3314411F129F12D68218AB6D0BCCE2E5AA9AA1D5FEE1E2F +0AFAA4BCC3D226C5512B456CA8F28DE54858F18DE2B30AB4FA02840859988BDE7ECECB4EB0002523 +C6EC40BAC2E7ABC411329F803DE2DCE1EEB354E4E6771E4328ADEE4E713AD248BF4F91108C52B169 +140F33D5C56F1EA2240E7E182C060187E29020139C373B4A6CC4D2156F7C15590D3C07C98535853F +4DF901EA9F2C66510C190D9456EE037DCDAA428D433CC2231B2B627CE2B5304B6A4630576BC48984 +66D7A8BB75D53ECA10C74D434A4E50B1A706ED6C7BA274A9CAD5D929B9BB8A631825A9C32A8F468D +578507DF2291DEAB6338ECC92CE8664D4B1C211A4CCE186679B6C71ACD5655B97ED8E552B09C1C85 +387749406C549057DEFC059CC85639203160B8FF05A48F7D5C4F452B111891846A521674C0E2734D +50B8C7E7B5D9F438C58DB139A6509DE3495388E0D7AD24F64FE73707C7BFB8CB06FA0E0C41346B98 +220E007E28515428C1874AC996819F16CB152C16F89CCDB3F9C83070AD90337F1823AC0A48B72749 +C6C29A8FDE1EC2E76B0D29FF711891EC81D0ED0B3349E9FDC413047731D70C33E57D2C4B637C8FCA +B027CADCB3E11F94F61CF3A56E4D90E8550F456BB90638DB6118229C9B74C9533508F343C0EB422F +87627EFB562C7730E1A804E3E4DC80FC0F199CE210045ECB1E3313C3364F78A500A8ACBFCAE0F7D6 +56FDC8B1BB95262A32ED7562A62EE5CF989235D1E641D726F34D215242D44A946662EE94E765A3C8 +75557732FB4DE1CC2699202802D4A5D99C621478C1C6D583FEE8CBDAF54C73C8C17BC73F1B414EEA +BD901409B83E98D62749F9E742FCA7C632C176D323E72FAB241D143950B87AFCA5B7B75936FC6638 +1FD0E537C30D744E970A08636D27AE7A277F3838BEB7D1BF821F331D483FCEE4EF9FF98F350B5B3E +CF2D6A5BBE95862FD5DEA6D3A5322DE6723B5201FF81EB1D1BC5BD0F77CC1737837655BE23E0662E +AFDAB16BC0635F40DA516BEBA8128C414D6EB5F5AF5F98B8C0129606FCF536181E7A8ECA563BBFDF +0AC65F1932F1DF20DDD6739F7B1EFEFFE282FB6DF35222E8148FB5968BC7E40A582D7B146E374270 +D3D014849E898E91997288BE44220B6B6E431B7AE3F2F7C5BF3E3444F5088F9F42B7F676EA14671B +C70A84B77BC275E82516040F7B9DDC582C0FE335852A83C31BE3B3F716F17253AE57372D14951A2B +58F69C2DF7B93052823311E4208A72C10D0625869BC5F3808D231E86CD259824D7E6C7669013CC55 +B61E4C20C0041C35BBD7F1C291EE7A3CAE016A8C90C56F22C515375252FC3E188B80940816EA5117 +88A2FC7AEEEEDAB9E0A33F2C82D886F9BE029BFA1348DAD16874751460DC149CAB5189D061E7C804 +1939D80A03BB529E3580A235F8C37EE25C503BECB9C31CB91904BFF4460837A1165A7474C83B5773 +5945BE04F3FAC3381310E4BEF8D8D4A7343B75D23BEFC58060C48BCEB52566A021C197ADCE5FA025 +1AD31CF2F90CF6A72122C38FEEACE6BE36176B1A990DBC42A33F0BC0090799C2F6C8AE6990725562 +B07725B3DD15C9011205C28700DF82AE4F00F9842DDEA3BB5C15D3A4CDCD18E692427505D7B24CEB +40CD7AE0D81A4C83A0F9ED579F924FCB19D9D017E60C6500CC64572E0161EBA57EBC11A5932F24FE +9F1AF444B3C657AD162BD39682D23D6355EF5D84440A124138CEAC85C926BDF484AD7B976D9A44AC +6015C25354DCD72A889474F31B8BD3CB7371B95A9729FF0E46EA3644477AA4C11FF5913D917B7742 +065F0593482B6E6EEC9EE63633A6A963819F3E6A2920136B17C982353F1F071B3D585DD24B22DE9E +EFB2B34F776DA85F85E9E107D16960AD67046476ADEC509FCFC64E8AAA6427935FC231C817A21C71 +6DCCE088EA4186DFF0A3A7A1C367615B8E7462DA176C9F9EA893DD04E29DFBF88679991AAB79A172 +48C06E2BCF3B91C108E09331FB57D94BE85EDCC66DA8225FF4B38E12F8563398E62622EBD2EAB098 +691EDED4D5D7AFC360AD6F263C043DAF483DA05CF70DD8BA8F96D2A5A87043DFACFBBBB3F8A378E2 +A896897DD48D8B13888A023AE6AD778DE5FA991907E731E5C5B0A3DABDC360D77CC71C59C34506C6 +39C21BA9FF7A9CF322C21B457D62F20448BA19CB765C4C0AE7CAD9A972F60ED60A21C92AB665537F +EAC8738AF8A27D3946F759162731F62C0A657CED40C66B8A9941EC2559074279CE0F6E10BE3F44C1 +D517E10D85EDA6BB6D097F4DF55A3DB7D50679675A781FA1FFFD6F1A8349B2870C8114A05F5F7645 +3B38446D57ED63FF8731661F0FEA79033E4C8B5CFA29CEC43355780C5E2EE86CCD449577EBDC0140 +47AA5CEC980CEA8200867212DDDAC234BEFC9FDFFDB43DD32F44883EC6F2963D4D28171E19E144FE +1BE2B8FCE99016691A00E4EA594F94E973E899D14DC44486BA6B4278DCB292FA5C7E6D73068A3BA5 +1557C3F072547E7F2F869D4E9AC03514276EBDB0920FA04E67E2934A250B1A502A8D06A25037CE59 +920D0E136C02D5DDEE2EDBE31A38BA32C4122AF89F295ADDF579FBFC72391283DC1914E9322A63E9 +44A280ABE7CEFA54ED2EF42B79FA97EF21EE83EF20CE34850FB66C378EC7C08B2BAB924F58FB8123 +FEFB43A385BC1EB922AD8360750FC1B0D8A303AD19286308B7A39A5086A50A8FDF7D60188342A9F6 +42286540945790524174800F8C44ED71306ACC3437FB49D8FC43FBF8E88103A76B4A92D95DB9B45A +FA067E31EAEFE6BC818D11D7CB8566BAB418B596A7494FC7326AB3CC029D010917B305CE585B194B +C5415088BC7AC7852A6D52EABD223E2634DCB29080217F6755B023C591F08C7E5D72267664136639 +9766EDF511FA744675D473AB37BED0ABD92E04049AA9008014C5EEC1B0443C6C86302CF6A3C38BDC +B8D9E0538E349BD930707DFB002700B5EB427192AD6105E01B8C2FE488CF617E9EACE73EFE3BAFD0 +F87CB57E02AD31627E6439188006655D6B992B393F4858254C2106FE9A3F0EF7347347C66C94A999 +D49527A2A177EE20A08BF594FF1E08CB091D22A8C1569320EB5145FE4A2674151750620B5EEC822F +A48C5F8B565DE0E18AD05F99827FF24D3BE03B007774F25284844D0F1F8F95643646960F303A831D +4C2C4ED9E0664C1D705137C157671D62179B47FA4A6441DAE99C2F7C8765C4C931EF345CD8F92D11 +E290B2FDEB1B0FAB4BA661A4511F75768808AC1DF2FE79BC285B976D364ED25C64EDEE62E8E035A4 +B79344D55B1E7E2B43AF1CF94ABFC8D0CA5E89240EEA231464449B831F1B3F9EF01AD07B38F0B402 +712A0346892A1DBBBCDBCC827220CE7F492CD7471FAD35B43E71CA14F1F1A9CEA740C4E1A98337B1 +5EB97B10AD57DAA9E9ED134CBB4614321D548C6A71D8ED95900E7947A7E4331D7DF3EF367F6DE8F3 +113A7DDBEA0F741F9C189B8B586B83671475A492AAF9994D884FEF3A646ED4F272668DDDB05EA230 +399223AE63088D636AE6AC7CE2DA06CA6AADE9272FECE86D0EEA8290B927B17450DA6F34A3D566E2 +096300CD8D5A34139489228ECFEB104714FF907A6E1D3DE35BC0FCCF45A2781AFC5562CCDB627E06 +F23DDBEBD4212F36C332C4A5A9498032213DA7C3FD03FC4832D1F2AC9EFEE3B840BF8356A16E14CE +989C37E6234CAC7A215DAE7C4DE2E2B6D9A876F709422113B503556A4BBD0ADC107B6B639F1BAC9C +6FAC7F4092D23C04EB8684C9D0A5F184160CB660CF6E8672ABE1AFA596EA86890DD22D0A406B2118 +9BB626943F378227132475A25710B75E5B3BB2ED7DC0412A0A079E2AC311ED55AD8E7B2A1A55FC84 +E62D61398511B70877F3DDDFEE5D033A9ACB66899021951378EAFB9E9A799AC9686FBC2E9E9B3A51 +6DA9DECFDE87FA4BC042A5D26C2117E29AEE8840B18361B7B38B21493401E931B431EAB1A3371628 +EF13BE5A0C64A3B9F8B6A29D209884706D2A9AC85B86E3839706269366EC7E53F31BA4219F741E55 +21E42F0A10B7B29E839B924AD90861FDFE3A3D446D1E32C06E66A5D7187A63E590000D1356718648 +7C99BC31088356C9ED29BD80C61E442B81E15657AB191E90EE77A6804C762C45D3C1937EA17454CB +D76D781A0C96A0914B9DE3D3984BAF6075D3A25AC69BEB34CC413D26C39824B83F853DF864C269F6 +8443970933FEF93208BAE4DB3BBE90DD3CABF6EAAD2F6CE664CFA05999FB1CC406A3502DAA6C8145 +3C69CD218B18BF0B9654FC3637EBAB8007A2EEA6ABDD11FB338E89FEC84B344857804D028F849E44 +5982E294384A442390AD36D7C4182EDD7A05BE6744FCBD3BBE6FAF4796063CF42499F2DEC302AEA7 +B64FEF6DD74F278FB5C2897CFBB87FE03538B2739E25EDEB806142E0030F7413018EC1833840BF37 +269E21DDFF67B8059BDC83DE9C6461F3EC2BC2224D2585AFA2AABEFE7D3B2A899C3E08F00AF2A707 +55C145C6BFFAC8F96B54EE682C3C8CE01BB44EB574E1C721974F236E8A2AA28DF0A4AD285EC4DE58 +DBD3A2FAC8A20173AE84CBF877559ECCEC64D9448F8CA0FE5272BEA6543738D5EDD54B73AED3C4CD +172F91C4F70616B36D37B1252D355820DA88536C829B1542C1EF4F76375360E7123525744F55001E +71F5AB1E5B39A5248CCC6789647F1FAE5E989A8A8ECAA2A9116ED69DC9B9AC25A743F70AC4B62D72 +5F120D94431528EF8C74611F7529BE325AF84663989C219B70274584D1EC4485E11AEFFE4A29F534 +6912577C010FC753DB47204BFD9E504027B335EEBD952FD299E5562407234C5DBF0B839D1010AE10 +5D56766F87910E44AC7968842833A302882AB481FE0AD444911F7EB8555DBB9F3D062E9E9FF3B529 +4E9A3B3AED4AECE34C816BE7FBCDAEBA33FDC491C725F13801A6BE162F7F8E99491E8BF77F2D2010 +D9FDD78BA2606A28810CB5D88CE00218D49609C62E96BBD36110DD611C3573EA341FD2AF6FE0C932 +58565D4A6C88CDA3951E287168A478F5EE074BAA9676EF5911D8EDF8D66A7E8F3BED1BB786F480F4 +423C3D3CDED2E80AB76CF5CE5A53B092A44FE52806D8EA72033BD3E7BCF1DA9C870108F32D8987FC +25E7D873985C3C4882A5CDFE7A8B51F3754842FD6F1FF6235DBB88F04989BB2F2816A476B513C10F +AB6F81CF115EF5044D3366A4E192EF68D385B6B6F4BDA7D14D9627F40843035CA80DE49F9DB52CD8 +714F769DF97F73E3152E8DE34D8C37163D485750F8F4E37AE8A3C3BF54D97BC3B2978985B557F9E2 +9F0537CB743EBFA0B7864BBEA9C126F13C02CECFDA50A8019F90900F409B6D700CBB9EEE18FD1952 +D496DBFDBAC800FB83B37C705367F182D91B21C7F6C242D8735A75343C84DBFFE583337DE2A95890 +660584B513C5BEB7A0926BCA7B7DC3ED4D080CC3F1264A4215ACD35DCC62D896B4354F2F7254444A +7235E0E3D53D02583710DBFF2CD55AE5E61D25ED1B3C3B6708E5BB308A3D658F043C26B881C949C1 +0940AF6BDFD2DB0D544DE119BC7F7B03451E61FE18845000D350AD6D04A09D8E3E999E6DAC6AB73F +818A11EAD345EAED03BA083A6EEE7E9CA8CFB760FCBC88B8DBE0887F79AB430913604F15272F9C73 +DAD19D591B40CB7863414A8FAB21C41F80A4BB0A3AFD9D4B1322487429149470DE62F305906F1244 +2AE20521BE034F159A7E7EA211A2FC6193AF59CBDD4B43207BCDE8697DD515459F80F8EDB982C97C +05ED3996E03891DD7EEAD505F6A71A924CC1CEF29053ABC8F0B5F56D0DA1249F317406822C225863 +ECEED46BE0072EDDFDF5F63DC8E94FC119087A66E394A653D5AC774407B006B35C406E7EE4385565 +78290F8CB8B131B88BD78CB87A11CDA44C5A199BC71388A81F2F30E2C003094E793969673E8D0906 +1F4A3FAB9B14C52EC89BACA1C52703F000A967EEC445E5423D3BCB9253D91AA64BB26727C8461FA7 +FF61022B4C6A9E793901D3407487B4962A16B564CAC93D7AA28A22C28318F69770E12DF9D6CFFF17 +09EE0604890191217696AA52630231FE11153761310A72D60E6925AD6B9D63A66047F32B9425C91E +57505CFFE42A90185451297C2CAD408B0CA4F8E923EC26A3D5D66448550AFA3CE3BE9296C8149878 +F853F2A7C3B2E98899C8F9B47B1405F96D2B22E9F1CD62A2945AB62F67AB0297982809A829826BC0 +F24A7777508EE0A71BA7588663D8118E3BAC936A61FF4A628EE96C0B9AE05072A5A4307E68EF2C3E +97A46EB31A2A7D4A33CE9C44E1111D73D9D3C6A0F22F50D8C22153C2CBB5BE187C0F2F37708237D6 +FEBC445843CF88F4C3A249B39DC971CA003A78028F8A2CB3BFBD2C26CFE457A9561350AF93E60295 +D21E1C2024312B1F2F76F2EAEB822CA72412F860F5A87DF705C2F82E681AB9AF45A19023E02538B6 +9E0F273BB4811F07D153029BF988D3EB66358D987895B96B5EC4C24F3409C1EE1B5978B1EC8F3E2E +75FDF2BEDB0AF0CC66481FA98ADBA8DF4D8C8EB800F88C7776BACEE6F61095FC2CA891574F309E1C +0C27E37D8B03472DB3FB8D6668E286501118F7DADBF1106687AEC6DC4C162D22E3BD0BD42DBED782 +BA4707ED5181DAA9ACED58FB358DA0AA9B63AB4394D42360C3F5082A6F168CF41D381073A1D99CA3 +3BD97F62446D62059BF4A8616A8809147B739400362748EDCF39FFD2BF2C16D4632C06A5D43B48D5 +0D0B98E4449B4F1D39659DCCE8AC72A2E8F32487FF8686E550299F37A6353A8E558D4A8B074C2F5B +864DBC8FA3391E3B5135FC738E4C13868D67234489B6AE382792FD5214D5F9E5249AD65C433C22A4 +D63FA7A36A48A339F443AB39C23D2025741186C8B18AAE9952D41E25E930D6905B29F85A4589E9D3 +95FF040E3F72FBF29650D4DA2B6FA27F60DAC4169DC9764021F4E7E094FA25F6B5AF25602C593266 +ADED528EAE6C967E66F0BA05F258E34CF6AA5488D3F7406C2AA9D9FD9C533E827F26862D348B5223 +A690E40953F337352C8245F1861A19E5326490F2A9742917B5546884E8036DE0874363F812CF6FF9 +574DFD5BA77A1DD7C5778B2B9A2E22F2482744E2CE9323E590C60FF02412CC7AB0EC30D0EC857D27 +A4E3D8B35FD69FE28287B2160AC0BCF645A0654403D26B05041654B17D82928044BBAC40871BC3FD +D8EFC3207928BDE5D66926ED199017B223AAEBA563F2723AFFAB737F6482DD269F44ECCA3B32FA03 +FFCE3ED882B449BEE196F59E6616EF1A2F08B42B1A184727D5BD96DF83972BE1B5F8CF098F61B84A +B5BBCBF231E099CFC07D4748D43F129D123FD8051628564931E43A70BC09BF20AE2C0ABB009014AF +89753F91ACB574C5A218A47A02DA3AE44D3F688F9D96076AED9EDE7388B2935C01FD400BA7EC9574 +96E317C6931E3ED7078A53CB4CEE4E56311F4D8A368AE981606AD7E9DB0EA2A10E079476D9881596 +8C9675D9CF15E29B9328600FECCC02B484D8749B4154D69CF90997AD650D881735AA71289AE93FB5 +8C56686C1F9FB0E696CC0285A1A870373E5DCAC285A1B4FE903CC30B8ACAD2E4613873ED77D813D2 +9FA4984A7530F5B046A71011D97CDE4BAE9648AD54537A1D87A5AF1B92560DD7064F3EDD3CCFA7A4 +FBD6166D067945F103C13B3019540C3FE706804A7A00E1D28A0C26A40AC5A8845E39631A00099CE9 +88C3F12C8954EA84BF268D99E2E13726BF9C63474A5FC874CBF723BD48A5461177789E11B4F1CC63 +0FF4D60DE4F01422C1029E664782232A8BEFC38CD058135C79E015A55BA933AD8D446A81051D956E +429779863DBA60F4258DFC62D1DC3CC3862C5D310579417FEF4D7642ACBA8BA5284DC5833F581150 +ACF91709D6B3A41395960197AB43E63B8C6C2F745DEB4647E298341F3E3023D7DA22509340E28140 +97A0C193493711E60C5DA99C464D8DFC83A61291BD0E13AB6A42C81253C0F1B37E71E7706BF2D662 +76C60A3A7DECCC58E56524A200F8C6C0512FAD2FEB51F5C24109E284FADFFD8484A7A053E8EA544F +AFC1C7BA21E4824866090E542B8BB0BE603060A36D8A8CD64EC92D6037B438EA6A7F3A23E9608F29 +02E8DB10F2854D89E2CE6F093E6804299305BFF3D1601C2830F793B92CC2542A6CB0E9DA602E00FF +DEF234702D0DEFCD270F2984642F13B818F65BD407B61227A94AB11E7ACE8D40849808AB3A7E6EFE +B2D4F3B9FCD233B8497B35299DF28F651B0B2960B4545BF2C05952229EB1CF676DA761995413051E +885A529957538A8B2C6BBDCEDB2A3F4104004B880624C2F55B544EEE8DD29231386492598C2BE995 +6E7BA78FA75FAEBED43807FAF072839BAA02333D38BF4A59B1F3ACEA5C7BC188C0BE8A8BCF2069BB +E36BDB2203EAC19D249C4ACFBF8F9717111B7F4637D631D058EAE0CD4E4E8A9F0ACE1BE19B3A241C +9C6DCE2513690473D8F4A59E1A7FCC926885CC324086981FE0C6AADB8F48116822C59B1E93F53829 +77CC88B82FEEDD2B4CC6095691FFA1419D5A6850D3576C4E705C676850D0BCAB84D52791355DCC7D +70A7F5CF603B4D0D7F0CF4F25AEF44F21A2D627D45D306DE0BE1C794AB90DD1EECAB2D61A115D3D4 +AFAD1914E808B16BC868FD48208E1F915B0E8ECFD59AA5895CD7ACF95E0DCE87DD8B12F683C8EFD9 +625CD388337C262013626A06ED60556FA70A8D425D36B48E430758AB4CAD34A656F457A60E4DF933 +07C4D5B2698501E6D1F270CFF70E47DE38A5FCA2B04D7BEC3A2945EB93C49F1D366835581B42F43C +C99D71F13F45A9FF8D12E26C43E1B994BE5AB44E5309B0F936D78C93169D666DDE6D18E33A5E016F +32278897FD7E76BAFE39498C6A849F4A6D882D109C40B42488059554CC95530FBBAB2591DCFADED7 +3EE5F2F2BBF17F4A131B8126F9E0AEB4B379CEE4EAD92A1BB29E3789EC19671E77558B4DE961629F +28B49DB4D8FAAF541D23205844EAA801FEA468D26F32BF9CAC30BCA244246A55F600BFBB61C5E8C4 +10CA07319DD094770DFFC1CB700DF67097F61C46036353C8AB3A5E5198445A194BF189E20490E970 +7D2C03C1A003BC782A66841AE5DDEED2297BB6DD019C98A66A8F279748DA39C85CE2082D09210EAF +CA995591A1A3DCA52EFC9A752DAE0CFB125127DA2932AAAAD7E9850AD78D48304260B4C270EFF12B +160DEF2C2B8E30E8C137975AD20046F37A72F1355F31D878334D96313D330C24EE7350D4042AAE2F +A345EDCEB133121D4B39645B1D6114D0597C3301B4CA56DA2B4A457D7A51BD13B7AC61FE6E1CD451 +6253F606FD4B57E9F4895CAC93691EEBC2AC992CC5D122DD3FC6A9EC8FD337CD402F03F901CE7986 +2F4F4E4500617DA0F913E357BED3ED04F49FD61FD1C66606CB231C3B7A5042C7C07EAB2E02BE8CF6 +AEE5E16B4AB725B5FB5D01EAD4887E365BC2ACA579BD80E0AA686E4A08DFFC70F99132353E3D5898 +5205807271753BA3DB7264C4567DF5FE999514F28E1DD6D3E966D8810978B140F8DD9BB259078A47 +013BCF247C37F543C0899B532F34843CA56F18F688B42A12DDE2A90CA457860540B6FA138F753DAB +E7331188ED6F535480FBFE934F68EFB1C9C16D4F11EDB35F944EAE63751101928EDD0E7AFE64D7C8 +5E9CAFCBA88450DEF9122A245FC1ECCF9EF8DD94EE6A70CF16ECAB39B52597AB1E8C47B6DA4FF0DE +C7D0FABC84DDCC8C652DD7C941DB3FDCC5F0542A8F433AF9FDF4D393E123884E1DD5D359D46DAC61 +0694020BAE84B3BD4C068E3BC871BE21DA13571BB61387E207926769236776B5B31A4A462902966C +DC3D92BE171F10EF8395D2402F0C492A3FE55979CB903CFF2CD2319CE4B46481489E798A131635CB +2E70147193FE3C8F4570FD01BE10E004B17341C4DB8B029BCBBC45D31227A684E5F38F5D6F0821C3 +ED13D31DBCFF51BD759C84A98145FC86D82F871D2D83F43C3DD7FE9A064120338A7BC63A2C60667E +25B50EA1C267174B334F4437856295A6B826F54C3EA9ED39CA6909A0F6D9669F1E75A7A05CFBE7B4 +2C330668E311872177F0BE3A9B3EAE611EB48721AAC2F10C3CCD897CEA8A136E5E10C2337BC5EEB2 +FBC1A3646A6B6792CA3946BA5D4D135D929547042A2F0D0A202A4D86E3F7098C823E7AE4331CEC6B +607F6AA434180B4153F6B10DCC914A92D6D0934551BC9DDB3C78065B2177B264216AF5524D798AE6 +2A90D00A70CB36C5B9950499163C2C1B04339FA76D28E03A4D0C80FCF7BF29B8A188C67EAB6A4BE7 +8C07713C7EE09043301B5BFD60222DD0D0943180AFB286D2953A8A12661986A4812E2C0DE5B3E703 +DAB34AF0E9306A5711D286AD09A3C6AB80841491BD0E5A1D1ABE1D600CB494BC17EC4F74B45870F6 +AC41EFE16BB6C87F382DFA4B2B8DDC9C2E912FD139A2FFD5C92D836F3D749EEB985C62A376849751 +A6AC56B1F679A85FDAD9448DC7A4CFE323AFB540408547C53297DB6FE9A0B08901BC285997934A2A +1772090FBA175CF4660764B87A21A738519D5B619840D15F45DF3589E8B80A6DDF1F395B65345869 +58C7060DC131700DFF6E25962494583085E6F8BAEA557A5666E6634E4704D0C07BB0A2EB228A7BEF +EB890B4EEC638303B8005BCCE922CF3A7AE627206F2946A142B0095FF960BB8B8F9C975B6FF07479 +2D5C3DFA125B7BE7A8356D8B44E264AF6AA582DB84BA09D2FFDC2213903FE8FE16DE5EF61E518DE0 +6A29D98E217038A4CA4D219E4F114858CA493AEF0CD6495A7C5EF1ADA06AB543051B1A5213952D46 +648BE06D15B1728768BB853CE32943AB0988D172227780CE82D1A1D297D0D6ECC51B290E156645B6 +9BD54699940AB17EBE10EDF258BBA6BFEA39F4F0B066FF6B3FA16C7C72F2565CC028F249BAA4B488 +B48A2513DCC5D1E205FB874BCFE45612DF4EDAF815CDD53CBB80842B429B1AFA32D35EA58E17F4E7 +252C2C9A737AAECFC25FD8CA5520D3EA38AF71C61B88F31FB53A7F5369305D63495ADABA455C3C4C +35D9FE423A00CFBB278CD482D3FD33BEEAC1F359AD9B6AA17D60CC46FAB670ABFB3B2B4161B9EB9D +949A06CC3B734F63DE821FE0B8EA065B6F79C0601E46E8CEB6E2DE0C052771E0EC7012063F1AC46C +3FC454767469225AA266784DD77256C6FDE25D7D857FCEF2562AEBE38A9A47063AEF91449723E680 +B36D824BB95BE95C9802F7AD4DF1AF37B4777A1E116A7EEF770F0A499BC9F9675A775503091D4EBD +F55118782E3E54AEBC67A0545E8D75BA5A482C6CEC50595D45AF041664689AA4338C1428870CDC15 +BFEEF788C1D1E87B3389103225E2619120B129FDF048DF3F5DDAFF1CE87428E6BFA591B91D82720C +E4A72FBB952403435248657ECD5456CA814FA0ECBE70BC3D391AEA0A195AE8FBE92D054AD0F3E549 +841ACDCE59DCAA9DAC20348A1D05DF7B4127C75F4C6607EF8501F741CBC96AD8D429A4CA8034CA52 +9AB673EE706F1FF51DD93F45802031EEBF8F3AD29D2A74888B1CB344F88ACEB9B4DEA13ECAE0A3B7 +1FF5CAA38D0DC484B96E90251D56732B85DB8A8DBF10E0A5921121CFDFDB6EA9CA45B98584401C3F +346BD026DA2A17E75CEEB74E48667F9BF5C538623E88021F0C24EC7D082C3E1FFDE5A6F68F0F3F6D +2B61547C4C14614118F7942851B090D6C8D80BA06D9139CB5AEB0EF77DEF2376C0BF602FB1083178 +27E86DDCC00622438FD7A268C0252CA2E20DA887A9D5ED251FD5CCE811E514D702206E413DEE7528 +3A6AEF793F5E3E3B0B10E59226102DF2A7347A95B96E39C6B6ABD1FB4B7CDC0813D7390E2819EE50 +7E5458E06D43CC3BF16E40CEC4E64909615CBFCD233B5D316BC8B8B46243CDB7F2FBECA2F208D0A7 +985048E88A5E685A9EE8C3A351FFCFB522C1EE41A8E4EF0A080C0D110D2A0D8A980AF1F604D1CD52 +3CF41D08A45EB809AF11F530C138F256FFD49BC20F77005D004AC85FD563E8E8BA5F79CC60FDE714 +1416F4C14FFF281ECE2BE1D167B7B1ECE17790454F795B056CE4DF1D08F25F0E5E2A16CFA066AF97 +F3A80015068FA2A1D65F329A5CF7856114A77EE7295C7929CA27796D8C51B31347CBC7C4EEF1E75D +12237C558BE5B65DCAA4CDDCB419E543B1785DF8B225C2E9778839123E6F85E53D925C6D2FC0327A +EF9CA13D1F2F9B3E510A1A4190D3057227621788AFB6567132238BE86559BCA006B85AFBB8AFE5D7 +A8CE58AF731F2550108C6B8CD63632184B1E218F01881CB94CEBFFC310F263565A39C83C874AE474 +AA213F7DD9CB3B8446A3E47E81BACF7B16E4A80899BF2D30C28F9DE0678DF364589DA6A454398E30 +21A92AD1D8B0DC494D96A06C7568E34547F0E68829E5D15F6500ECD7403B40B5CC2A479D0F8CE3E8 +709861608AED046339BE055BAED1A6BD2311CF918E74378D893710D42A769671326E947F108A38C2 +3F92EF5C6A50EFE36E858671F17151C719B9C7D60E8E429F088616D616080DC676760BE83A3F5229 +6C4B13CA3818E3FEB56D51DB5979F28063F7D537C15493D57CAA1E55438C01C794336D9F21520484 +FE5B5BAEAD7121E3CCC5086E2F2191DBEE9527CA61CE85CC0A4D99EA29169B17A10B3D4372EA493D +48CF572E570CF48B3B7512268A9AC4CC161F97695D19412C36F1D67522E272B1C18B6122A355B43C +37636E71462503C43C1AEF5ACE34A6442075B59892BA54E9B5A99B9733BAF64161450F8ECF4FB2B2 +DA6F9AAEBC827CD9AEF94BB463935C26E1DD5DDCA5BA87E0EFB62D9D3226DC28427EC097B68C9EE1 +E47D125F884F8656C8AD55D625A8F7F1A306383A5036A12E63B6FAC4A215AC88725611EBA74B6770 +B20F45356879DB807E9C9105D14C83104B2DCB4F274A2F37152425935B3F2D1633E4444540F7411B +24C17055850C9528D912F9DD40984CA1B3BE277F82198BC99B80233B8AAB674A44B42B9123E886B7 +B3161D48B4AA24970BB75A983569690BFD4B92E6ECB410DDCA14A3BF59057397FF1AF159BE230216 +FF2F7009C3A3DDD81F2BD1F5C10C2C2F30A8095D7371427AC9D527AC6794CF8593303F786F21CCBE +36DFBE01B0ADD78DB3C9483D9DB09A9E2577A3A4296E7BD09C41F5C91B29C9D7E69EF06B5BDB66E0 +853C3DB133769BC8858121A350CADC1D61C77C1A313833D077938AB2E10EF970590256479B1549B0 +401A9C5E97F12A269A305B372F0EFC035B06AE3E2187D6E3A62DE3B4E8075F0ECF5BE32A5B97A4AC +23A7335C4AB29917129C08250DBD1D5BC123C92E821BBB1A7598E32DDDF1876C6FBB7B858FD317D7 +6022955595338AAD2BFB3B17DA11380E7473A5F566C6329E0F38D9A67E68FD4B8CFCC82FC0BD7BA3 +9A25D5BD0952CC71CF0BD7B5EE184A8E62FBB366D95AB051BC3A80E9F6C27EC100E00C4A002291B0 +4D758A9683657FCB07A2AA8B07DF585CD4205B4DC55330E8A2A367CBE966E84618ACFCAAF81C8A9D +3A3165FE5A623720A0C11CBDD5CA200100DD2C970A09B124D98987D6A2CA7CBB40DA5B585B86FCB3 +8E96F54C64A921D7A5D2F805C67339DC4FC026FB2EE533BB9700F66F4AD58949B3DB75D770C4D7C5 +C23DC720D78124A7ABF30CDFF10C708219903431C7B1A19317341CEA22AA11A588F8AF9BFA4AAC40 +31EF22F13BA597BD1E5958CEA741274197113CB0B7975424C17F73733ED30BDC0170CD0084BB7DC1 +8F031BEF2A6C44D0EA0EBB84E31D5B4C50B63DF0AF96AAF33E3C986D4B82D161E2F9049DE9710EF8 +A11BA0E08266A3547F20567E582EDB47633772D37FCCD119AF5B11F338098F185D5B055975F56092 +CC6979FF49487D69E08A4E8754FD51D29410F56E0F3EFB9C3B86253B3B0A39E9D217604F664CC696 +634D216524F976D066034DDE8EDF5E8F9644337C50C7D8E2230B38E0EE88C53DEEDB3363477E32CB +88B3ACB6931B9717FC93F8D1A9DDDA3A7D8A56783F2B651A1DE216493C34DCD17A7635D21CEC0770 +751424B599B00CE950E60D8A8B3444F1629220DF073137E2ADE1B6243C1C69BD5E10450B948BFCBA +46A841B98BCE7C86E120789C73C87E581868021E68F78CB903AA6D494331174CBF944221DEA9E567 +A2542BD399C5E471D972FAB7E8950AEFF3D91A1296E685B0BFF91367C2703DCAB10AB236DE603E3E +993C4C9020EEBFE89726AB9FB92AF9D371D7188448CAB9FD2704506C39F0842E36EB8B9AAFB1F26F +CD366E66725AD28B19FA57537757EC71B04B451F056C3AEBFA04D1C0CABC9492B6B8D239EEC36776 +D6B515ED43A66AB4342BE4C8B2027F2D008EA231A24B472DA8352A05DDEE31CBC3577E5814DD2D4E +C17A216A7C8FFB27012346C9ED12F7819B96A5B135B6196E888C9AC73D7D4B7DC370E2FBFA17FD74 +0BF516CFF69294493900BFD63721A537BEFB31C5262567103F9CEA4E8DB02964B983E1AF190ECCEF +41F552C45E9B94E29AE3F129687EC35719F591C1987A08DCED3B822344DF81A70AE78E14A81EF1A1 +A77FD5D19F7D35B7C12A473EE11C655E15DC5D3C94F226CBDA85377338BFCEA18359176CA7EED622 +84F9015F2D592E27F1037C95570CFDCA3B9B90D35DB8C341434BBF04C0E692D4C2C3F59EF386CD1C +5A8C783198EBE42C89E3B64C662F7875325982E9299C18DBD1FD2257DD964F9F9DDEEBFE56E4AE9E +8163E8C58581BFD5818BF396CCDE3A66F58E17BDD262D63E5D9AE0D0E43C304CCAC4776B87D40F6B +5AD7E625B2065FAABA81AAF2E2D32E0ACAB12DB2A9FE9C6160FBA7DE5AF019810AD9C64B2E6567B0 +F0108E8EDEC635F4BA88BDEBD3561CDBBB9063B4D19C493E0CA4F551255A7DF6BCE96C17A5DD4877 +7F654F4F114CF29ECDB9779F63EECDC04E1FA06E48701547A8F96483B4195C0DE90B0FD1B95B5F12 +5C43BB973D0B6414A994595359289B5FFEE4A08D684C2E7E3F417585581318D54DF2EEAE7CF36E2C +A1A70EA76154B88C1E4FFDF5EBC28ABE264B40583F2DE1F761DC25DF242396AE502CC63EFD6EE284 +DB37B0DF36A8833ED3EA5A1DB372C8E1F3203E6E945A53D83B87250CD72C851F68A602E7BCF92DDC +E600D518069F8900F3BCA434D21694229572D55F307E0FAFA3DAEA864BAA19F227097963CE83882D +22FB7F1AA3FE558A3669BB7FA37C0A591B4BF3E2A2FF85BFEDE5E424E0AE337EABDFD67BBF38E160 +09A687D2E7C1882DB79304D722EACA233EFA489B10E8D4D404D12A5A4683FDE377A9A952CCAB257E +ABB408D75FF130206DC3447F26E74D1D00354313E340878917177DBD2AA5A2A70E2087D551DA5181 +6FAD07F6E826DB0BF21560BB26906D3100E12AA3801D4A8BDCBF1A0A8D58F1054EE2A0DB04ADDF3F +EAF1FD6E322BBEE274A4995EFFEAAD0F24D9FB45730F6BA0F42D88BE4AEBE9777F8AF508DED61024 +EF955B26ACEC51AF5C21BAD7AD93CC0C9DBE03C0FE9637A3099E5EF329051C87FFF70042D788F21F +4DE645FEBF58374F7E38A9AFE3DE4D2888DD807A09169DA8516DFE37591C122A4798343C1A8121C7 +430F244EE5B19A21897A4423E21CE0492E75C9320E37BD65F1EACA7FC6FE032842E4D985E666E633 +AE5CC62B3449404672B284EA5C6A01E927787104ECCE1354D1C0E5AA2452B7B12937B946B74EFA98 +8C5C79EEE5ACDCFC994CAA853AFA08EB9E180C5E898FDDF8903EB0863E98A4FA537207C154CE5B98 +EF4E97FB6A1CBB07C45D34221EED26998EC864475D231FF76E830A7C0900C4B5FCB980E3F67F4EAE +3DD8086CFA1874E65C424646C7A8F84272DAB04A5984FCE3F4B89380B3A5C6F04EA3B683CC224913 +9209136336AC9349AA89690B5DE0D93E92F996BA7E346B043EBD45A35FA297DB70E9A6250B138674 +FDDFA609BE11EBA8106EEE3923B4E2657CF7A82443C25022A5DED862E8E3A5BFA7B8AC1C1B804B23 +C08A7D8FE594140E8432C1A5E183FE361B6B8C763054458798B756A6CD2535FF4667C634DF78AFBC +C87D40DCD56FFFA12CFC6EDC6A8AF41AF657E0137320624F1895D9AA84AABAC5F3653E977B054C52 +92B06FEAAAC0E5C7FD45B6654D0D9CCDF292A8489E1E6CD929F0AF25B9604CE33BD25662133CEE99 +CDBBD00F865431193767480700CA019D694B38D695CC29F732B5B39B6CE654F1AA18AC3223B60E55 +3FC3C3E114E6A2E59617BFD58AD8E429200B59E035DB7D67C8DC6C72001D005924670ECF7A06FF5F +7B3DD5556DDDE1BE5163F7CD6FC969033EBBAD85856522ECA91F2A200A75BDDA69070240D86C2D7D +959F1DBE45D488A96F308269E8262327A410D057446F596418DF4A12267A7A3FF5ADEA0846896D3A +EFE3D5C72387F7EEEE809BCCD23D1126B86C45A0F66404FAF617D03379A2E44865051E92B4C835F7 +21492147DE3DCCE47EAD192D3F10A5DD459634D6C253F4D09DD98E7DDC836EED8DE08A78A6F6FB93 +84565CF6DA8D4C948144272D5F8787643B9B550F96E42A8CFC0F1C1F2B14D83201199E9E1F5BC0E6 +51E39E16A744930F4409C61CDF9F456C7EAC5C62BD22CE0DDCCDF3755DC670FDC8FEBF09F241BF95 +1A1694BD70B9695A15653F719DF4374AFC6C27F58DD144253BDF86FC1BB3D4FEAEEB9196BCEC7168 +AF1488AB7072751FA24C6642B87DCADF4B2427631E7D7FA39D126410189CD8E24CF0A490FDC7045A +A9A83D02CAB57B9C27211FE1805080FBCEF23B86CDB19885A574C589BDB15C4CB3651FA3D2D6B619 +4449B16D927047E376A84F9423562CD548D192AD0A57E0D5B41010D1C7D87929DB6F456B0A073DF9 +A8220BEB3D4DA01518B5C85C82ABCC821BEEC1965283DABF6AF3AC55ABDDAEF1B53E6119DBB4A114 +44FB1769F9891B5DC931ADFEC8ECEFFA179427FCB08C1B149EDA70717C5E9322F3CE9405438481F3 +90968A38061EDAA2223147D6143877E4C4E645EC0A9803A933116D610491FDC639DF69B418156772 +E14A428864982D3230EFFAA1D2D3388B0F70D8FE202CDD6CB0E97B3290ECCF4A1A69D93C11D3D735 +FF0B5A9A161A86CA4F16B7C231B8BF71D5083697B2F24C0BDAA6E83E5DD2222DA248D4ED3B5509CC +7CA40D8D5A2507A1D40FC1AE9486343CA2B2C65B6CED9B1F4C0535ADF7BECCE45E1DB74C7417B06D +897C6ECDD05F068857D931B3D960103628675BE2FB82DA774ED3786670D8C2BA4ECC36C203C7F7E0 +9C97A09E3B2E2C3EC30B53A8825519B664D09F149AB73F183A4A22C8F20E2052F76649AB881BA28C +6C3C3950EA9B3961F87F6AEFF24DD7B4E10DE28C15DCB27F593F98CBC9CDE19360C5531B2A0C6DC1 +8FAE2832AF49BF9522C068F3036516F711AADEE496EE4E3EFA2F55EA4891DEC6D8A868E0B0017076 +3040CA4D42A73A71057DFE315FF8625BBD74F41E5CA77B7DF096C2F4F0D51B1184CA9F2C8517FCB3 +956C44873D630E94EAA1D2D1451B332770172B2D7A21ECAC864C612235C0B39ADF55EF074F2073FA +D3B54A66B07AEA1FB495A9F7269BD1C07692B0B762DA6881EE6B265ACED0BF0794C0397F8D8B13FF +275BE77561067E2C1FA536131184682BFD32384C6488012D29E8E55838E5A5AA7C40C20AB6B03BFD +BF4EFD2001A612100D585BAC1C77C1CB1ADAF2B000E09686091AB7D1AB6ADE395A03580BD78E961D +14D052E7D4A61227534B55FACAFC4E8F9326DE35BA53A463F7D94B705698300771185DB19E78FABC +D0AE4FCE005B79C795B692F2D9C00B6A61D4B343C35E417BBA169EE82A2E4AE204A126B08A94191C +6E5B5E8328A147BD6ED5A5AD5C9143A50C47789DCFA699720336AFFBD6B1646D8C35139B0DC34340 +C76C7E4FD72DABF80BF64BA04742D07B380E20A678EBCB19057F2346FEC653F1302992279DCCD2E8 +902B2B8F78435AF3400DAE319E94E3148BA88C056701D524DD67FF368C85EC6366F31689A62FFC03 +4BE436588ECBB8B41E8C43112F3B65F50D20A5EC51A26FE899E74731B01C7771F75B76070DC0B223 +1845BE9C09670E65C4DF54C0FB36511B735251AEEBD13FDC0FCFC3134D8DA826E4100521CFEAB202 +B83267EB3F69AEEA25FDE1E9C86407E38AB4CA2D1B91607EFC96DDC5BB10FCD46DEAA5FFED196959 +12302ED55DFACFDC6F22C1528AE3A9902B75F46A489352C87B0F5056FD149DDE67219A3C3D1DD875 +BAD81AD443E0F90DC79D979E10B4262DD20B255B04BBBE5C988B23667135CC61D988928F5F07753A +C76807203942F6B62A178A1E5CBBFBBAEBE735D09B747BF00C360F2E2B6C9F2261F7DB018EDF40B1 +3DB4B7962D4D2F858E1FA6376F21ED9CEA4BEE07FFE27F136BF60352AFB71CDBFF7E65F6B4624E16 +C6E5AB7FF60B4711A178FEE691DD8D362B14175A69070FBC5B9EB15248E4DFDA3C9453335D0E1EB2 +7714E4624C7215A73B53402B02CF7AA05A14CE1E5AE161B75F218BF0EE1215D3CD925D5ACB7DBD2E +BD8E4405433F432C491999794C814D5B384B7A1C0AB28A11CBD0A6EA12678508CF86232309B0BBF7 +EEA929FB7951F5A6B367C5AC57DD7D3B3C1E2188DD4664D14FBC32B1A1EB53D7EE7F55F4D5C2D014 +528EBB7F0E595F7618721451EE6B83EB240A6C4D33377893D4EF542F47EB2845A09759D5554C74DC +5E9109FFC8C929CF1AC446D8149957720EE4FB670D3CA61378549DB992126B23618CF49361D6D4B1 +C73C3D37E4A4465ABB349CFA34E9351D1192E366267EDF02DE432ABC80792B0CFD41FFD0AAA42E63 +3F5B2177A351D33477C636CA573CB02C07F7F7A41C9F1BC4C112BD6459DD130757D2BD6F47495C3F +92E99522871DAC2865 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +cleartomark{restore}if + +%%EndFont +%%EndResource +9.72 /Mathematica2 Msf +0 9 m +(\310) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +2.25 9 m +(q) N +9.72 /Mathematica2 Msf +7.5 9 m +(\310) N +9.72 /Mathematica1 Msf +12 9 m +(=) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +p +20.25 9 m +(0) N +P +p +24.75 9 m +(.) N +P +p +27 9 m +(0) N +P +p +31.5 9 m +(2) N +P +P +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +P +P +[1 0 0 1 -118.485 -136.705 ] concat +1 w +[ ] 0 setdash +P +P +p +np 181 96 m +181 112 L +219 112 L +219 96 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 181.505 96.904 ] concat +1 w +[ ] 0 setdash +p +np -1.505 -1.904 m +-1.505 16.096 L +38.495 16.096 L +38.495 -1.904 L +cp +clip np +p +np 0.495 0.0956 m +0.495 13.096 L +37.495 13.096 L +37.495 0.0956 L +cp +clip np +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +p +np -1.505 -0.904 m +-1.505 15.096 L +38.495 15.096 L +38.495 -0.904 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +9.72 /Mathematica2 Msf +0 9 m +(\310) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +2.25 9 m +(q) N +9.72 /Mathematica2 Msf +7.5 9 m +(\310) N +9.72 /Mathematica1 Msf +12 9 m +(=) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +p +20.25 9 m +(0) N +P +p +24.75 9 m +(.) N +P +p +27 9 m +(0) N +P +p +31.5 9 m +(5) N +P +P +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +P +P +[1 0 0 1 -181.505 -96.904 ] concat +1 w +[ ] 0 setdash +P +P +p +np 267 3 m +267 19 L +305 19 L +305 3 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 267.864 4.035 ] concat +1 w +[ ] 0 setdash +p +np -1.864 -2.035 m +-1.864 15.965 L +38.136 15.965 L +38.136 -2.035 L +cp +clip np +p +np 0.136 -0.0353 m +0.136 12.965 L +36.136 12.965 L +36.136 -0.0353 L +cp +clip np +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +p +np -1.864 -1.035 m +-1.864 14.965 L +38.136 14.965 L +38.136 -1.035 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +9.72 /Mathematica2 Msf +0 9 m +(\310) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +2.25 9 m +(q) N +9.72 /Mathematica2 Msf +7.5 9 m +(\310) N +9.72 /Mathematica1 Msf +12 9 m +(=) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +p +20.25 9 m +(0) N +P +p +24.75 9 m +(.) N +P +p +27 9 m +(0) N +P +p +31.5 9 m +(7) N +P +P +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +P +P +[1 0 0 1 -267.864 -4.035 ] concat +1 w +[ ] 0 setdash +P +P +p +np 295 86 m +295 102 L +328 102 L +328 86 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 295.617 86.954 ] concat +1 w +[ ] 0 setdash +p +np -1.617 -1.954 m +-1.617 16.046 L +33.383 16.046 L +33.383 -1.954 L +cp +clip np +p +np 0.383 0.0458 m +0.383 13.046 L +32.383 13.046 L +32.383 0.0458 L +cp +clip np +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +p +np -1.617 -0.954 m +-1.617 15.046 L +33.383 15.046 L +33.383 -0.954 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +9.72 /Mathematica2 Msf +0 9 m +(\310) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +2.25 9 m +(q) N +9.72 /Mathematica2 Msf +7.5 9 m +(\310) N +9.72 /Mathematica1 Msf +12 9 m +(=) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +p +20.25 9 m +(0) N +P +p +24.75 9 m +(.) N +P +p +27 9 m +(1) N +P +P +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +P +P +[1 0 0 1 -295.617 -86.954 ] concat +1 w +[ ] 0 setdash +P +P +P +P +P +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +%Trailer +%EOF diff --git a/services/clsi/test/acceptance/fixtures/examples/epstopdf/main.tex b/services/clsi/test/acceptance/fixtures/examples/epstopdf/main.tex new file mode 100644 index 0000000000..c35a378422 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/epstopdf/main.tex @@ -0,0 +1,10 @@ +\documentclass{article} + +\usepackage{graphicx} +\usepackage{epstopdf} + +\begin{document} + +\includegraphics[width=\textwidth]{image} + +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/epstopdf/output.pdf b/services/clsi/test/acceptance/fixtures/examples/epstopdf/output.pdf new file mode 100644 index 0000000000000000000000000000000000000000..199333ddf030b3845cba6d081522bed92804628c GIT binary patch literal 31035 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 literal 0 HcmV?d00001 diff --git a/services/clsi/test/acceptance/fixtures/examples/feynmf/main.tex b/services/clsi/test/acceptance/fixtures/examples/feynmf/main.tex new file mode 100644 index 0000000000..c36ed70919 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/feynmf/main.tex @@ -0,0 +1,28 @@ +\documentclass[a4paper]{article} +\usepackage{feynmf} + +\begin{document} + +\setlength{\unitlength}{1mm} + +\begin{fmffile}{diagram} + +\begin{center} +\begin{fmfgraph*}(41,17) +\fmfleftn{i}{2} +\fmfrightn{o}{2} +\fmflabel{$g_2$}{i1} +\fmflabel{$g_1$}{i2} +\fmflabel{$p_2$}{o1} +\fmflabel{$p_1$}{o2} +\fmf{quark}{i1,v1} +\fmf{quark}{i2,v1} +\fmfblob{.35w}{v1} +\fmf{quark}{v1,o1} +\fmf{quark}{v1,o2} +\end{fmfgraph*} +\end{center} + +\end{fmffile} + +\end{document} \ No newline at end of file diff --git a/services/clsi/test/acceptance/fixtures/examples/feynmf/output.pdf b/services/clsi/test/acceptance/fixtures/examples/feynmf/output.pdf new file mode 100644 index 0000000000000000000000000000000000000000..bb7fe9099cb500584a40d61e48110085d5a6f64c GIT binary patch literal 26753 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 H;mm&nd$@)w literal 0 HcmV?d00001 diff --git a/services/clsi/test/acceptance/fixtures/examples/glossaries/main.tex b/services/clsi/test/acceptance/fixtures/examples/glossaries/main.tex new file mode 100644 index 0000000000..336f70868c --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/glossaries/main.tex @@ -0,0 +1,17 @@ +\documentclass{article} + +\usepackage{glossaries} +\makeglossaries + +\newglossaryentry{Physics}{ + name=Physics, + description={is the study of stuff} +} + +\begin{document} + +To solve various problems in \Gls{Physics} it can useful to express any arbitrary piecewise-smooth function as a Fourier Series composed of multiple sine and cosine funcions. + +\printglossaries + +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/glossaries/output.glg b/services/clsi/test/acceptance/fixtures/examples/glossaries/output.glg new file mode 100644 index 0000000000..6bae571d8f --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/glossaries/output.glg @@ -0,0 +1,7 @@ +This is makeindex, version 2.15 [TeX Live 2011] (kpathsea + Thai support). +Scanning style file ./output.ist...........................done (27 attributes redefined, 0 ignored). +Scanning input file output.glo....done (1 entries accepted, 0 rejected). +Sorting entries...done (0 comparisons). +Generating output file output.gls....done (6 lines written, 0 warnings). +Output written in output.gls. +Transcript written in output.glg. diff --git a/services/clsi/test/acceptance/fixtures/examples/glossaries/output.glo b/services/clsi/test/acceptance/fixtures/examples/glossaries/output.glo new file mode 100644 index 0000000000..0b6f71e75f --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/glossaries/output.glo @@ -0,0 +1 @@ +\glossaryentry{Physics?\glossaryentryfield{Physics}{\glsnamefont{Physics}}{is the study of stuff}{\relax }|setentrycounter[]{page}\glsnumberformat}{1} diff --git a/services/clsi/test/acceptance/fixtures/examples/glossaries/output.gls b/services/clsi/test/acceptance/fixtures/examples/glossaries/output.gls new file mode 100644 index 0000000000..128261a191 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/glossaries/output.gls @@ -0,0 +1,6 @@ +\glossarysection[\glossarytoctitle]{\glossarytitle}\glossarypreamble +\begin{theglossary}\glossaryheader +\glsgroupheading{P}\relax \glsresetentrylist % +\glossaryentryfield{Physics}{\glsnamefont{Physics}}{is the study of stuff}{\relax }{\glossaryentrynumbers{\relax + \setentrycounter[]{page}\glsnumberformat{1}}}% +\end{theglossary}\glossarypostamble diff --git a/services/clsi/test/acceptance/fixtures/examples/glossaries/output.ist b/services/clsi/test/acceptance/fixtures/examples/glossaries/output.ist new file mode 100644 index 0000000000..1861f247c7 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/glossaries/output.ist @@ -0,0 +1,29 @@ +% makeindex style file created by the glossaries package +% for document 'output' on 2013-7-28 +actual '?' +encap '|' +level '!' +quote '"' +keyword "\\glossaryentry" +preamble "\\glossarysection[\\glossarytoctitle]{\\glossarytitle}\\glossarypreamble\n\\begin{theglossary}\\glossaryheader\n" +postamble "\%\n\\end{theglossary}\\glossarypostamble\n" +group_skip "\\glsgroupskip\n" +item_0 "\%\n" +item_1 "\%\n" +item_2 "\%\n" +item_01 "\%\n" +item_x1 "\\relax \\glsresetentrylist\n" +item_12 "\%\n" +item_x2 "\\relax \\glsresetentrylist\n" +delim_0 "\{\\glossaryentrynumbers\{\\relax " +delim_1 "\{\\glossaryentrynumbers\{\\relax " +delim_2 "\{\\glossaryentrynumbers\{\\relax " +delim_t "\}\}" +delim_n "\\delimN " +delim_r "\\delimR " +headings_flag 1 +heading_prefix "\\glsgroupheading\{" +heading_suffix "\}\\relax \\glsresetentrylist " +symhead_positive "glssymbols" +numhead_positive "glsnumbers" +page_compositor "." diff --git a/services/clsi/test/acceptance/fixtures/examples/glossaries/output.pdf b/services/clsi/test/acceptance/fixtures/examples/glossaries/output.pdf new file mode 100644 index 0000000000000000000000000000000000000000..297385b8d11da3dc276a35bec6d0d05676532cc9 GIT binary patch literal 34478 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�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 literal 0 HcmV?d00001 diff --git a/services/clsi/test/acceptance/fixtures/examples/knitr/main.Rtex b/services/clsi/test/acceptance/fixtures/examples/knitr/main.Rtex new file mode 100644 index 0000000000..add779afce --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/knitr/main.Rtex @@ -0,0 +1,13 @@ +\documentclass{article} +\begin{document} + +Hello world $x^2 = 0$. + +%% chunk options: cache this chunk +%% begin.rcode my-cache, cache=TRUE +% set.seed(123) +% x = runif(10) +% sd(x) # standard deviation +%% end.rcode + +\end{document} \ No newline at end of file diff --git a/services/clsi/test/acceptance/fixtures/examples/knitr/output.pdf b/services/clsi/test/acceptance/fixtures/examples/knitr/output.pdf new file mode 100644 index 0000000000000000000000000000000000000000..365ce1af85e7946954f23a63defcad9fc5f9797f GIT binary patch literal 43271 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 R8w)opD*^?DxS|BY{{TGb3-SN} literal 0 HcmV?d00001 diff --git a/services/clsi/test/acceptance/fixtures/examples/latex_compiler/image.eps b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/image.eps new file mode 100644 index 0000000000..fb131b9c36 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/image.eps @@ -0,0 +1,6673 @@ +%!PS-Adobe-3.0 EPSF-1.2 +%%BoundingBox: 0 0 432 268 +%%HiResBoundingBox: 0 0 432 268 +%%Creator: (Wolfram Mathematica 8.0 for Linux x86 (64-bit) (February 23, 2011)) +%%CreationDate: (Monday, October 8, 2012)(15:03:46) +%%Title: Clipboard +%%DocumentNeededResources: font Times-Roman +%%DocumentSuppliedResources: font Times-Roman-MISO +%%+ font Mathematica2 +%%+ font Mathematica1 +%%DocumentNeededFonts: Times-Roman +%%DocumentSuppliedFonts: Times-Roman-MISO +%%+ Mathematica2 +%%+ Mathematica1 +%%DocumentFonts: Times-Roman +%%+ Times-Roman-MISO +%%+ Mathematica2 +%%+ Mathematica1 +%%EndComments +/p{gsave}bind def +/P{grestore}bind def +/g{setgray}bind def +/r{setrgbcolor}bind def +/k{setcmykcolor}bind def +/w{setlinewidth}bind def +/np{newpath}bind def +/m{moveto}bind def +/Mr{rmoveto}bind def +/Mx{currentpoint exch pop moveto}bind def +/My{currentpoint pop exch moveto}bind def +/X{0 rmoveto}bind def +/Y{0 exch rmoveto}bind def +/N{currentpoint 3 -1 roll show moveto}bind def +/L{lineto}bind def +/rL{rlineto}bind def +/C{curveto}bind def +/cp{closepath}bind def +/F{eofill}bind def +/f{fill}bind def +/s{stroke}bind def +/S{show}bind def +/tri{p 9 6 roll r 6 4 roll m 4 2 roll L L cp F P}bind def +/Msf{findfont exch scalefont[1 0 0 -1 0 0]makefont setfont}bind def +1 -1 scale 0 -267.698 translate +-35.28 -2.88 translate +[1 0 0 1 0 0 ] concat +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit + +%%MathematicaCell +%Cell[BoxData[ +% GraphicsBox[{{}, {}, +% {GrayLevel[0], LineBox[CompressedData[" +%1:eJw113dcTf8fB/B7697q7tW0KSoJWRnp/ba3rCR7kxmSzKy+KFlFvqSMjB++ +%ESqKrlVEKJoy2nvv3S/O+5x/7uPce8/n83m/nu+zeq7cOmeNBofDqeVyOH8+ +%2a0l/9EWydgK9V7d4EmZe47Cm8LwOycM2X1fGDzdYkfqz3L1024exbOfBoC3 +%r26ui2c5/R4EpUfSFFnDytWX/v2z/Q9yO+VvM84uo/8Hg9/5EpPBp8vUJbP/ +%fBMCXy8fN5GPKqPjn4DGhdnXo/JK1cKUG86jheEwxHn2wDHnSmm8Z7DcMeyV +%7+hStfni069rzZ7D73FreM8KSmj8KKiZGhB1z6dEHdRxdMqNl+C31zh+u20J +%zfcK1nC8d2oVFqsH/93egLor7NzkU0zzvwWPY/98uDG6WP2m1qxjhmhwtBjk +%dye/iNbzDkRHH6e5ny1Sz/k74XvY7J513WxUEa0vFhJ+vlgTkFOoztzzZ8AP +%YDckMqHAu5DWGwf9rDutaBhWqN72Z3nOn2DLwfSeGb8LaP2fobqXp8v54wVq +%RiMedJ1rPhpaFVA98XBvmqp6Y1q+evLfCRIgKtrukO+hfKovAa4/0ex0wDxf +%/We2xae/wuWhSVqYkEf1foM7E+x2fnPLU/8tJ+Ub9MHHQYN75FH9iZCe2ui9 +%4F2uuvuf5XRLghFBj47Yb8mlPJLAyiiZ11kvV71u7Z8tGcxC4sIeROZQPikQ +%nNGyS29ljvrPaLrBKXB/zgzOGJ0cyisVnGs38MYEZ6vr/gxXmwpxOW67tedl +%U35pMMcjofO/jVlq278DfoegKVuPNQVkUZ7f4RpHXT5wfJb67/I80mF2ZJu1 +%cWEm5fsDHKa6JZd5Z6o/xf3ZfoDBO+2sPYMyKe+fYGHW3z0pOUOt93eBv+DB +%/IdHm/ZkUP6/4Mi8AfcqumWo/w63+DfofvIPWuzxmzwy4HDdvBtr4n5RPRng +%XyQ/26D8RT4ZMPzRQcuhjj+pvgxocL11Ky/gB3llQtVuy7W2OelUbyYkKzUb +%upink18mhO8U4sXN36n+LKgLm1j3X0gaeWaB1zML89G1qZRHFmim+1ksHZ5K +%vtlgedzAUmNvCuWTDQ3HXh8c/iKZvLPBRX+wU01bEuWVA6+K352eiUnknwMT +%axKOmR9KpPxywHbtC6ejr75RP+RCoPeYCnfuN8ozF3oOWGKVjV+pP3LBe7L5 +%piz3BMo3Dxx73Vu6Niqe+iUPxjkazH/X8IXyzoM+e25rp+74TP2TD+LzNpol +%pXGUfz4kRp4W8dZ9pH7KhytWwuBOv2PJowD+O3xdbDv/PXkUQOigqX62cTHk +%UQAmpw6MmzEmmjwKID7AsmxD6BvyKAQn433ymWavyaMQ9Dj2C8ZeekkehfD2 +%tIXdFv0o8iiCLxaLbU3qItRn/noUgeSth6vXt3DyKAKT2d+7fgx+Qh7FUOYz +%5qzF8RDyKAazMPW0oKj75FEMtptcBlWtu0UeJTCxx4K8oZKr5FEChh625dcX +%+ZBHCdxdculc2bQ95FEK/jDse0aTGzAepXDat36zh6EPMB6lEOMyLXttSyAw +%HmXw0vl9VIrLLWA8yiDsQt/zKyvuA+NRBjv2/d43+GkIMB7lUHT/5ubmpifA +%eJTD2mtGW4vgKTAe5fB4Z5evQccigfGoAOVTaPVLiAKmngqY/6hX1yGyV8D4 +%VEBui2yWT9hrYOqrAGPprh6ey94C41UBT9Mc+z4XxABTbwVYR3y/ZB36Dhi/ +%Crh4w3aAYmUsMPVXgmNgbMYq+UdgPCvBzrn8foY6jvKohF1ulbIi58/A+FbC +%qfuxDgGKeMqnEmpM34d7FscD410J2DMwyigmgfKqgsnmb18fvPYVGP8qyP+i +%eXn3/m+UXxVk1g8+OnVhIjDnZxWU1nmvz7FOojyroIeBPsdSPxmY/qiCWTF9 +%JVibTPlWwzDfRG2DpBRg+qUaTjQei30Zmkp5V8O1LS5aQ/zSgOmfanAvvXnN +%fvd3yr8a7G59+7JjcTow/VQNMr7DyVn4gzxq4Oo/D/X5vX+SRw30CbVN8hT+ +%Io8aWFESNqam4hd51MCExLtuY1J/k0cN3LLSbRmxPIM8asBzeCet0MQM8qiB +%xasjHkZMySSPWoiyEMg7qTPJoxby2zPkrUOyyKMWLhklHVp0L4s8aiFlYNjQ +%vr2yyaMWTljpGO/5N5s8asG5x7V9qMghjzp4963x2KkTOeRRB5EuJi12Grnk +%UQeF/fc1B+3NJY+O/4+32L6lNpc86mCRzYR5UVvzyKMO9DZbrbtYlEce9WA1 +%w8COvzafPOoh3SfIODcznzzqwe12cr91SwvIox5SRvB2uKYXkEc98DhqF6lj +%IXnUw9beZyJtUgrJowFsrQ6d0ZpfRB4NYHrH/+TapCLyaIAuOL1mtn0xeTTA +%GuPw70VJxeTRABNkzUUqhxLyaACvueemlaeWkEcDOFnuMly9qJQ8GsFlgFfM +%gV+l5NEI+U+NetquLCOPRngeGBIYmFdGHo2wcvfdxR82lpNHI5zRh4MnK8vJ +%oxG4C3e46NhUkEcTNMd1Tw5wryCPJpi5wunA1TcV5NEEPv2vtpRoVwJzvWyC +%hNniq7emV5JHE+j1eBv69GwleTTBucifASNTKsmjGQ6uu2yt37WKPJph027d +%liWrqsijGZZWO1vp3K0ij2ZIWrG+uVtlFXk0w5uBsu6Bw6vJoxnEvr7uFw5W +%k0cLZAa39qx4X00eLdCw/9iod4oa8miBzg+mjuIvqiGPFni9sK/u26Aa8miB +%Al/97Y1lNeTRAv945ufdGFFLHi3Qpbvry89Ha8mjFbJ6eHq7xdeSRyu4t5vZ +%X+9SRx6t8HFCVbC9Ux15tMJJ19DlHmF15NEKBUP6rQJePXm0wrlbvnoec+rJ +%ow2GN6beXXKtnjzaIEQ7Qvmhop482mD5z+vvorGBPNrApmT4yTlnG8ijDfQq +%YhoOZjWQRxsIRxUOnz2kkTzaoelrk27YP43k0Q6orNB8m9ZIHu2w1HNA0X7L +%JvJoB0Mb7tisQ03k0Q4nnwiKeSlN5NEOrolmkc/6NZMHB0+MWM0dc6SZ6uFg +%7LMxG3Z/byYfDm6x7mLnOqiF6uOg79Tpd829WsiLgzNttkwMz2mBv+Wu5eC3 +%2QNna0Mr+XFw67ivTf0vtdL1gYPJni0OpnWt5MlBz/itIby5bZQHB1sbMyRR +%D9vIl4Mng7Y/Wydrp3w4+GOPlMvf2k7eHDwaGWJx6ks75cXF5DI/5+OGHGT8 +%ueh/JmX++ykcZPLj4nHJw1On9nKQ6QcujnPtZ5PxHweZPLmYMH6oV0QGB5n+ +%4OL7L1PajHW5yOTLRRvp2tM9J3OR6RcuvjN3mfxkHxeZvLnIyX8mKwzhItM/ +%XLR7Glv1NJ+LTP5c3Ou/b/LgbhrI9BMXh4UZzZ9nr4EMhwbKe4SeMfXWQMZD +%A9/FH4OwaA1kPDRwxL86ri1tGsh4aGBk9AfkjtBExkMDd7rFhH/boYlM/2ng +%4ATDJ1seaCLj0THvBndRdrEmMh4a2Du8LXiYOQ8ZDw18dd4yfPs6HjIeGmia +%1DnC7xYPGQ8NtN/c//GDPB4yHho4LPTEwxhTPjIeGhj4wmluhRMfGQ9NLJ7n +%mGv8H588NHHZ7mzu/ko+eWjij+WDrPjWWuShiUM9n54O3a9FHproO9Y5/0m0 +%FnloomCt/IGmTJs8NHG00ZFz9x21yUMTM0cbX4i9qU0empi9YeGrzdXa5KGJ +%Xvo9TS+P1SEPTaxVVV3c76NDHprYMNZ2uixPhzx4+NEnJXbXSAF58DBb97z7 +%xzMC8uBh2zLR5oGFAvLg4QHhp+f3xgnJg4cRws6zVl8VkgcPraICnnu0C8mD +%h14jQjcPXyEiDx7GpEws8owWkQcPV1zemBzdT0wePFzjsF5Hx09MHjzUuXtS +%5ceTkAcPJzs6TQ/YKSEPHgZO/xLhXCQhDz4mdptgumiVlDz4uPCrVpRPhpQ8 +%+HjQZoT/uZUy8uBjr415hx8UysiDj6tc5vYeOUpOHny89DlzY8RROXnw8YP4 +%2ODrCXLy6Dh+bFe1XS8FefCx3n/ZZ4tdCvLg45ul2VYv4xXkwccGvW3hdwco +%yYOPe54UTHjkqyQPLbzbdNf7AkdFHlrIUWPfEBcVeXT0kUty5/FVKvLQQr7R +%zaMz9uqSR0dfRW7eoKfUIw8tdO/WLXFihB55aGGd1ZKK9W765KGF41+08r7P +%NSAPLTzbeGeVaIEheWihmb5sW85FI/LQwtHDQspjbDuThxbqTQ8JHBXclTy0 +%8NGVKzfH2fUkD208suB8pPknQ/LQxrkbyiR29/TIQxtTp7aeKF+rSx7aOOrC +%4WEXL6nIQxsrrU5OsNVSkYc2Opd6f15yTkke2rhlkNmQe4OV5NFxXpgZWaZm +%sx7a+CT3WZoikPXQxvCAL9Y/VrIe2vhc5jRvuaWCPDr2HbF5eaucPHQwNt7k +%dUGHN+Ohg8szu38bf1dOHjroOoNTnO0hJw8dNLh9xX/Wajl56ODH7Zn+AyfI +%yUMHHS8WbhlqJicPHayrWcPvLpEjc//UwWNdgnq45cvIQwc1LfudCQmXkYcO +%9p/le+K+p4w8dNBUEuTfbbmMPHTQwS6g5JO1jDx00LKqzOSEQkYeAlyvv2CP +%WSl7fgjwXtXzlkMf2PNDgJ2Gvm1w/p+UPAQIid39v5+QkocAz37gLb2+UUoe +%AnQ03/Xp3kwpeQhwEH93Yf4gKXkIULVkY9dRhlLyEODI1Z+X+LdJyEOAe0VR +%B6pyJeQhwJeuv+v6f5aQhwBX5R4QTA2XkIcQ+00ct2nwNQl5CHFSXO2vn14S +%8hCi/y/dk2N3SchDiI0ub9bbr5KQhxCD7zSaSmdJyEOIptmFtStGS8hDiP8z +%vvlmmoWEPIRosnSx6K2RhDyEuDVm966XOhLyEGL/5o2FoxvY65UQy276/BhW +%ICYPIVb0vOMXlComDyH+1+p95UismDxE+LzfgvNxEWLyEKEgPUCx976YPEQY +%O2uA8mSAmDxEGCbuv6X5jJg8RLguUKV8e0RMHiKcK3jUNcdVTB4i9JiQmDp/ +%g5g8Ova33QgwXComDxFW/HvZ1XyOmDxE+H2cfOGRiWLyEOHPuV0/m4wSk4cI +%Rw4yGS4cKCYPMb7rbJ08qLeYPMQoWBQ86XInMXmI0eHZiU0oF5OHGL3HDbja +%U0tMHmI06GPUbNMiIg8xrkz1MThdJSIPMYYeLQs1KBSRhxiN6nPOJv5m7x9i +%PHTWI/p1sog8xHhsAudK1icReYjx48pae6uO+w3jIcbS10cy7j8XkYcY7beN +%z5j/REQeErzdq+Rh3/si8pDgogMWm0yDROQhQcPN9tEz/EXkIcFrpVMXXfYV +%kYcE3Ve7L5Z6i8hDgvsWHku//cfhr4cET9WPX7n6gIg8JPhTc/GC8btE5CFB +%jwdPJkzscGQ8JDgqLkTo5CQiDwka3NnW9f5KEXlIMLXx8TtpRx8wHlI0Gfrg +%/ml7EXlIMfRmulV/OxF5SHH1ovT9RZNF5CHFSdue178eKyIPKb5clnwkzIb1 +%6LhObBxhGT2M9ZCiw+ry96UDWQ8pfnXPnzDQgvWQ4qgVQi/P3qyHFH3vnX/R +%2p31kOJQ3dTo451YDykG8swCzfVYDynumvhQniVjPWR4e2LdqYdC1kOGd/wm +%p/ryWQ8Z1h+OifHmsB4yDGyy5V5uFpKHDAsXqVIj64TkIcOf+d2tKyuF5CHD +%AylXDEeUCslDhpv2nfjtWyAkDxmeOjjzCDdHSB4yPGuVevvgbyF5yHDv1AVN +%inQhecjwtYk793GykDzk2NX8Ysuar0KqR46iVP5Q889C8pFj3kuDSS2xQqpP +%joYDWuf/ihaSlxxf+PUoi38lpHrleOOkdvjXF0Lyk6Pa521i1jMh1S/Hx+1G +%CZww9vlMjm2Lp6RZPmLzkGNBj/bY9cFCZN5nOu476/YWP7zL5iNHq7DgG1q3 +%2ec3OVaevW3qdIPNS47lccqA1EAhPT/IMZ0/ycTen81PjsZDV+/4eVFI/SDH +%6c5vmredZ/OU48iN7p7Kc0LqDzl6XXU2Up9i85Xjhv5Nuq5eQuoXOQb5BPUb +%cZzNW47TJA8rtDyE1D9yjPfwP/T7EJu/HOf17rv69QEh9ZMcz7Xoj324l/VQ +%YJ5Gb7M7bqyHAuHBsCF3d7IeCkx4fu126HbWQ4G2ZxsHfdzKeijQftr1psJN +%rIcCM8y/LFFuYD0UaB0SrTt+HeuhwHqfrF0HVrMeCtTvMz3z1QrWQ4FCboNS +%uox9flZgnx4tUSsXsx4KfHuv2kvtyHoocMWXDVEmDqyHAo2CTZeem8d6KNBj +%qnO99hzWQ4EH7B9P9LBjPRToGHFHXziD9VBgb4d9tRemsh4KvHz1XTeLyayH +%Ant8CV8XM4H1UOCMkVYjncaxHgpcN6abg2oM69FR75Tvs97ash4KxM3JbXtt +%WA8Fpr25fWXkSNZDidf1lpa3W7MeSuzbuVfEx6GshxI3Ogi0AwazHko85/Xo +%k6sV66FE25/LdtoPYD2U6PZsQdJIS9ZDiQOv6Bv0sWA9lCi4yNtoYM56KHFl +%U6yt1JT1UOJUt4hRot6shxI3KOSPJMashxLN93vl6PVkPZTY/L6+xLg766HE +%IapVUuuurEfHfHe+JNh1Zj2UOCWiJGyzEeuhxBXVRulnDFgPJf7PetaiZ3qs +%R8f+4bSwPBXroURV1to4QyXrocQj/V2WzJKzHkq8O2DNvpNS1kOJfcIUNz+J +%WQ8lFojPfFWKWA8lBqgn9VoiYD1UKHTg6t/XZj1UOPfEleh2Puuhwp+xr7Y7 +%8FgPFcZ3VWs+0WA9On4/cmO2Lpf1UGFfYd85u9vZ90cV3ihKm5/VKiAPFdrc +%mxNm1yIgDxXqn71l/KpJQB4qPFUxy2FYo4A8VDhoZoDmw3oBeagwwSZpgGWd +%gDxU6HqWj8E1AvJQ4dV1Rw8PrhaQhwp/VxQufV4pII+O96i4M7mTKwTkoUK/ +%DTv6p5UJyEOFlY+G/LOpVEAeKtxxcYAbr0RAHh3vJZHL1gQWCchDhbc1upWP +%7nhfZjxUqO57Lfp3voA8VLjxznOlR56APFR474X0i2WugDxUuDpzSmJatoA8 +%dPHMBqeU41kC/D+QSguY +% "]]}, +% {GrayLevel[0], LineBox[CompressedData[" +%1:eJw113dczfsfB/BzTvPMzoispGGEzJtrv98qZFzRtSJZyYpCXDKvkK3749rE +%jcxKSEY5lAZpUlFJpb3XaY9fnPfn+0+P+n7P5/N5v57v8/30MV7lZr+Gx+Fw +%VFwO5+dPdg3+Y5N4+KVq5W79wOm5nofA0/+3/IGu7PdzEN594CYdqFY+73u4 +%bN7z6/Cs6XVygozdvwX6g/qU18VWKS9f+nndg0fWhkbCzVX0fCCk7Bv4qFZe +%pSyf9/MvwdBjiWr0ndBK+vxT6BD6xJg4VioF6X7ukwShMKbG1HA1p5LGewHZ +%++0H7btVoTR3PBOhGhQGO2rTps+3raDxX4P+DdstzWXlyltdn073ewOhltbC +%ZWfKab63kL88dvbVUeXK0b+uSEiV5ReeTy2j+d/B1ee6G5bvLFNGqgZ1zRAF +%O5xbi+t7ldF6YuBqovuk2eGlSvtfE8ZCg8fgHNflpbS+9xAbPnuZE7dUmev5 +%c8APwLkqCRD6ldB6P0LyuaUuJ2xKlFt+Ls89Hqwnx71NKCim9SdA2Lp9JzKP +%FCvVGkmg52Y18uHAYqonCYKa3nNmxBYpbX9NkAzXJAc2PlhXRPUlwy3jzsI4 +%3SLlz9kcz6RA5tkn7hF3C6neT2CYE3l5l22h8lc56Z8gZmfeAlVRAdX/GdxP +%PHIY712gNPq5nL6pUGIc6jhlQAHlkQq/dwwM1YvKV651+XmlQdEq8YQbq/Mp +%n3TQ+K3kdQM3X/lzNP3AdEjs7GfW7cYPyusLRINQ0jTph7Lh53CqL3Aq4suQ +%S5l5lN9XUOVmFPJ25Skn/xowA/zPXTQf2j2P8syA50NHfen+JFf5a3mHM6Fk +%pgKi7HIp3yxQZedNtizPUcZ//HllQYjzNff1R3Mo728wKLf4nINZjrLbrwVm +%w8fBR5b3P/Cd8s+GaTsvhotCs5W/hnP8Dk5X27ttrPhGHjkw0WnO10mm36ie +%HFgd86Jn4OIs8smBsOJpIT6nMqm+HDjyg38t+20GeeXCbW2vRc9UX6neXPC2 +%2u/FM/9KfrnA3eSv/W7pF6o/Dza+Oe7deiqdPPOg7MDX9TeUaZRHHvydM2hj +%YnUq+f6AKzGlzm7GqZTPD5iXYM7bN+8zef+AJ2KTqsoDnyivfPhkG+SQEpRC +%/vlwPim0b7/sZMovH2xsj1/OFyZTPxRAwve5vbnjkijPAugR4NA8LC6B+qMA +%rlVsSZ9lGk/5FsK9U3H3eZ5x1C+FIGlW/uuZ+J7yLuwab8GecNNY6p8iGO9+ +%4EzhjmjKvwgcbTSuPIt9R/1UBDGWv3lb9owkj2Lw06yb4r3uLXkUQ7NjBkdv +%k5I8iqFu3o8HI7XDyKMYcFl/j9arz8mjBB79+8a1bUQIeZTAyqiAxNa3weRR +%Am8MF036cuMheZRCt1PtYX7Pbyt9fnmUwoDjh5J/O3eNPEohzF0ybo3DGfIo +%A6ltcsLzU0vJowwKl/+5aPPxo6D2KINhTwt+t553GdQe5ZAoVsaeHn0L1B7l +%0G3btXHHpj4AtUc51C31i9g/LBjUHhVQNfNA46vwp6D2qADz/unbP9o9B7VH +%BdhscL75Kf8VqD0q4UyNM7zYqwS1RyW0FS7vcffkW1B7VEJvQWa3MY6RoPao +%gjEF5+18LaJA7VEFpx/vbjvOiQG1RxV8Xvjmo0NqLKg9qkF6LNBP+eAD1VMN +%a68EO1sd+ghqn2qwG7Dkej+nBKqvGvz17Zf0MkwCtVc1bPg2vL1EmEz1VsPY +%1QLfqNZkUPtVQ+VX5Qf/8hSqvwYMBla+cMn+BGrPGuAG7p1skfyZ8qiByg+T +%fpS9SwW1bw2sjO5Xe/tFGuVTA6/GTHA7F5QOau8aMJlqZ+3l/4XyqoXIurm3 +%V1//Cmr/Whi4fyLf5UIG5VcL1teK3Lz/yQT197MWjF2PZoeczKI8a+HJvVnv +%dY99A3V/1MLmiKySE0eyKd866FXzunrm4e+g7pc6GBXTuf+SVQ7lXQfc2kWB +%+pE5oO6fOrjbvqZ4ik0u5d/1/O4B3lrRudRPdRDwMbl9v20eedRDbIXy7am4 +%PPKoh8yLgZZWdj/Iox7e7L5w6+anH+RRD2aen4fdXJxPHvVQ5j55vEd2PnnU +%g2dzg26+cwF51EPtjUdT+OUF5KECYWhP5+JtheShAvel//O93lZIHl33VU4h +%zYeLyEMFE6Lt+hpJi8lDBa17/+gluVJMHioY1So/8W5ACXk0QIjDswKbJyXk +%0QBr37y56Iul5NEAW+L8xsQllJJHA7xvaX4Wv6yMPBogvnlQw7OKMvJoAGXd +%UdHefeXk0QivfDJmWEoryKMRhnlJL3z+r4I8GiHgjPNchzGV5NEI/w4LPZH+ +%oZI8GmGUym6X3Yoq8mgEh2aVLKKhijyaYE5q1jKT6dXk0QQlw8w+LztbTR5N +%IE57eXFUTjV5NMEGvVozT4sa8miCfO302fN315BHE3zof8Ip4n0NeTTB5rTT +%VyJ61JJHM3B2Xk+yX1dLHs2QluP7ZF9XX6o9mqGh3qvZll9HHs1gpJ138fGS +%OvJohhGpbSvCH9aRRzMknvxTd3NnHXm0AHSGXky0ryePFhgzufVOhn89ebTA +%FIXlf0da60H9vmwBB72ipsa5KvJogVjf7lLjOyryaIFkc9tGfoeKPFrBcOti +%8aMFDeTRCrevX3GxCGwgj1boF5dvtFmnkTxaISfwndOBlY3k0QomRgY/Noc1 +%kkcrHHlwr8aiRxN5tEF+5KrEDI8m8mgDX/25591TmsijDXQ0C3PLRzSTRxtE +%X4j0W+nTTB5t0DvJ1zClupk82mBZvHTxNPsW8mgD/rT3CyKftpBHOzxtfHd/ +%aY9W8miHovSG97y9reTRDkGizVMS81rJox3mbDuT/nZGG3m0w4qNx4wKgtvI +%ox0875Q9ntm7nTw6IPjI56aWw+3k0QFXt3OOltW0k0cHuKnyAu2Xd5BHBwQd +%uh5mkNBBHh0wanODxazJneTRAaNzd5xvDeokj04oWeJZcEnGQbVHJ7wr2nRE +%PJuDao9O+G+tcdRcbw6qPTqhbrGv65ZIDqo9OkF/4PUH2zhcVHt0wgl/XbcN +%k7lIxw8UJI05u2gvF9X1cBCl9scwnItqHw6aTkgy+62Di+r6OFh4PmCbFfJQ +%7cXBD/d77v3Li4e/ynXhYNbMBSPjY3io9uOg2bkZWavEGqh+P3DQ4KzK64/5 +%Gqj25ODMx3O27L+qgeo8OCjteBpiXqiBal8O8rYlr5w+UhPV+XAQ7jcpyvZq +%otqbg8YbRm0e+1ET1XlxUf/FX74j+2ih2p+Lnat6DO3cpEX5cfFl8FiLgLda +%qO4HLh4VGQ9ebqBNeXLxRVzfxQPctFHdH1z8ZJQ01uK9NuXLRV9pypgL/XVQ +%3S9dOXlM2Hf0kA7lzcVlwvVLpIU6qO4fLjYt9xi3c6Yu5c9FLc+P2U3Buqju +%Jy7+I9g47mofPnnwcNhL311hx/nkwcPoDudTfm188uBhvJ5BwPGtAvLg4d8O +%UWbPywXkwUOx8/LprzYKUd1/PBxfHFflXyUkDx6+CVq3a+guEXnw8O7Cjffu +%6orJg4cbfWIL4q6LyYOHt5Pfz5AF9SYPHnb7LBoSy+1DHjwMM3QOaV3chzx4 +%GGR6rLdhSB/y0MC/0/Iza7obkocGZk8JsRXuNyQPDVz1oXTUkkpD8tBANIve +%2telL3lo4Pht33pvKOpLHhp4v8+2qhEeRuShgTcW/jN/rE4/8tDAThPrP244 +%9iMPDZQs3DHHLqwfeWjg14FBwQJzY/LQQM9v376Y3jEmDw08qLI/vWKSCXlo +%otuIJIlzowl5aOLIwRn+O8pNyaPr/iGRbdX8/uShiQZZ3+dO6DGIPDTxYWd8 +%jCKjJ3loYknACkHIoO7koYmPex61bXymTx6aOH5MmMAxWUEemijWd/3DaKiC +%PDTR8L1NYa+HcvLQxChweatvIycPTVyy0ju6tlxGHpr4+ftTU/xPRh5aGP17 +%4m7jlTLy0MIBPoOzjg2UkYcWLo25u1G7XkoeWniJJ+x5LUpKHlo4u1D/wror +%UvLQwo+9dwYleEjJQwtv3g84NXWelDy0MP+CXbTbCCl5aGFa6qvF9TIpeWhh +%k7zVtVeZHnlooXsfk+jqED3y0EJN/81nZYf0yEMba819Xcct0CMPbXwdHjWF +%a65HHtoY9Ubv3ymdEvLQRp6Z7+176RLy0MaRL+6M7PtYQh7aaOx83Hn9aQl5 +%aGNV+czgTa4S8tDGluKpjzVnS8hDGz1qb+jqW0jIQxtNjZdpe+pJyKNrfQOu +%pPSvE5OHNmrsSQpsSxeThzb2EY/dXRYuJg8dLHGqds+7JSYPHdTP0f6ccFJM +%Hjro8yfY+G8Xk4cOLg/33Dx/uZg8dNBb9iQ2aYaYPHTQSDJimNRSTB466G/S +%mqpjLCYPHTTfGaJ5VywmDx107N1rZ2WLiDx0cGSywcfEYhF56GD47/XdbNNF +%5KGDzQ+99s+OFpGHLmbrTHzyKUREHrpYzdsd8e22iDx0cfSGqMmrzovIQxeH +%9c7hO3iLyEMXX05J7/F6p4g8dLG5V8m8UxtE5KGLi55effDOUYTq/VMXvxqp +%+jrYichDF2M8suNmW4nIQxc5OS79L1qKyEMX2wyyD04wF5GHLh75Ni1wmKGI +%PHQxooeBdJtMRB58/LtgtCNPW0QefMy//1dxVouQPPgYv2b31tau96nag4+v +%XgSecCoQkgcfl5ekGWhkCsmDj896xRgXJgnJg4+Dd52M1IoRkgcfT1gIuCvC +%heTBx3spQcl1T4TkwcdvB9Ymvr4vJA8+2rqeLgq7KSQPPl6/sci++qKQPARo +%5yX0/NNHSB4CXLpte1WRt5A8BHhO5BR2b7+QPAT4tv2R34W/hOQhwDcnF395 +%7Mb2DwFqu64c27iW7R8CDDgy+tnqFULyEODU0szHzYuF5CHA0nqN5KfzhOQh +%wPXNrYfPzxSShwCXcS1DL1sLyUOAbgdbPkVMFJKHAEuMv6tEY4TkIURJdIVo +%13AheQgx2DsmX9OceQix0Op+aJAJ8+iqqxjSPfswDyFO/2qdv6o78xCi6Vfj +%UBcp8xDioI8Hg70FzEOIs2dGit9pMg8hjjBJutirU0AeQuzu1/j2RLOAPLpy +%0fJw6V4vIA8h5o/au+ZFpYA8RJhitX7dthIBeYhwT8PVy9PyBeQhQutX872G +%f2f7uQgnjHs/aEQG289FuNEsbfTUVAF5iNDC/4iPe5KAPET4wcZD+1GcgDxE +%6DVP8kQjRkAeInx18bnTxggBeYhQ3zH0ZmG4gDxEqMed+JvHCwF5iNCy4mu/ +%biEC8hChysMuOPqRgDzEaFw9euvxhwLyEOPShA6bFXcF5CHGPtutA6fdEpCH +%GJ10Ok5MvCEgj6733mj9xdZXBeQhRpdHdpccLgrIQ4yTps4u3X9OQB5iHLiE +%0/+pj4A8xJguWu/SdJJ5iDFlgK/FzGPMo+s9G9Gt495h5iFGf4HLgx4HmYcE +%b1yO/ufcPuYhweDq0j2Gu5mHBL+9rjj55C/mIcFdaXXOCz2YhwR3xjsO0trC +%PCRYse+k6s0m5iFBt14Bhkc3MA8JDrsksF26lnlIcHPGWfPxzsxDgsbufZ+a +%rWQeEuSb/q+ppxPzkGBBt57+PZcyDwmKeesHmy1mHnq4qseQK+MWMA893JOn +%cXixPfPQw5PWAScP2jEPPbxzQFUQMpt56OH7IXOSamcwDz18azD56LjpzEMP +%df13NR+1YR56qCzjD86bwjz08EuMz1MbYB56aJt57a+gicxDD+tdtf82Hc88 +%9DDZ82xf39+ZhxT3h5z+x9SS1SPFpcX3W4JGMR8pxqc8gqkjWH1STH3s0TvP +%gnlJ0fSDzcIjQ1i9UozrnDxplDnzk6JzxSyrwgGsfik6ZZ22v2nGPKXoenXo +%59UmLA8pZo9qX2jRT0DnGSnaD18wqMOQ5SPFT52Gw1J7M28pHtedavekJ8tL +%irEjxuZeNBDQ/w9SfL3uqMehbiw/Kc77WuC4Q8H6QYobrrQI3GQsTynOsLm4 +%c5Me6w8pHtq1ymarmOUrxYhYTYO9QtYvUsw38Lx9is/yluKQXe9e+umw/pHi +%rPMh+W+0WP5SNJ46uyNPg/WTFDO+WFQJeMxDhqtj/XljOcxDhhkV3kUbOth5 +%RIaH95Q5/td1HlF7yPBskEonp4WdT2RoLbr+p0kznzxk6PV41sv1jXzykGFm +%wZX1z1R88pBh9/DWYzr1fPKQYa0lRC6r5ZOHDDfYTk57Xs2n75sMeZYPAntU +%8clDht3qvYP3VPDJQ4Z7jYOHFnbti2oPGbbt8Hr5ZymfPGRoar9hX1Qxnzxk +%2KNuz/cJRXzykKEiup/LswI+eciwMbNshmU+nzy66onIcnyexycPGRY6KEWQ +%yycPGTqvtDr64TufPGRd++W6IodsPnl0Pe+4Pqk8i08eMrx+uWz6oUw+echQ +%33SWX98Mdh6U4z1RfHb4Fz55yFFof7f7inTmIceTcycaaacxDznGrYvTfPSZ +%echxzb31lss+MQ85BuS2TpekMA85Jtne3hSRxDzkODzlwlLPROYhx/xdlncs +%E5hH13p8EuPqPjIPOe6LmjE+JI55yHGuR3CO5wfmIcd0T9v/Wb1nHnLsNcRv +%oSSWechxXuTV4qxo5iHHPSr90YFRzKPr+VkNd7zeMQ854qL+q5dEMg85rm41 +%0bWMYB5y5G7b1iF/yzzkeG7B/rJaJfPoWp972j9pr5mHHHdecVgWFs485Cg7 +%fbrzVhjzkGPaC9l2n1fMQ4HXE9eH73vJPBRoNUU0x+0F81Dg6CVyyernzEOB +%X6sbZQ6hzEOBKxx5bfOeMQ8F7iqT1c4OYR4KfPhxnfGsp8yj69z3tDhx5hPm +%ocC+kdut/njMPBTIvdB9rX0w81DgmS9xdQ6PmIcCqw6dPLc6iHko0GtC761u +%gcxDgTNqHhzeG8A8FLjK23n86YfMQ4ELx0zk3HzAPLrGFy6c8ew+81DgvLuu +%ofH3mIcC1xTuVRXeZR4KNLvnNIB3l3kocOe3yQeN7jCPrvWIXGPBn3ko8Nje +%KcNX3mYeCjRasrvl0C3mocDl/LOj7vsxD31U8aOPp/zHx/8Dq98lgg== +% "]]}, +% {GrayLevel[0], LineBox[CompressedData[" +%1:eJw92XVYVNvXwPGhmWKKDhEMbEUM1Ktrid3YCihiKxa2qIAoBnaDioqg2Ipd +%iIFSiiAlId3dHe/8nLXf+ec+4zDnnL0++3tm5rlmyzfPWaXM4XCKVTic//2X +%PbQjVt3cYFYdulf78aRs1+3g/sXs10Yue34Ivlu9ilpRXRX6potX6ew3p6D3 +%mIPB81Kq6PULcCLT8uSYz1WhV3z/PeDzLT+b7vfY31+HuqG1aZyzVaFls//3 +%L/6w03WFS8Ju9v5AMHs28NatZVWhvOSALaN5QbCxuKlu5WR2vHtw9IJdTI9B +%VaG9HU5/qe/1ENLfKIen67HjP4Zpa0yFxp2VoYHydycHPIX7g7qt3xhTSecL +%hulPXSKe+1WGWv17PIfsgLEJRRsq6fwv4Ph7nrbhf5WhX+t7yc/wEj41GBkO +%4lfS9byGjdHN0aNTK0LnKE4Ih78b3xp5r4Ku7y04mTSt67e7IjTb9X8HfAfn +%qnfacSdV0PV+gKqCRz9zdCpCXf53eVtCYAVf9jMor5yu/yPsteqMdHheHqrQ +%+ASeE0MylDzLaT2f4Exd3RV/2/LQyf9O8Bly7xvs72taTuv7DEFfs/Y9LC8L +%/d/Z5KcEbtZSf+MPZbTeryAJG3d0t3dZ6L/lJH+F/FuvdvxYVEbrD4PBwxY3 +%yyzKQk3/dzldvoGp46os2/pSmsc3cJDdtNj5tTR0zer/Pb5D65m2Bu+zpTSf +%cICEdKubjqWh/zua9uNwcNjeZdT1/qU0rwiYe0IWeaq1JLThf4erj4CUA15n +%N0SW0PwiIWxa4f1xl0tCx/w7YBSYHjlkpLyqhOYZBQ4TT4z4OLgk9N/leUXD +%U6zf6sgpofn+gKRLQzM5McWhP3/8ewDf763D8avFNO+f0PbXeKXGuuJQnX8X +%GAPqNjONXYYV0/xjIM7JqiFWpTj03+EcfkGBXmhit7gi8oiFoea/nddeL6L1 +%xEJiqvYdX+ci8omFlFNZbh+ti2h9seD+ZKJ7sloRecXBDY/pq9N+F9J64+CY +%hQ8/+UYh+cWBe9SQ/z5vKKT1/4bvzt+KL44oJM/fwD9oc9tZvZDm8RsuRK8d +%1iu+gHzjgXMoKiPxRgHNJx6W+IU/2r2hgLzjYYX/8yz+iAKaVwKExV/tf0Kt +%gPwTYM3Er5eUf+fT/BLA5dZI8fLr+bQfEsFpc9cL79bn0zwTwbjlV1nHsHza +%H4mQvN5NNkoln+abBEe2XJ239Fce7ZckaIjUXbXvah7NOwlS2/5YHFqTR/sn +%GVotM57vs8qj+SfDHu1xHWs7c2k/JYNd9439JkXnkscfaEvj6Usu55LHH2j0 +%MkqOXp5LHn/A13995p4BueTxB56knjxq0JJDHilwf6nG5wffcsgjBRYdsftt +%cTaHPFLgs9vTyEsOOeSRCvYf9h4rtMgJPfPPIxU0jvNVR9dmk0cqSIzUc90+ +%ZpNHGvwYtS324bFs8kgD2xubT36fl00eaVBQ9X1/smk2eaSDU6uKb0JJFnmk +%Q0+uWuOHl1nkkQ53annfrnpkkcdfUDq+bZXTtCzy+AtN+3+46OtmkcdfaNlj +%cufG50zyyICzi7UHpCzIJI8M8I/aYZRcmkEeGZAuWWr52yODPDJB9PSkNFAn +%gzwywW30om5T7/8lj0wYZlh0+euYv+SRBWbFaR0Yn07ryYLgbNNLm9ekk08W +%TEm6vdK9NY3WlwXlPw3/XDydRl5ZcG7bss0e3dJovVlw1K/oVP/XqeSXBd+G +%PJ4cPTWV1p8NxVMKbC0zUsgzG1JOO0494pJC88iGiPx7NrdUU8g3Gy6Kxzje +%vvyH5pMN6a1DL13q84e8s0HT0UAyISSZ5pUDw/bsX58/K5n8cyBsQ6bvvJwk +%ml8OOK5v331mexL1mQOcHxDwQz2J5pkDHaOFWaE+ibQ/cmDtfjO/q30Sab65 +%cMBlgWzShwTaL7mwdUzLk6QZCTTvXNihrZ3aLzOe9k8ubN7/Jsp5SzzNPxf0 +%cnydzynF037KheHdIq9uOPebPPJgdYKpBXT7TR55cClx4tqE53HkkQf+eht/ +%TB0fRx55oPJSctAnIZY88uDswgHWL1bGkkceCPaa2B8u/kUeeTBw29fBNZq/ +%yCMfbv1NO2vTK4Y88iEvob/n6kk/ySMfVreUCKtW/SCPfNCf/eQU/1A0eeTD +%3uhlBma3osgjH8oadgmuh0aSRwHIek/vofQ3gjwKAB1GDMpqDiePArDy3cs7 +%oxtOHgWgE/9+/73B38mjAAbqlZU+nvmNPAogWtnSqff6MPIohNJC7Y/WXl/J +%oxCiFoS/HHTzC3kUgsEj9ef+7z6TRyGI+L0CTiZ8Io9CODOieOGZ9FDyKISc +%Xe5/F2t9JI8i6Dzq0aMJP5BHEfxYfyqqzuUdeRTB+T32vLRbb8ijCIKsH327 +%HfeKPIrAvjN+XDfOS/IogvXBJqKSAc/Jowhix63hdtgHk0cxOP83dMgXyRPy +%KAbPoaf7DVv7gDyK4b8dU5/afAwij2JY/evarHeSQPIohlvzB00dsOomeRSD +%o/vxzqz1vuRRArsTZaVzNM6RRwl8nvJshfYUL/IogafF2fnCn+voflkCHwev +%0zgVtQUUHiWwMtHiid64I6DwKIG6D0fX+krPg8KjFP7Embw9e+wKKDxKYdPQ +%8eMfKvmDwqMU5t6o5Ol+DgSFRykkcDpDAw7cBYVHKchOtK6WTHgICo9SeDKA +%Z/pK9SkoPMpAd+noUVGFwaDwKINIx7KNrtHPQeFRBpaW81ISnr4EhUcZpGWU +%ds649BoUHmWQ9KjT5sL+t6DwKIO+r3q1c1a9B4VHGTzLyf4weWYIKDzK4Rtu +%MRpoHQoKj3IYZZf3adnZT6DwKIe/qycen3LkMyg8yqFL1igfbbcvoPAoBxU/ +%G+G77V9B4SF//ip+KW4IA4VHBbgOM88UrvwGCo8KmOXzNPazw3dQeFTAgfqw +%9PL54aDwqICffYpCvGdFkEcFfM/ZZL17SiR5VMDxSV8Tt46PIo9KaEjs8mgG +%RJNHJej8mvV8+qgf5FEJu0KMcvyG/ySPSpjvn198dkgMeVRC+fvY86qDf5FH +%JVzYaFAxQD+WPKqgclvtm02XY2k9VTAtN6mbnl4c+VRB7dlrZXqX42h9VTB0 +%4fusgXq/yasKmhcmX313+Tf8W+7qKjgU+2D8Lv148quCHqoLQ375xoPi/iA/ +%vn1/tzFGCeRZBUPumZ9W9UugeVTBjA+LDfRME8m3ClaHR5697p9I86mCbDuz +%Oyu7J5F3FaTd/V1+PiiJ5lUNE23umRX0SSb/ajhuONbmiPx7jGJ+1eCghIa+ +%g//QfqgG3xNPHgte/6F5VkP4l+3hBaNSaH9Uw9NUx3Fln1JovtXwsNqzfMfE +%VNov8ufvT2k6/EileVfD/KcvLh6ck0b7pxoM9Mr366ek0fyr4aeT3ZZWx3Ta +%T9VQHBI1aW5hOnnUgFr40sdKm/+SRw28nhxwR6nxL3nUwKrOX3FH3DPIowaK +%DI/lzdLMJI8aWBIWl3jgbCbtvxq4Mi8x6I4wizxqYNyJn2qX52SRRw3EHxUM +%iricRR7y4wVd0xz5N4s8aiBn9Phbf82zyaMGVKpGiB+szSaPGkh92f3Yg8fZ +%5FEDvMXqFcV12eRRC5xxO1rmjMohj1r4e33qT65nDnnUwtnYX5dqInLIoxbm +%LbpiqinOJY9aGKqnKVi8MJc8aqFQ8mtp4fVc8qgFu6v+V98V5JJHLaRr3Twe +%PSCPPGrB4m3IQY1deeRRCy3LNqqvD80jj1pQ43HaLDXzyUN+PXfjK6xn55NH +%HTgdWbLK7ko+edSBpvY9i4jcfPKog3737p850L+APOrgS5vw0rZdBeRRB/7O +%2c+DPheQRx0E1AgdtQSF5FEHPl39e6csKCSPOkj/o4Qp/oXkUQfz/Fc/E5UX +%kkcd3LbS+3DRuog86kB0eELEskNF5FEHPd1X9F0SW0QedRAvOvr9rXExedRD +%2LvqyCXrismjHjbN21jR81UxedTDt4DYpctUSsijHsww7VyYbQl51EPkWusd +%ftdLyKMeYkK6C26WlZBHPdiEZvatGVlKHvWwIXj7voPHSsmjHob0DN22608p +%edRD/rzaO18tysijHjr87p5evKuMPOoh/rvznOnhZeTRABt87ilt0isnjwb4 +%41I+qHZNOXk0gJnJjx0Rb8rJowEe1RzrVcStII8GaN88qmKRfQV5NED+144/ +%Ux9VkEcDTOAZzdzWWUEeDfC8JHWj6pxK8miA99WDs7MDK8mjAQ7ZbpCNaKok +%jwYI/FRlGNejijwaADr2rI2ZVUUeDXBqit7Quj1V5NEIz5qrtNYEVpFHI1zx +%+vAzPKaKPBrhq75j8IHmKvJohIvaSeUruleTRyNYmxuZuc6qJo9GKFvNmRDq +%Wk0ejTAxqb3PpDvV5NEI+guqVIrjqsmjEZRTalzT26vJoxHMqkdpN/euIY9G +%2P22Z8qwBTXk0QidX3LLP3nWkEcTcLtnmxx+UkMeTbDQ4eqU1ek15NEEM/c5 +%V5zm1pJHE3iPW8ipH1ZLHk1g+ZRvfnJlLXk0gavLI32Hc7Xk0QQ8aC3a8KmW +%Pj+bIKET1j+vqCWPJlCa9PLGCJM68miCQwYTZqpOryOPJtBqfSZrknem8GiC +%GXYux0bcryOPJkgZ3e/E55Q68mgG7V+/9m3g1pNHM9TxdtusH1FPHs0Q5587 +%JmhdPXk0w6TdoweNvlJPHs2w+/Sjbs1R9eTRDKbJzq86WuvJoxlcZLOU5/Rv +%II9mUNpbujhnaQN5NMPZiMaAkDMN5NEMWZMHL6j40kAezZAU8mPIKvk+Ung0 +%Q2FHjptJr0byaIGVPXtv7WnfSB4tMMVr5FuXU43k0QL9HhWNNZY7KzxaYETl +%S2N+QyN5tMDYqEvR0/o0kUcLvBo/7savpU3k0QIbe8R+DDvfRB4t0PfdzB6N +%EU3k0QI7Fm5o9OpoIo8WCLFYOF8/Q4oKjxaIfXJO716pFBUeLaD5JF40q1mK +%Co8WkG36XifUkKHCoxVarG0PpWvLUOHRChmaHZN/mstQ4dEKCw+Z7Pw5SIYK +%j1b45N1rS9EYGSo8WsFhsINJlxkyVHi0wpqVnGtb7WWo8GiFQ5udshPWyVDh +%0QptGxdtddgtQ4VHK1SEZ5nUHpahwqMVat2dBPcuylDh0QqOlrsa9gTKUOEh +%//vpD6rXPZehwqMN7IOXzHX9IkOFRxvYuukdvhYnQ4VHG2SmvHEpzJKhwqMN +%rH4ccJxYJUOFRxv0O9/o+aNThgqPNjgwrO7ZZpE2KjzaYGHiEu3hptqo8GiD +%8+laa7oO1EaFRxu8v1jxujtoo8KjDUz6en6ZOUubPNogfH/ieD9HbfJoAyja +%0SLdok0ebVAx8uqpJx7a5NEO626UqK09q00e7VBuUpc46ZY2ebTDD2fJ7kXP +%tcmjHQb7LjA4FqZNHu1wYULt4cJEbfJoh9l/zu7dXqhNHu2g17Ww26BmbfJo +%h2U3eF15fB3yaAdlw/fnzU10yEP+3MZnxcqBOuTRDk3LqnwSx+qQRzvMeZw4 +%fc88HfLogEQ3t6Xz1uiQRwcsueMhXemqQx4dMNxNsvjxSR3y6ICSEaeDevvr +%kEcHWOxy6Z33Qoc8OqBVvfJBSoQOeXTA/uH3tsn+6pBHB+ztlr/0WLUOeXRA +%26WGmxPUdcmjA5Tm9soZY6RLHh3gZDq2+PAgXfLogFmrHs/Qm6hLHh1gddNA +%udFelzw64YptnorJVl3y6IQ+1UOWXTuqSx6dsPH4VgOXG7rk0QkhLuU9/F7p +%kkcnpFefi+kVo0senaAzwfqdYYEueXSC2q4wvmOHLnl0wpi/Wp56enrk0Qmn +%BY5lYwbpkYf8+ZGlvIQpeuTRCcsObXuXukKPPDpBd9vgCkc3PfLg4L4bhfdd +%fPVoPRz8kHxCvf2FHvlwMPY294xxnB6tj4NuEw8e+16uR14cNEy2tdTh69N6 +%Odir4Ty3vZc++XEw7WTKxzuT9Gn9HDR+YN2larU+eXIwtCxGWH1Yn+bBwQil +%P88ig/RR8XuGg9OcvVLXRurTfDiof23AqOJSffLmYPLNgPEzRQY0Lw4K04Xd +%Aq0MUPH9gYOcZuONnYsMaH4cDBKHa69wN6D9wMFxyz28iu8Y0Dw5uGLtg+sn +%fhnQ/uDgn54R8TubDWi+HDzQ2NF6s7sh7RcOWnIe2JrONqR5c1DdL+Zoi5sh +%7R8Obgy4u6zbY0OaPwffzEk79iPDkPYTB20M1B8ki43IQwknfM4Zaz3eiDyU +%8HAT72L8biPyUEL19xdFux8bkYcSbsxtHPktz4g8lLDzmajFxMiYPJQwymRD +%6WtbY/JQwqS5QeXcQ8bkoYRio/jMzmfG5KGED8x9z0xKNSYPJZzqeMSgvs6Y +%elPCaxNmDBe2GZOHEo7fYdTVqMCYPJTQPnZyq/NdY/JQwkpf9Z+bRxqTh/z9 +%vMC0l1eMyEMJhzyueFUXb0geSsiPFJ01b2YeSmh0vXfJTWPmoYQyk6FqxtP1 +%yUMJ7XY9Vg47qUceSng0+kLj8Fxd8lDC0dkvSmymsZ7lr7tVeZwMY/cnJWyo +%35//aLoOeShh1/aYyxOztclDGTt8Bl1K+//7rzKqOz/MaeqtTR7K2Ffz0Zf6 +%VPb5qIy/3+/9r/Ac+3xRxm6hya2/bdnnpTKGe7wWWGizzxtltKn3GV6eJiUP +%ZdQN6xfWFiQlD2V0KDJKu7RLSh7KOMki3mX6VCl5KOOOlgwDblcpeSjj9skD +%T65pkpCHMp5eobPVNF5CHsp4Yu2CublPJOQhX1+QqdGDUxLyUMZXt4/b9N0s +%IQ9l1MozzbKdLSEPZSzh+quVDpGQhzLe7TjSu8hAQh7y+Ri+knTjSMhDvl7t +%tEzfQjF5KKOSUso6vVgxeShjd+eFPrffislDGfd/abUyDhSThzL2i6vftOW0 +%mDxUcIy7hZ/PXjF5qGBj4rTxXmvF5KGCz6fbv7VcICYPFZxpO7DywHgxeahg +%c8VNHXcrMXmo4M//bjiKuonJQwVbu8WsHCkTk4cKtvCvDstWEZOHChbVP1Dq +%XSIiDxW0sbkzY9dPEXmooJnOw1sfgkXkoYJ8lWnd4i+JyEMF7bc0xETuE5GH +%Cm6/8PTjiRUi8lDB9NERyWrTROShgufn3JwxzEpEHipo+XLsXB1jEXmo4M0Y +%8UM/NRF5qOCms35XvlVqkYcKBjtZHz2YqkUeKqjT8Wzat29a5KGCNeu7jrwd +%rEUeKmg4++bkTj8t8lDBBKt7L7K8tchDBeFQ7dzRu7XIQxWnDgmMEa/WIg9V +%jPT2rbadp0UeqvjNs2O/yjgt8lDFDM/T3C6DtchDFc8Ihx/yMdMiD1XUv3Gj +%p6tEizxU0a7s/In3SlrkoYondYLHz6oRkocq2jxetnxsrpA8VNGZa831ShCS +%h/x1/dm3TL8LyUMVh/f7O0PtjZA8VNEqqN9ly/tC8lDFiVHCF8+uCclDFR3a +%ElN3nhaShyre6Ts02sNTSB6qaB2f6xmzQ0geqhhq+Mhr8ToheaiiZnjnDosl +%QvJQxcMfDacMnC0kD1W8cWe01dYJQvJQxc04e2PFCCF5yOc5v9zjzgAheaji +%f86LOk53E5KHKo4Xhbk80xeShxq+//7KkaclJA813DvtcffjKkLyUMPO1LGb +%xzQLyEMNh8ycsbd7pYA81FBzafC4ofkC8lDDZTqLTm9NE5CHGpZ83dUtM05A +%Hmo4OfI+uEQIyEMNt3vUDx0UKiAPNfRatq639JWAPNRw4+s6E9NHAvJQw1yl +%J5NnBwrIQw1/Lr3RHnhVQB5quDXe84b5eQF5qOH333bdQrwF5KGGK/8ILT08 +%BeShhhseZoU4ugrIQw1jq9+kLdsqIA81NNo91v3QegF5qOGLVhXJ9+UC8pDP +%79jYwB72AvJQw69P6sJuzRWQhxo+5R+fNma6gDzUcMfb/a4N4wXkoYZ/Ays6 +%I0cLyEMdDVTjo94ME5CHOoYtGZjyaaCAPNSxxwZeeVYv5qGOylayRV3MmYc6 +%Kk3bULDNiHmo49PDe3pkaTMPdczUNLBbo8U81LFzrOC8iibzUMcgt4jVL5WY +%hzqeqNRxcW/lk4c6JmXMVXes55OHOu6rThsxr5JPHur4M2BM45JiPnmo4xKx +%uoVrLp881DEw6q7347988lDHN00bDjQm88lDHR1XPmmx/c0nD3UM2TGr5f0P +%Pnmo41uLfPPR4XzyUMf3XScm/vjMJw91HL0xw3jzBz55qOOaWbUt3V/zyUMd +%v0wzby8P5pOHOvZ0con48pBPHvJ5Tc4/8zCITx4aOL0gyuz2LT55aKBHbdzG +%h3588tBAFeWOO999+OShgYPuDl1Xdp5PHhq4T999TI/TfPLQwAdrLSY7e/PJ +%QwMbi5ZqhnjxyUMDV6dzLc0P8MlDAys7V/c6u49PHhqobpPqJN7NPDSwn8Y5 +%U79tzEMDNV+knB+6mXloYEfTy81/1zMPDYztkht/ZjXz0MDD4Sufzl7OPDRQ +%X9u0xWQp89DANYMMZzcuZh4amOxU/Tp9PvPQwL4R9f4/ZzMPDez9dtLh6BnM +%QwOPTg6I+z2FeWigmv3EcQUTmIcGcvu7RKjaMA8NLDFvshw4hnlo4NZjQZdW +%jmQemphpvsY7YBjz0MQV6v0elg9mHppoNeveoLEDmYcmzk0/GHijL/PQxEXh +%uhyNXsxDfrzpAR57ujMPTUzqca9fXVfmoYmhb2o/7DZhHppoPO+JqaYh89DE +%6w45//nrMg9NvBf6MBNkzEMTF98Z3a9UxDw08THf8JafgHloom5tSuICLp9+ +%f8rPd6StzEideWhi3TqxZrEy89BED8f5Az508shDEwNd327wbeORhybqhF2f +%tq+ZRx7y9RmE9Hdu4JGHJg7Jq3uyrJZHHpo4zcZCd0kVjzw0ce/X+Sucynnk +%IZ+v8fwZziU88tDESouOcrdCHnlw0aBvbqRvHo88uHi83XTju2weeXBxwfTq +%1twMHnlwcbLm9q/SdB55cDHt5rlBU1N45MFFvp2T9+EkHnlw8dQmA6/oeB55 +%cDH8nN4unTgeeXAx0/Ne56oYHnlw8cql5SEfo3nkwcVjmya3GkfyyIOLXX0m +%9T3wnUceXGzWbU8s/cojDy6WdB8w3+Ezjzy4+GDw+te/P/LIg4vWx9OCZ33g +%kQcX3wgmmce/ZR5c1N8cKXR4zTy4GCBy8C5+wTy4uK1gw4V9z5gHF98GCLrq +%PWUeXOzhHVf48hHz4OKQ08eP2D1gHlzc3vKsWfUe8+Bi28bAvy/uMA8epsVX +%aq8NZB487Mrv9sbsFvPg4f2tKh8zbzAPHkbFb7UN9GMePMzQnvZjw1XmwUO9 +%BVuPjfJlHjx0nucUL7zMPHj4ouBeVP4F5sFD4cQTsz+dYx48PLX4euutM8yD +%hzu52hVHTjEP+XUc0lbfdoJ58NDnQr27kzfz4GGu/lWzeUeZBw9DHk57PP0w +%8+BhdPnpBZMPMQ8eFo7JmjDFk3nwcNecFS0zPJgHD7+nxzfNd2Me8jnv1lJe +%vo958HDgJf3D21yZBw9t1+isOrabefBQypEW+O9kHjycXTN04aftzIOHomsj +%qrK2Mg8+ui0xDldzYR7y+9Cd8V0HbmYefAy4UHnJfiPzkN8nNrhPOuHMPOT3 +%sbbi+Z/WMQ8+frC/kdO0hnnw8b86s19Wq5mH/L543/zPtpXMg4/L591seLmc +%efAx92fuxbZlzIOPYpHD3YmOzIOPEVM6Us4vYR58FF1WM8y3Zx58XNX+NNPa +%jnnw8XifAMGZRcyDjxMnqF8pWcA8+HhS2WP2lPnMQ35fvJJ69f5c5sHH4RyL +%3lpzmAcf0xd9qdlhyzz4eFWUbJY9k3nwceDjZp+ZM5gHH826aPf+NI158NGg +%aM9Iq6nMQ/658tYp+v5k5iFAs0dzw7tNYh4C1Lh3a5P/BOYhQLcOpzVm45mH +%ALd0fNALsGEeApxun7Cl91jmIcC7pTJRMDAPATrH2W3+bwzzEGBS6C7zqP+Y +%hwA3+ElLFo9iHgL8o5TBqRjBPOTfe0uyfQ9ZMw8BxvYP5pkMZx4C7GNjFfZ6 +%KPMQoP9Tw/L5Q5iH/Hvfw8fRDYOZhwBfF6X4+FoyDwEO2NeUPGYQ8xDgs9zZ +%WDCAeQhwtNeGfWf7Mw8BRs8O7j6mH/MQoM+kdQ3lfZiHAPe47hx4ozfzEKD9 +%1GPFc3sxD/n36h7h/XgWzEOAPz73bP3cg3kIMSanydWtO/MQ4sUTGVUjuzEP +%IX7RXb6t1Yx5CLFS1yL3Q1fmIUT3wDfOB0yZh/x30/4818ldmIcQh5760CYy +%YR5CPPSpMyPNiHkI0XLryJV3DZmHEBOnJq3bZcA8hPj2L+f1ZH3mIcSI+Dgv +%Iz3mIcQFrqHSah3mIUTrETFvw7WZhxB7Buzz8JcxDyHOzR9lsF/KPITYlqGe +%YCdhHkKc/t3+ywgx85DPozM92FDEPIR4xpQf2S5kHvLX7wXeyhEwDyEunryU +%E8VnHkI8V9hd/TmPeQgxuM/PD35c5iH/3edxcI23JvPQwhGnFlvs0WAe8t/5 +%9SZX1qkzDy2M9HB+YqfGPOS/s9+VBc1UZR5aaF615MJ4Feahhd57M9tHKTMP +%Law+btIyRIl5aKGL2oIRgzjMQwvPLVxr2a+TSx5aqFbb9rpPB5c8tDCxxapv +%33YueWhh6YjlZv3auOShhYVHuv4a2MolDy3c7jkwf0gLlzy08EzQMruRzVzy +%0EK35W6fbJq45KGFuW0DNKc3cslDC78Vx6QtbOCShxYKVk1atFL+uavw0MJ3 +%JywOba3jkocWFl+J6eJZyyUP+Xq9hxy8UMMlDy0cr+kfcqeaSx5a+OitrOhd +%FZc8tLDPz+9r4iq55CFC3cSka0UVXPIQYR/D1c5K8ucKDxHOv6xhZ1TOJQ8R +%ThumNmF4GZc8RPh+bMvcefLvDQoPEc749DhnawmXPESo4pq8/1wxlzxE2COm +%0fFFEZc8RLindO/VpELmIcIt3OdzWwuYhwgLB9w50bWAeYjQr8T29cR85iG/ +%3guD52zOYx4i/Har+bNPLvMQ4X2Tl9Zfc5iHCO0GaAytzGYeIqySFv0yzmYe +%Imzt+Nx3Wpb8e9M/DxF6fGtw35vJPER40v7gvocZzEOEO9y2xGX8ZR4ilIQc +%PSL9yzxEuOqS7bpJ6cxDhJrfbdfuT2MeIowcYnTlRSrzEOPie38sylNoPWpi +%HDql++yeKcxHjJMD9qx3+sNV/L4SiTFj9PrVfsnMS4zCLocbUpNovQZiHJQb +%9Uk/ifmJsf/GH6sXJdL6u4lxn3FXb58E5ilG28lvrFPjaR4DxGjS093TJJ75 +%yq/vcOVJp9+0X63FCHc44XfimLcYI3vN61keS/OyEaPHU9fbVrHMX4x/0qe4 +%7/tF85suRpedvZ98i2H7QYwOa5MniWNongvFeHez5lr7n2x/iFG6qSwj6AfN +%10mMu33E3vXRXPr/M2LcmfLOcXw0zdtZjKFFtyaej2L7R4xTQxKm5kXS/HeI +%Uav+cvbQSLafxDjs774DRyPIw0OMF6dKPNPD2f4So9L7oNGDwsnDW4xL736x +%PvydPE6LUfy08EP6N/K4IMa9nQNdhnxj+0+Mzlofu58II4/rYoz1v7Qr/yvb +%j2Lsod3zGH4lj7tiNL/qtfXqF7Y/xVgbURTR+Jk8notxTop9+bzP7P4hn2fR +%nsPPPpFHiBi9v8ycLfnE9q8Yx7ROt3YJJY9wMdpMKNz7+yPbz2J8bRmYN/gj +%ecSJ8Zn3tbGXQtj+FqOeS6R58wfySJfP67j7tSUf2H4Xo07WyxVf3pNHgRjD +%v3Gn9XrP9r8Y59343n7qHXlUiTH54CnlhresBzHyup8/svQtebSIceIky6jw +%N6wPCfImunEGv2F9SHB5w+kF116zPiT4yajIU/M160OCI3RTi7a/Yn1I8P6B +%rvNyXrI+JHhmXvDbWS9ZHxLcmnd7QOgL1ocEN2+z2j/gBetDgpUOlrIbz1kf +%EhRJTs8TP2d9SHD6l/SOA89YHxLM1vXIqwtmfUjQRFY+Ym0w60OCQ66V6aY/ +%ZX3Ir0fXPmX2U9aHBK8Orov+/oT1IcGVfNmm0U9YHxJU7Xzj+OIx60OC5Q+l +%yX0fsz4k6DRo95/AR+x+KsFeTU+bTR6xPiRombYs+vJD1ocEfU+UBkgfsj4k +%aBqy/NnJB6wPCe538+XzHrA+JDiH093/8H3Wh/z6ZitPUr3P+pA/r3dw8rzH +%+pBghVvIcqV7rA8Jrg3r2ONxl/UhwdATlQ2cu6wPCRZPNz1+IIj1IcHMEVwP +%5SDWhwTx5gMrrzusDwmm3+NlaNxhfUiwR9rXTO/brA8J7ozO6SW6zfqQ4MJ+ +%M36dD2R9SHCFZuZ4g0DWhwSl16q8bgSwPiTodUWrqEcA60OC9kX3vz68xfqQ +%yH9PRlUNucX6kOAMfdd+If6sDwk6SAP6TfQnjwIJTvjoZhZ7k/UhwbFGz/Xt +%brI+JBhXku6fd4P1Id9PE/c1b77B+pBgxrQ67bbrrA8pug2fa3f0OutDik/s +%dq7Quc76kOLMhcEzA/xYH1I8YCrbaenH+pCiclBYbug11ocUc0x9fs26xvqQ +%4rqI/g8yr7I+pNhRa6W+5SrrQ37+RWeclK6yPqTo02XbwPNXWB9SvGp+jtvj +%CutDig8Ww7nXvqwPKR4/kxs71Zf1IcXYniYLM3xYH1IcvXhb3VYf1of89cu9 +%MjR8WB9SBM/lln6XWR9StN07c7bVZdaHFIc2+a2JvMT6kGJJxeHJyy6xPqS4 +%I8o2o/Ei60OKl/PcY09fZH1IsV4y06zXRdaH/HifNJs/XWB9SDFyx9M3dhdY +%H/J5j7C2rTvP+pDiO9kjvdPnWR9SXPGr5mDv86wPKf52F+K3c6wPKQq2meg6 +%nWN9SHHMqoCX7WdZH1LMU3Oec+Us60OKTpsLXYafZX1IcXF08qLEM6wPKe50 +%SF+77QzrQ4oG5zdNlp5hfcjnYf94TvBp1ocU9z27mmZ7mvUhxe4xfl2rT7E+ +%pNimOePz2VOsD7m/Wr+8wadYH3JfTumwhJOsDykeGzR0z86TrA8pJrmHb9I/ +%yfqQ4vh5zRbvT7A+pFho88pu6QnWhxS//ne9WukE60OKfbgD228fZ31IMfPS +%lZCpx1kfUvQYc+ZXpTfrQ4YXu3wruODN+pDhbmn/8yO9WR8y/HZhQlvWMdaH +%DMdm7j5/5BjrQ4ZvRjvYDTjG+pDhzqcLkhOPsj5kqPLMInX/UdaHDCP9Hkzs +%eZT1IcOS1idhMUdYHzJU35aXt+sI60OGsn6qaHaE9SHDISk7rKIPsz5k2K1o +%ydYdh1kfMpxnOrzG9DDrQ4ZDBeP+RHmxPmRoXmG/facX60O+fjX1ueZerA8Z +%+k7Xt4s5xPqQYbD5r7+uh1gfMrwQvJHb6xDrQ4b97cVWiQdZHzIcPmvBE8+D +%rA8Z3r/829jyIOtDhjru+ksyPVkfMsxcN8zllCfrQz7Pvu8jRnuyPmR4t8fg +%JeUHWB/yeW1sWuB3gPUhw6kfg/rNOMD6kKGueVpnuwfrQ4bTnxkrPfFgfchw +%WlwNd5kH60OG1qXyu4wH60OGASPqm766sz5kuMP53eqd7qwPGbo+K2ro5c76 +%kGGb106zdDfWh3z+41f7nHZjfchQeewUu3FurA8Zbnru4dq4n/Uhwz/n3oc9 +%2M/6kOHxVe+PLNvP+pCh1bCJ3jr7WR8yTHjbMjl6H+tDhhmZCYke+1gfMvy1 +%wSV/2D7Wh3y/nMsYV76X9SHDo9OOBwfsZX3I3/986F27vawP+d/r6CyU7mV9 +%aGNE04ukSFcu/h+i+QLA +% "]]}, +% {GrayLevel[0], LineBox[CompressedData[" +%1:eJw113lczNsbB/BpmmavZokkW7cI19ZybZfOI5VoVymKJCJbiVC3ouxLJJWI +%uOLey00JP1uYuAiFlLQo2hftm/b6DXOe7z+9pu/M+Z7n8z5zzjM6a3yXrmMy +%GIxOJQbjx1+8Ul26/o4JaJf9oZG8qCxoP7EvejblzHx8HU3Kp3Tv3KfSLrs/ +%5kC9w/0EYvLPcO7Gt230/hUSCL43zWPaZOfO/riukUvnfW4OX9lG359MLp2e +%4fRVr03W4PDjP6nkOLep5s+GVvr5O8R/ZtyTlXdaZfz8RL/5/HvkN9fQC5rB +%rXS8B0R9W1JLxsJW2ST3k886Jz4i3df91u0UtNLxn5CuwPe3dXNbZFfkn85P +%TCdX6xcp551roc97SsaeXSA7sKZFZvTz+o+cu2fBNpjcQp//nGw94u32sqJZ +%9l/nRPkTXpBj6y1fGm5vpvPJIAe1JiYnMZtlS38+8BVJ3xWvPzOqic7vNXmX +%EvnwjU6TrCzox4BviOGo0dt9UhvpfLPIRH1zkfaCRtm2H9Pze0vYlebXKrIb +%6PzfkfS0E4bpqxtkCo1sovf0TMejlnpaTzYxStlZk763Xmb58wEfiEWWzrh2 +%UT2t7wMZ+76vYOaf32Q/nuZ+Moes+8e++ILBN1pvLulmDSpNe1Yn+1lOfi7J +%2q+kW7C0jtb/kahbvitKrqiVjf0xnTF55G7pwekXd9TSPPLIxOEub9JUamXr +%vX9cn8iI7SY3hWdqaD75ZJnBKub+iTWyH6NpJOeTzZuAO/FhNc2rgESvbe6u +%saqWff8xXGcBufAx51FuSRXNr5DYvXROH+ZXJTP5OWARmRzfp7qDWUXzLCLW +%He4uddGVsp/TO/CZBCzmBH3Sr6T5FpOh/7UEZD2skL3N+nEVE6dLHZ6DNhU0 +%7xIyI377/ZCyctmwnxP8QtiJLrGTAspp/l/I4/P321R45bKfw7l/JfY7Pe5r +%XyijHqVkzICdT4JBGa2nlFyws3TUfllKfUpJsdLhEqZbKa2vlPS/D9zlWPqV +%epURrdyQ2z6Cr7TeMtKt22sTP/ML9Ssjq13q/FevKaH1l5M7ET3VshPF1LOc +%kDuSwZK0zzSPcjLX0C00qK6I+lYQ3U3SeDfNIppPBdGPdB78xaKQeleQw8KO +%x3sCCmhelWTqNqdxm6/mU/9K4mhePz4z7xPNr5K4f+v9PMj+RNdDFel6wdRM +%nJ1H86wi1zh3H52OZoNifVSR624L1xgks0GRbzXZ8i79iyyDDYr1Uk36NijX +%TStjgyLvamL6yND/VC8bFOunhlxcJ/izUMoBRf41JInt/og3lQOK9VRDLryI +%mDDVggMKj1rSvuLwQkMPDig8asn1lPDrWrs5oPCoJbuW/6vaFskBhUct0Q4X +%cFKvcUDhUUfMohOuOT3jgMKjjqiP6tSsLeKAwqOOpAdUqy1r54DC4xvRyOmX +%3RZwIfKnxzfya5ZdbKcuFxQe38jbm0kfpfO4oPCoJ5n9jp1jnbig8Kgn+5M4 +%9fzNXFB41JNAPe7Bin1cUHg0kKIzyeHR8VxQeDSQB8Xx18ff5oLCo4Esn9v6 +%9NIbLig8GoljSnrGYBkXFB6NJOiwh/eMHi71aCRXhwseWol41KOJbMn0Zi3S +%51GPJpImWOM8zIRHPZqIzW1OZoYTj3o0k4TR0+4s38SjHs3kaKnV8PQwHvVo +%JiwDHw+lOB71aCFWMIM1NplH62khUS9reRrPedSnhVgbGJ//XMij9bUQVePI +%wh3NPOrVQiKs1OrqWHxabwt54B+UPmUkn/q1kFcjWx1Np/Np/a3EyPjRhwlm +%fOrZSlK5r5vyXfk0j1bCuFbW5rCFT31bSalhsvf5MD7Np5W0rPhNIymGT71b +%SXH1dM2Qa3yaVxvxbw3J037Mp/5thPVCemR3Np/m10ZeXnTNiKngg+L72Ubu +%elZxt37n0zzbSO7iPZUqPAFdH21EZ6bqTXttAc23nZjbs7udpgroemknc097 +%zFcnApp3O3kxm1+xw15A1087+T1jUVCkp4Dm305m/hK/aqm/gK6ndhJ0+qHa +%w3AB9eggy2sdZ72NElCPDtJQlKsTdllAPToIz1qq/y5VQD06CPu37G1P0gXU +%o4McKw2fZ/FeQD06SEZtZLRXiYB6dJCjG7duEdULqEcnCf3rz9k23QLq0UkK +%HErbR6gIqUcn+Tt+dukmsZB6dJKsC8b3zEYLqUcn6XltrHt5opB6dJI6rvX6 +%UCMh9fhOVmUM+uTNF1KP7yQyqOvBP4uE1OM7ebvdM6zNXkg9vhPd3P2bUpcL +%qcd38m6q/5QyTyH1+E469l2Yvd9HSD26SB37zalYPyH16CLrJ++ql+4SUo8u +%EqezIHgwWEg9ukiQC+uEY7iQenSRoqilT6SHhNSji+zZ4Fv7+zEh9egmH9Sm +%2r0/IaQe3US3503em1NC6tFNJp2McjOIFlKPbjLayrKOGSukHt3k5DdG3IIz +%QurRTbQttkqb5K8VHt0E3L5bCeKE1KOH7LnrnHNOfl/h0UP+47jMuxSLHj1E +%dbxgkXYMevSQvcabWBqn0aOHzDDfueB4JHr0kD7fuqDDEejRS3Z1nq/jH0WP +%XmJw+Z3WiIPo0UtsjmnF3AsT0v2yl7wemPBLQzB69JJTUu20tF3o0UsKTQaL +%J/mjRx/JC1N78Ptm9Ogjz6Z5CLrXoUcfMbw4RcvLAz36yGpXJdtwV/ToI9yC +%m87uDujRR9JWbwvrWIwe/eTS+3y2gyl69JMvo6bl75yLHv3Ex+PTk42G6NFP +%vubv/NNwMnr0E795re45OujRT9gr8sfba6FHPzENeTGYJEKPAfIhKHKoiYMe +%A2TNeda/2kMC6jFAjpjxcmZ+F1CPATL54f63CxoF1GOA/JUOdxdUCqjHAPFe +%/+z4/M8C6jFIApqjOUY5AuoxSMIuGSye8FpAPQZJV9XacaPk32+FxyDpfMxa +%NeyegHoMkqf3v/2nkYz71SCJPrabMe4q7ldDJDUmjGt4HverIeIoOKu+7DTu +%V0Pk1OQRJYeO4n41RPr3Maa+DsP9aojcC7YyHBWI+9UQmfXilvp+P9yvGDBB +%i1U7uB7rYUD7pkTdUx64fzHAMMi9ZI4L1seASZ4uen22uJ8x4KXppLAPFgL4 +%Wa43A/ROTD3z0gT3NwbUloqO58wU0P2BAQEaVqo903C/Y0BC2JK7s/QxDwbY +%JwiXxY7F/U8+fuHBa6ojMB8G9J4rq7oswv2QATc812gv5WFeSlAyh9czion7 +%oxLMspycxOrj0/yUABqjVTkdeH4pQaL5yJLxjXyapxI0vp/DW12N55kSJI3R +%s7n7lU/zVYIp1e6dEwvxfFMCW/u7pXdy+DRvJQh+ZfjOPQvPOyXYslY8oPOS +%T/NXgm0HC58y0vH8U4KeUCLpesCnHky4t/d4GP8OnodMmLUuzW9mMp96MCGx +%57ZO8D94PjLBOfn60+LLfOrBhJXR622WXsDzkgm/ivcwq8/wqQcTtG1SNkdE +%4fnJhNa7anE2EXzqwYSooazB8YfxPGXCwemZK4ftw/6ACQsjR87QCuVTDyY8 +%VdeuNw7EfoEJSapm1T47+NRDGeIfx7Ym+2L/oAy/eV2yUd2EHspwYkdeQag3 +%eiiDkW6OWGUNeihDjdGXlxdXoocyjH2X3WazHD2U4Y1txr9qzuihDJ8fP3lW +%YY8eyuA/+GHtG2v0UAb192eVn1uihzIE2M43yjZDD2X41M1PagD0YIF63PhC +%7fnowYJXGr7PV8xBDxa8WP5r/7+/oQcL7G61HxUaogcLtoYdbv9jGnqwYPtS +%k4yByejBgojzR8Mi9NGDBcPZA+ZT9NCDBQZeB1IKx6EHCz5/U74YOxo9WLCs +%rt3McyR6sKDu2j3vOZrowYKKPz6+HquBHirQo9PDlIrRQwUCnM9HaaihhwoE +%dwY80BGghwq8rPaYO4+LHiqwt1LNxksFPVQg+6lgZCwTPVRgR88vbz8O8aiH +%ChBPVcexA9i/qkBJlaZbQC+PesjvP9FOyuviUQ8V2FXtc9q0k0c9VODwHKWZ +%D9qw32XDgb73z+e2YH/NBqXogIEXjdj/sqE0EhLc63nUgw2XN6Sf7K/FfpgN +%1rKBwqvV2I+zIS2WMcG1Evtj+Xhz42KGl/OoBxt87w0zK/6K/TIbTB24XddL +%sH9nQ9jhb9Xhn3nUgw3m1o4Fa+T9tsKDDR990q4syedRDzZ0ufZdn5eH/T4H +%1mjV+M3M5VEPDri0n1SZ/YFHPTjwNeBxKrznUQ8OJIZYDjq8xd8HHAi0LXyz +%MZNHPTiw+9n220df86gHB1zvORWnZqAHByygmXx9gR4cyDArDBz2HD04kKSf +%luvwDD04cPNzqUtMOnrI75cHZ355gh5cuKW82Xv6Y/Tgwh33tcUH0tCDC5wl +%3ZnlD9CDC8Yfnn02v48eXJCcDhtMvoseXHC9IRga/T/04ELyQG9M1G0ePT+5 +%ULv5oonqLfTgQlTK/7wjbqIHF/bYjd8iTUEPLixq+scj4QZ6cCEwM9FrWhJ6 +%yOfj98Du+XX04EHXaaWkVdfQgwftgWNeD/6NHjzY57t7deJf6MGDzQWMYuur +%6MEDr4sZ4r5E9OCBVbzm8OTL6MGDML249+v+RA8ehLfpOOlcQg8eHKpLaitL +%QA8e6OU97//rAnrwYHLMQnO/8+jBAzuN2Ffz49GDD51Z043Uz6EHH4pWm9hV +%xqEHH2xcSooen0EPPviOq4qMj0UPPvz++Oyp4Bj04EOL+Pkjz2j04ANv7Mnz +%S06jBx/e39qvNysKPfhwNXFzoP4p9ODDwI2cbdqR6MGH7sTqPZKT6MGHhfXb +%U4Qn0IMPnBmH7vIj0EMAq41/1RMeRw95H+HsrSk6hh7yvm3oYq/mUfQQwOxr +%Goa6R9BDAAvVHhobHEYPAUStdHY0PYQeAvC3t/BadhA95H3Rp7YNWw6ghwDq +%0z1WHNqPHvK+4tIo3Sv70EMAWrmRr/8LRw/550e/elgVhh5CMPUKuCAIQw8h +%KJtHjTHaix5CmB5XHr5yD3oIoWMo6tvRUPSQ/66asD7vQQh6CKH1jmpCQzB6 +%yPve/KHfdYLRQwgjuLVrXf9ADyE070v5cCoIPYTAE1otfxuIHvLXHZJPgkD0 +%EELgUNYU693oIYSWRP+CE7vQQxWKwtR4H3eihyo8d24pH7kTPVTB0WNayNoA +%9FCFsoHWTyk70EMV3EadujOwHT1UITmj09lmO3qowqHbK9IT/NFDFe6daE5s +%24YeqpB9vcHccht6qAIj2/fART/0UAWf6kSVHl/0UIUd43LWO/mihxp4KU27 +%mroVPdSgpj/EUbQVPdRgha5+yLYt6KEGUxP4IR83o4caXPnlatjszeihBluP +%hYYnbEIPNfBxel7F3oQearDD8V2o30b0UAP/htBzn33QQw02xD6KsPRBD/l8 +%0pd8u7sBPdRgVohV44QN6KEGUZ1/x8StRw91UJ+496lgPXqoQ16/m/9eb/RQ +%h1yp6Oz3deihDtlHvlpuXYce6mDIH7GxZi16qIPrScsBz7XooQ6/apV1lHih +%hzqUelUGunmhhzpIh91eWLQGPdRBP6NUe8Ua9JC/P+1V2WdP9FCH56OFD1d5 +%oocIGkbYnilfjfWIwFaTKd6wGn1E4P6x41GTB9YngtEtYck7PdBLBKmGUZVD +%q7BeETzYdsLh6Cr0E4Gnw+bKYauwfvn9xBdNl1eipwgkqcojDVZiHiIw0ro1 +%IJPv44rfMyKwV7pB7N0xHxFsX5yQXOaG3iLomLbXdIcb5iWCvz6Gf+W4Yf8g +%Am1vm8rzKzA/EUQZ5hwwXIHrQQRZDDXH18sxTxEEly177rkc14cI9qyWru11 +%xXxFkGtmuyDaFdeLCFaGul+f7op5i2B+t8+iTBdcPyJghbWKNrhg/iLYrR+s +%z3bB9SSCfQdYrleXoYcY1vXPyzFbhh5iOJq+p6bKGT3EUN68J+uQM3qIQbfY +%Y/RkZ/QQg1bqf5ffOqGHGD7pbyzb5oQeYuAtyPDVdEIPMawqGHPrsSN6iGFj +%+pHCtY7oIYZXupeyhI74fZO/n6/E/99S9BCD2vclh1ctRQ8xGD0ouMVdih5i +%MEzJHrjtgB5iEPeWtXs4oIe83mEuSUIH9BBDQo/6uIf26CGff2ZjyAZ79BDD +%jTDueE179BDD6+E1rS/t0EMMA8t6L++yQw8xtB62ZE6yQw/5eAHLm4ps0UMM +%Q580QyJs0UMMe6b9dgxs0UMCV3b01HXYoIcEbK80ZF+zQQ8JtAUVqnjYoIcE +%fHXemAyzQQ8JnHKZfSvTGj3k9x9ywvZZo4cE1v7x7tbv1ughgUxVu/Z2K/SQ +%wNQPX1JuWKGHBIxkxhnrrdBDAmXujdN/sUIPCSwxWGBesgQ9JHDd5eTss0vQ +%QwLvh5+86rwEPSTAMTh/TLIEPSTwarePT/Zi9JDAv4bxlScWo4cEsk5PLbBZ +%jB4SsOHMSRIuRg8JpD4dpvfWEj0k8LHZ+EiEJXpIYNSdDeNsLdFDAj47Hqap +%W6KHBLT0nmrnLEIPCVSY6dZHL0IPKVwNFX5zWYQeUri7eIqR9iL0kAJX/1dG +%qQV6SOHI5CneVyzQQwqhST1nfSzQQz7eVqn1dAv0kAJ71ZOTneboIYXHN71M +%HpmjhxQcnGM37DNHDymEWNx3tDJHDynMGLHAV2qOHlK4b7x8eLEZekjB7eMx +%vatm6CEFE+ulwVvN0EMKKddI0Gwz9JCC8dncCqYZekjB1W+4yduF6CGFywfW +%jDq7ED2kwGl69mjtQvSQwuDe2rIZC9FDCtWnvEIHTNFD/vwt7/a+MUUPKVjH +%6XLiTNFDCn9kjvmwzhQ9pFD4yiXfyBQ9NGCbumUmU/76/4f9glI= +% "]]}}, +% AspectRatio->NCache[GoldenRatio^(-1), 0.6180339887498948], +% Axes->None, +% AxesOrigin->{0, 0}, +% Epilog->{ +% InsetBox[ +% BoxData[ +% FormBox[ +% InterpretationBox[ +% Cell[ +% BoxData[ +% FormBox["\"|q| = 0.02\"", TraditionalForm]], "Text", "TR"], +% Text["|q| = 0.02"]], TraditionalForm]], {0.8, 4}, BaseStyle \ +%-> 14], +% InsetBox[ +% BoxData[ +% FormBox[ +% InterpretationBox[ +% Cell[ +% BoxData[ +% FormBox["\"|q| = 0.05\"", TraditionalForm]], "Text", "TR"], +% Text["|q| = 0.05"]], TraditionalForm]], {1.67, 6.4}, \ +%BaseStyle -> 14], +% InsetBox[ +% BoxData[ +% FormBox[ +% InterpretationBox[ +% Cell[ +% BoxData[ +% FormBox["\"|q| = 0.07\"", TraditionalForm]], "Text", "TR"], +% Text["|q| = 0.07"]], TraditionalForm]], {2.85, 12}, \ +%BaseStyle -> 14], +% InsetBox[ +% BoxData[ +% FormBox[ +% InterpretationBox[ +% Cell[ +% BoxData[ +% FormBox["\"|q| = 0.1\"", TraditionalForm]], "Text", "TR"], \ +% +% Text["|q| = 0.1"]], TraditionalForm]], {3.2, 7}, BaseStyle \ +%-> 14], Null}, +% Frame->True, +% FrameLabel->{ +% FormBox["\"Impact parameter, b\"", TraditionalForm], +% FormBox["\"Spatial rotation, \[Chi]\"", TraditionalForm]}, +% FrameStyle->{{14, +% GrayLevel[1]}, {14, +% GrayLevel[1]}}, +% FrameTicks->{Automatic, {{0, +% FormBox["0", TraditionalForm]}, { +% NCache[Pi, 3.141592653589793], +% FormBox["\[Pi]", TraditionalForm]}, { +% NCache[2 Pi, 6.283185307179586], +% FormBox[ +% RowBox[{"2", " ", "\[Pi]"}], TraditionalForm]}, { +% NCache[3 Pi, 9.42477796076938], +% FormBox[ +% RowBox[{"3", " ", "\[Pi]"}], TraditionalForm]}, { +% NCache[4 Pi, 12.566370614359172`], +% FormBox[ +% RowBox[{"4", " ", "\[Pi]"}], TraditionalForm]}}}, +% FrameTicksStyle->{16, 16}, +% ImageSize->600, +% PlotRange->{All, All}, +% PlotRangeClipping->True, +% PlotRangePadding->{Automatic, Automatic}, +% TicksStyle->16]], "Output", +% CellChangeTimes->{3.556953242036603*^9, {3.556953596625984*^9, \ +%3.556953702375863*^9}, {3.556953796337514*^9, 3.556953956593231*^9}, \ +%{3.556954020687426*^9, 3.556954030706046*^9}, { +% 3.558693364370013*^9, 3.5586933829491863`*^9}, \ +%{3.55869345851305*^9, 3.5586934742140837`*^9}}] +%%EndMathematicaCell +p +np 33 1 m +33 273 L +469 273 L +469 1 L +cp +clip np +p +np 35 3 m +35 271 L +467 271 L +467 3 L +cp +clip np +3.239 setmiterlimit +p +np 70 3 m +70 238 L +450 238 L +450 3 L +cp +clip np +P +p +np 70 3 m +70 238 L +450 238 L +450 3 L +cp +clip np +P +p +np 70 3 m +70 238 L +450 238 L +450 3 L +cp +clip np +0 g +0.36 w +[ ] 0 setdash +3.25 setmiterlimit +78.19 226.892 m +78.919 226.724 L +79.648 226.555 L +80.376 226.386 L +81.105 226.217 L +81.834 226.048 L +82.562 225.878 L +83.291 225.707 L +84.02 225.537 L +84.749 225.366 L +85.477 225.194 L +86.206 225.022 L +86.935 224.85 L +87.663 224.678 L +88.392 224.505 L +89.121 224.331 L +89.849 224.158 L +90.578 223.984 L +91.307 223.809 L +92.035 223.635 L +92.764 223.459 L +93.493 223.284 L +94.221 223.108 L +94.95 222.932 L +95.679 222.755 L +96.407 222.578 L +97.136 222.4 L +97.865 222.222 L +98.593 222.044 L +99.322 221.865 L +100.051 221.686 L +100.779 221.507 L +101.508 221.327 L +102.237 221.147 L +102.965 220.966 L +103.694 220.785 L +104.423 220.604 L +105.152 220.422 L +105.88 220.24 L +106.609 220.057 L +107.338 219.874 L +108.066 219.691 L +108.795 219.507 L +109.524 219.323 L +110.252 219.138 L +110.981 218.953 L +111.71 218.767 L +112.438 218.581 L +113.167 218.395 L +113.896 218.208 L +114.624 218.021 L +115.353 217.833 L +116.082 217.645 L +116.81 217.457 L +117.539 217.268 L +118.268 217.078 L +118.996 216.889 L +119.725 216.698 L +120.454 216.508 L +121.182 216.316 L +121.911 216.125 L +122.64 215.933 L +123.368 215.74 L +124.097 215.547 L +124.826 215.354 L +125.554 215.16 L +126.283 214.965 L +127.012 214.77 L +127.741 214.575 L +128.469 214.379 L +129.198 214.183 L +129.927 213.986 L +130.655 213.789 L +131.384 213.591 L +132.113 213.392 L +132.841 213.194 L +133.57 212.994 L +134.299 212.794 L +135.027 212.594 L +135.756 212.393 L +136.485 212.192 L +137.213 211.99 L +137.942 211.787 L +138.671 211.584 L +139.399 211.38 L +140.128 211.176 L +140.857 210.972 L +141.585 210.766 L +142.314 210.56 L +143.043 210.354 L +143.771 210.147 L +144.5 209.939 L +145.229 209.731 L +145.957 209.522 L +146.686 209.313 L +147.415 209.103 L +148.144 208.893 L +148.872 208.681 L +149.601 208.47 L +150.33 208.257 L +151.058 208.044 L +151.787 207.83 L +152.516 207.616 L +153.244 207.401 L +153.973 207.185 L +154.702 206.969 L +155.43 206.752 L +156.159 206.534 L +156.888 206.316 L +157.616 206.097 L +158.345 205.877 L +159.074 205.656 L +159.802 205.435 L +160.531 205.213 L +161.26 204.99 L +161.988 204.767 L +162.717 204.542 L +163.446 204.317 L +164.174 204.092 L +164.903 203.865 L +165.632 203.638 L +166.36 203.409 L +167.089 203.181 L +167.818 202.951 L +168.547 202.72 L +169.275 202.489 L +170.004 202.256 L +170.733 202.023 L +171.461 201.789 L +172.19 201.554 L +172.919 201.318 L +173.647 201.081 L +174.376 200.844 L +175.105 200.605 L +175.833 200.365 L +176.562 200.125 L +177.291 199.883 L +178.019 199.641 L +178.748 199.397 L +179.477 199.153 L +180.205 198.907 L +180.934 198.661 L +181.663 198.413 L +182.391 198.164 L +183.12 197.914 L +183.849 197.664 L +184.577 197.412 L +185.306 197.158 L +186.035 196.904 L +186.763 196.649 L +187.492 196.392 L +188.221 196.134 L +188.95 195.875 L +189.678 195.615 L +190.407 195.353 L +191.136 195.091 L +191.864 194.826 L +192.593 194.561 L +193.322 194.294 L +194.05 194.026 L +194.779 193.757 L +195.508 193.486 L +196.236 193.214 L +196.965 192.94 L +197.694 192.665 L +198.422 192.388 L +199.151 192.11 L +199.88 191.83 L +200.608 191.549 L +201.337 191.266 L +202.066 190.981 L +202.794 190.695 L +203.523 190.408 L +204.252 190.118 L +204.98 189.827 L +205.709 189.534 L +206.438 189.239 L +207.166 188.942 L +207.895 188.644 L +208.624 188.343 L +209.353 188.041 L +210.081 187.737 L +210.81 187.43 L +211.539 187.122 L +212.267 186.811 L +212.996 186.499 L +213.725 186.184 L +214.453 185.867 L +215.182 185.548 L +215.911 185.226 L +216.639 184.903 L +217.368 184.576 L +218.097 184.248 L +218.825 183.917 L +219.554 183.583 L +220.283 183.247 L +221.011 182.908 L +221.74 182.566 L +222.469 182.221 L +223.197 181.874 L +223.926 181.524 L +224.655 181.171 L +225.383 180.815 L +226.112 180.455 L +226.841 180.093 L +227.569 179.727 L +228.298 179.358 L +229.027 178.986 L +229.756 178.61 L +230.484 178.23 L +231.213 177.847 L +231.942 177.46 L +232.67 177.069 L +233.399 176.674 L +234.128 176.276 L +234.856 175.872 L +235.585 175.465 L +236.314 175.053 L +237.042 174.637 L +237.771 174.216 L +238.5 173.79 L +239.228 173.359 L +239.957 172.924 L +240.686 172.482 L +241.414 172.036 L +242.143 171.584 L +242.872 171.126 L +243.6 170.662 L +244.329 170.192 L +245.058 169.716 L +245.786 169.233 L +246.515 168.744 L +247.244 168.247 L +247.972 167.743 L +248.701 167.232 L +249.43 166.713 L +250.159 166.186 L +250.887 165.65 L +251.616 165.106 L +252.345 164.553 L +253.073 163.99 L +253.802 163.417 L +254.531 162.834 L +255.259 162.241 L +255.988 161.636 L +256.717 161.02 L +257.445 160.391 L +258.174 159.75 L +258.903 159.095 L +259.631 158.426 L +260.36 157.743 L +261.089 157.044 L +261.817 156.328 L +262.546 155.595 L +263.275 154.844 L +264.003 154.074 L +264.732 153.283 L +265.461 152.471 L +266.189 151.635 L +266.918 150.774 L +267.647 149.887 L +268.375 148.972 L +269.104 148.026 L +269.833 147.047 L +270.561 146.033 L +271.29 144.98 L +272.019 143.886 L +272.748 142.746 L +273.476 141.556 L +274.205 140.31 L +274.934 139.004 L +275.662 137.63 L +276.391 136.179 L +277.12 134.642 L +277.848 133.005 L +278.577 131.258 L +279.306 129.378 L +280.034 127.344 L +280.763 125.125 L +281.492 122.683 L +282.22 119.959 L +282.949 116.88 L +283.678 113.326 L +284.406 109.112 L +285.135 103.916 L +285.864 97.083 L +286.592 86.986 L +287.321 66.511 L +288.05 107.215 L +288.778 120.201 L +289.507 125.455 L +290.236 128.764 L +290.964 131.167 L +291.693 133.043 L +292.422 134.577 L +293.151 135.87 L +293.879 136.983 L +294.608 137.958 L +295.337 138.825 L +296.065 139.602 L +296.794 140.306 L +297.523 140.948 L +298.251 141.537 L +298.98 142.081 L +299.709 142.585 L +300.437 143.054 L +301.166 143.493 L +301.895 143.904 L +302.623 144.291 L +303.352 144.655 L +304.081 145 L +304.809 145.327 L +305.538 145.636 L +306.267 145.931 L +306.995 146.212 L +307.724 146.479 L +308.453 146.735 L +309.181 146.98 L +309.91 147.214 L +310.639 147.439 L +311.367 147.655 L +312.096 147.863 L +312.825 148.062 L +313.554 148.255 L +314.282 148.44 L +315.011 148.619 L +315.74 148.792 L +316.468 148.959 L +317.197 149.12 L +317.926 149.276 L +318.654 149.427 L +319.383 149.574 L +320.112 149.716 L +320.84 149.853 L +321.569 149.987 L +322.298 150.117 L +323.026 150.243 L +323.755 150.366 L +324.484 150.485 L +325.212 150.601 L +325.941 150.713 L +326.67 150.823 L +327.398 150.93 L +328.127 151.035 L +328.856 151.136 L +329.584 151.235 L +330.313 151.332 L +331.042 151.426 L +331.77 151.518 L +332.499 151.608 L +333.228 151.696 L +333.957 151.782 L +334.685 151.866 L +335.414 151.948 L +336.143 152.028 L +336.871 152.106 L +337.6 152.183 L +338.329 152.258 L +339.057 152.331 L +339.786 152.403 L +340.515 152.474 L +341.243 152.542 L +341.972 152.61 L +342.701 152.676 L +343.429 152.741 L +344.158 152.805 L +344.887 152.867 L +345.615 152.928 L +346.344 152.988 L +347.073 153.047 L +347.801 153.105 L +348.53 153.161 L +349.259 153.217 L +349.987 153.271 L +350.716 153.325 L +351.445 153.377 L +352.173 153.429 L +352.902 153.48 L +353.631 153.53 L +354.36 153.579 L +355.088 153.627 L +355.817 153.674 L +356.546 153.721 L +357.274 153.766 L +358.003 153.811 L +358.732 153.855 L +359.46 153.899 L +360.189 153.942 L +360.918 153.984 L +361.646 154.025 L +362.375 154.066 L +363.104 154.106 L +363.832 154.145 L +364.561 154.184 L +365.29 154.222 L +366.018 154.26 L +366.747 154.297 L +367.476 154.333 L +368.204 154.369 L +368.933 154.405 L +369.662 154.44 L +370.39 154.474 L +371.119 154.508 L +371.848 154.541 L +372.576 154.574 L +373.305 154.606 L +374.034 154.638 L +374.763 154.669 L +375.491 154.7 L +376.22 154.731 L +376.949 154.761 L +377.677 154.791 L +378.406 154.82 L +379.135 154.849 L +379.863 154.877 L +380.592 154.905 L +381.321 154.933 L +382.049 154.96 L +382.778 154.987 L +383.507 155.014 L +384.235 155.04 L +384.964 155.066 L +385.693 155.092 L +386.421 155.117 L +387.15 155.142 L +387.879 155.166 L +388.607 155.19 L +389.336 155.214 L +390.065 155.238 L +390.793 155.261 L +391.522 155.284 L +392.251 155.307 L +392.979 155.33 L +393.708 155.352 L +394.437 155.374 L +395.166 155.395 L +395.894 155.417 L +396.623 155.438 L +397.352 155.458 L +398.08 155.479 L +398.809 155.499 L +399.538 155.519 L +400.266 155.539 L +400.995 155.559 L +401.724 155.578 L +402.452 155.597 L +403.181 155.616 L +403.91 155.635 L +404.638 155.653 L +405.367 155.672 L +406.096 155.69 L +406.824 155.708 L +407.553 155.725 L +408.282 155.743 L +409.01 155.76 L +409.739 155.777 L +410.468 155.794 L +411.196 155.811 L +411.925 155.827 L +412.654 155.843 L +413.382 155.859 L +414.111 155.875 L +414.84 155.891 L +415.568 155.907 L +416.297 155.922 L +417.026 155.937 L +417.755 155.952 L +418.483 155.967 L +419.212 155.982 L +419.941 155.997 L +420.669 156.011 L +421.398 156.025 L +422.127 156.04 L +422.855 156.054 L +423.584 156.067 L +424.313 156.081 L +425.041 156.095 L +425.77 156.108 L +426.499 156.121 L +427.227 156.135 L +427.956 156.148 L +428.685 156.16 L +429.413 156.173 L +430.142 156.186 L +430.871 156.198 L +431.599 156.211 L +432.328 156.223 L +433.057 156.235 L +433.785 156.247 L +434.514 156.259 L +435.243 156.271 L +435.971 156.282 L +436.7 156.294 L +437.429 156.305 L +438.158 156.316 L +438.886 156.328 L +439.615 156.339 L +440.344 156.35 L +441.072 156.36 L +441.801 156.371 L +442.53 156.382 L +s +78.19 227.244 m +78.919 227.062 L +79.648 226.879 L +80.376 226.696 L +81.105 226.513 L +81.834 226.329 L +82.562 226.144 L +83.291 225.96 L +84.02 225.774 L +84.749 225.589 L +85.477 225.403 L +86.206 225.216 L +86.935 225.029 L +87.663 224.842 L +88.392 224.654 L +89.121 224.465 L +89.849 224.277 L +90.578 224.088 L +91.307 223.898 L +92.035 223.708 L +92.764 223.518 L +93.493 223.327 L +94.221 223.135 L +94.95 222.944 L +95.679 222.751 L +96.407 222.559 L +97.136 222.366 L +97.865 222.172 L +98.593 221.978 L +99.322 221.783 L +100.051 221.589 L +100.779 221.393 L +101.508 221.197 L +102.237 221.001 L +102.965 220.804 L +103.694 220.607 L +104.423 220.409 L +105.152 220.211 L +105.88 220.013 L +106.609 219.813 L +107.338 219.614 L +108.066 219.414 L +108.795 219.213 L +109.524 219.012 L +110.252 218.811 L +110.981 218.609 L +111.71 218.406 L +112.438 218.203 L +113.167 217.999 L +113.896 217.795 L +114.624 217.591 L +115.353 217.386 L +116.082 217.18 L +116.81 216.974 L +117.539 216.767 L +118.268 216.56 L +118.996 216.352 L +119.725 216.144 L +120.454 215.935 L +121.182 215.726 L +121.911 215.516 L +122.64 215.305 L +123.368 215.094 L +124.097 214.883 L +124.826 214.67 L +125.554 214.458 L +126.283 214.244 L +127.012 214.03 L +127.741 213.816 L +128.469 213.6 L +129.198 213.385 L +129.927 213.168 L +130.655 212.951 L +131.384 212.733 L +132.113 212.515 L +132.841 212.296 L +133.57 212.076 L +134.299 211.856 L +135.027 211.635 L +135.756 211.413 L +136.485 211.191 L +137.213 210.968 L +137.942 210.744 L +138.671 210.52 L +139.399 210.294 L +140.128 210.068 L +140.857 209.842 L +141.585 209.614 L +142.314 209.386 L +143.043 209.157 L +143.771 208.927 L +144.5 208.697 L +145.229 208.466 L +145.957 208.234 L +146.686 208.001 L +147.415 207.767 L +148.144 207.532 L +148.872 207.297 L +149.601 207.06 L +150.33 206.823 L +151.058 206.585 L +151.787 206.346 L +152.516 206.106 L +153.244 205.865 L +153.973 205.623 L +154.702 205.381 L +155.43 205.137 L +156.159 204.892 L +156.888 204.646 L +157.616 204.4 L +158.345 204.152 L +159.074 203.903 L +159.802 203.653 L +160.531 203.402 L +161.26 203.15 L +161.988 202.897 L +162.717 202.643 L +163.446 202.387 L +164.174 202.131 L +164.903 201.873 L +165.632 201.614 L +166.36 201.354 L +167.089 201.092 L +167.818 200.83 L +168.547 200.565 L +169.275 200.3 L +170.004 200.033 L +170.733 199.765 L +171.461 199.496 L +172.19 199.225 L +172.919 198.953 L +173.647 198.68 L +174.376 198.405 L +175.105 198.128 L +175.833 197.85 L +176.562 197.57 L +177.291 197.289 L +178.019 197.006 L +178.748 196.722 L +179.477 196.435 L +180.205 196.148 L +180.934 195.858 L +181.663 195.567 L +182.391 195.274 L +183.12 194.979 L +183.849 194.682 L +184.577 194.383 L +185.306 194.083 L +186.035 193.78 L +186.763 193.475 L +187.492 193.169 L +188.221 192.86 L +188.95 192.549 L +189.678 192.236 L +190.407 191.921 L +191.136 191.603 L +191.864 191.284 L +192.593 190.961 L +193.322 190.637 L +194.05 190.31 L +194.779 189.98 L +195.508 189.648 L +196.236 189.313 L +196.965 188.976 L +197.694 188.635 L +198.422 188.292 L +199.151 187.946 L +199.88 187.598 L +200.608 187.246 L +201.337 186.891 L +202.066 186.533 L +202.794 186.171 L +203.523 185.807 L +204.252 185.438 L +204.98 185.067 L +205.709 184.692 L +206.438 184.313 L +207.166 183.93 L +207.895 183.544 L +208.624 183.153 L +209.353 182.759 L +210.081 182.36 L +210.81 181.957 L +211.539 181.549 L +212.267 181.137 L +212.996 180.72 L +213.725 180.299 L +214.453 179.872 L +215.182 179.44 L +215.911 179.003 L +216.639 178.56 L +217.368 178.112 L +218.097 177.657 L +218.825 177.197 L +219.554 176.73 L +220.283 176.257 L +221.011 175.777 L +221.74 175.29 L +222.469 174.796 L +223.197 174.295 L +223.926 173.785 L +224.655 173.268 L +225.383 172.742 L +226.112 172.207 L +226.841 171.662 L +227.569 171.109 L +228.298 170.546 L +229.027 169.971 L +229.756 169.387 L +230.484 168.79 L +231.213 168.182 L +231.942 167.561 L +232.67 166.927 L +233.399 166.28 L +234.128 165.617 L +234.856 164.94 L +235.585 164.246 L +236.314 163.535 L +237.042 162.806 L +237.771 162.058 L +238.5 161.289 L +239.228 160.499 L +239.957 159.686 L +240.686 158.848 L +241.414 157.985 L +242.143 157.093 L +242.872 156.17 L +243.6 155.216 L +244.329 154.226 L +245.058 153.198 L +245.786 152.13 L +246.515 151.016 L +247.244 149.854 L +247.972 148.638 L +248.701 95.265 L +249.43 93.925 L +250.159 92.513 L +250.887 91.021 L +251.616 89.437 L +252.345 87.751 L +253.073 85.948 L +253.802 84.008 L +254.531 81.914 L +255.259 79.635 L +255.988 77.137 L +256.717 74.37 L +257.445 71.271 L +258.174 67.749 L +258.903 63.665 L +259.631 58.799 L +260.36 52.774 L +261.089 44.836 L +261.817 33.16 L +262.546 10.25 L +263.275 102.86 L +264.003 118.171 L +264.732 124.159 L +265.461 127.877 L +266.189 130.553 L +266.918 132.626 L +267.647 134.311 L +268.375 135.722 L +269.104 136.932 L +269.833 137.986 L +270.561 138.919 L +271.29 139.753 L +272.019 140.505 L +272.748 141.189 L +273.476 141.814 L +274.205 142.389 L +274.934 142.921 L +275.662 143.415 L +276.391 143.875 L +277.12 144.305 L +277.848 144.709 L +278.577 145.088 L +279.306 145.446 L +280.034 145.784 L +280.763 146.105 L +281.492 146.409 L +282.22 146.698 L +282.949 146.973 L +283.678 147.236 L +284.406 147.486 L +285.135 147.726 L +285.864 147.955 L +286.592 148.175 L +287.321 148.386 L +288.05 148.589 L +288.778 148.784 L +289.507 148.972 L +290.236 149.152 L +290.964 149.327 L +291.693 149.495 L +292.422 149.657 L +293.151 149.814 L +293.879 149.965 L +294.608 150.112 L +295.337 150.254 L +296.065 150.392 L +296.794 150.525 L +297.523 150.654 L +298.251 150.78 L +298.98 150.901 L +299.709 151.02 L +300.437 151.135 L +301.166 151.246 L +301.895 151.355 L +302.623 151.46 L +303.352 151.563 L +304.081 151.663 L +304.809 151.761 L +305.538 151.856 L +306.267 151.949 L +306.995 152.039 L +307.724 152.127 L +308.453 152.213 L +309.181 152.297 L +309.91 152.379 L +310.639 152.459 L +311.367 152.537 L +312.096 152.613 L +312.825 152.688 L +313.554 152.761 L +314.282 152.832 L +315.011 152.902 L +315.74 152.97 L +316.468 153.037 L +317.197 153.102 L +317.926 153.166 L +318.654 153.228 L +319.383 153.29 L +320.112 153.35 L +320.84 153.409 L +321.569 153.466 L +322.298 153.523 L +323.026 153.578 L +323.755 153.633 L +324.484 153.686 L +325.212 153.738 L +325.941 153.789 L +326.67 153.84 L +327.398 153.889 L +328.127 153.937 L +328.856 153.985 L +329.584 154.032 L +330.313 154.078 L +331.042 154.123 L +331.77 154.167 L +332.499 154.21 L +333.228 154.253 L +333.957 154.295 L +334.685 154.336 L +335.414 154.376 L +336.143 154.416 L +336.871 154.455 L +337.6 154.494 L +338.329 154.532 L +339.057 154.569 L +339.786 154.606 L +340.515 154.642 L +341.243 154.677 L +341.972 154.712 L +342.701 154.746 L +343.429 154.78 L +344.158 154.813 L +344.887 154.846 L +345.615 154.878 L +346.344 154.91 L +347.073 154.941 L +347.801 154.972 L +348.53 155.002 L +349.259 155.032 L +349.987 155.061 L +350.716 155.09 L +351.445 155.118 L +352.173 155.146 L +352.902 155.174 L +353.631 155.201 L +354.36 155.228 L +355.088 155.255 L +355.817 155.281 L +356.546 155.306 L +357.274 155.332 L +358.003 155.357 L +358.732 155.381 L +359.46 155.406 L +360.189 155.43 L +360.918 155.453 L +361.646 155.476 L +362.375 155.499 L +363.104 155.522 L +363.832 155.544 L +364.561 155.567 L +365.29 155.588 L +366.018 155.61 L +366.747 155.631 L +367.476 155.652 L +368.204 155.672 L +368.933 155.693 L +369.662 155.713 L +370.39 155.733 L +371.119 155.752 L +371.848 155.772 L +372.576 155.791 L +373.305 155.81 L +374.034 155.828 L +374.763 155.847 L +375.491 155.865 L +376.22 155.883 L +376.949 155.9 L +377.677 155.918 L +378.406 155.935 L +379.135 155.952 L +379.863 155.969 L +380.592 155.986 L +381.321 156.002 L +382.049 156.018 L +382.778 156.034 L +383.507 156.05 L +384.235 156.066 L +384.964 156.081 L +385.693 156.097 L +386.421 156.112 L +387.15 156.127 L +387.879 156.141 L +388.607 156.156 L +389.336 156.171 L +390.065 156.185 L +390.793 156.199 L +391.522 156.213 L +392.251 156.227 L +392.979 156.24 L +393.708 156.254 L +394.437 156.267 L +395.166 156.28 L +395.894 156.293 L +396.623 156.306 L +397.352 156.319 L +398.08 156.331 L +398.809 156.344 L +399.538 156.356 L +400.266 156.368 L +400.995 156.38 L +401.724 156.392 L +402.452 156.404 L +403.181 156.416 L +403.91 156.427 L +404.638 156.439 L +405.367 156.45 L +406.096 156.461 L +406.824 156.472 L +407.553 156.483 L +408.282 156.494 L +409.01 156.505 L +409.739 156.515 L +410.468 156.526 L +411.196 156.536 L +411.925 156.546 L +412.654 156.557 L +413.382 156.567 L +414.111 156.577 L +414.84 156.587 L +415.568 156.596 L +416.297 156.606 L +417.026 156.616 L +417.755 156.625 L +418.483 156.634 L +419.212 156.644 L +419.941 156.653 L +420.669 156.662 L +421.398 156.671 L +422.127 156.68 L +422.855 156.689 L +423.584 156.697 L +424.313 156.706 L +425.041 156.715 L +425.77 156.723 L +426.499 156.732 L +427.227 156.74 L +427.956 156.748 L +428.685 156.756 L +429.413 156.764 L +430.142 156.772 L +430.871 156.78 L +431.599 156.788 L +432.328 156.796 L +433.057 156.804 L +433.785 156.811 L +434.514 156.819 L +435.243 156.826 L +435.971 156.834 L +436.7 156.841 L +437.429 156.849 L +438.158 156.856 L +438.886 156.863 L +439.615 156.87 L +440.344 156.877 L +441.072 156.884 L +441.801 156.891 L +442.53 156.898 L +s +78.19 227.796 m +78.555 227.699 L +78.919 227.602 L +79.283 227.504 L +79.648 227.407 L +80.012 227.309 L +80.376 227.211 L +80.741 227.113 L +81.105 227.016 L +81.469 226.917 L +81.834 226.819 L +82.198 226.721 L +82.562 226.622 L +82.927 226.524 L +83.291 226.425 L +83.655 226.327 L +84.02 226.228 L +84.384 226.129 L +84.749 226.03 L +85.113 225.93 L +85.477 225.831 L +85.842 225.732 L +86.206 225.632 L +86.57 225.532 L +86.935 225.433 L +87.299 225.333 L +87.663 225.233 L +88.028 225.133 L +88.392 225.032 L +88.756 224.932 L +89.121 224.832 L +89.485 224.731 L +89.849 224.63 L +90.214 224.53 L +90.578 224.429 L +90.942 224.328 L +91.307 224.227 L +91.671 224.125 L +92.035 224.024 L +92.4 223.923 L +92.764 223.821 L +93.128 223.719 L +93.493 223.618 L +93.857 223.516 L +94.221 223.414 L +94.586 223.311 L +94.95 223.209 L +95.314 223.107 L +95.679 223.004 L +96.043 222.902 L +96.407 222.799 L +96.772 222.696 L +97.136 222.593 L +97.5 222.49 L +97.865 222.387 L +98.229 222.284 L +98.593 222.18 L +98.958 222.077 L +99.322 221.973 L +99.686 221.869 L +100.051 221.765 L +100.415 221.661 L +100.779 221.557 L +101.144 221.453 L +101.508 221.349 L +101.872 221.244 L +102.237 221.14 L +102.601 221.035 L +102.965 220.93 L +103.33 220.825 L +103.694 220.72 L +104.058 220.615 L +104.423 220.51 L +104.787 220.404 L +105.152 220.299 L +105.516 220.193 L +105.88 220.087 L +106.245 219.981 L +106.609 219.875 L +106.973 219.769 L +107.338 219.663 L +107.702 219.556 L +108.066 219.45 L +108.431 219.343 L +108.795 219.236 L +109.159 219.129 L +109.524 219.022 L +109.888 218.915 L +110.252 218.808 L +110.617 218.7 L +110.981 218.593 L +111.345 218.485 L +111.71 218.377 L +112.074 218.269 L +112.438 218.161 L +112.803 218.053 L +113.167 217.945 L +113.531 217.836 L +113.896 217.727 L +114.26 217.619 L +114.624 217.51 L +114.989 217.401 L +115.353 217.291 L +115.717 217.182 L +116.082 217.073 L +116.446 216.963 L +116.81 216.853 L +117.175 216.743 L +117.539 216.633 L +117.903 216.523 L +118.268 216.413 L +118.632 216.303 L +118.996 216.192 L +119.361 216.081 L +119.725 215.97 L +120.089 215.859 L +120.454 215.748 L +120.818 215.637 L +121.182 215.525 L +121.547 215.414 L +121.911 215.302 L +122.275 215.19 L +122.64 215.078 L +123.004 214.965 L +123.368 214.853 L +123.733 214.74 L +124.097 214.628 L +124.461 214.515 L +124.826 214.402 L +125.19 214.288 L +125.554 214.175 L +125.919 214.061 L +126.283 213.948 L +126.648 213.834 L +127.012 213.72 L +127.376 213.605 L +127.741 213.491 L +128.105 213.376 L +128.469 213.262 L +128.834 213.147 L +129.198 213.032 L +129.562 212.916 L +129.927 212.801 L +130.291 212.685 L +130.655 212.57 L +131.02 212.454 L +131.384 212.337 L +131.748 212.221 L +132.113 212.105 L +132.477 211.988 L +132.841 211.871 L +133.206 211.754 L +133.57 211.636 L +133.934 211.519 L +134.299 211.401 L +134.663 211.283 L +135.027 211.165 L +135.392 211.047 L +135.756 210.928 L +136.12 210.81 L +136.485 210.691 L +136.849 210.572 L +137.213 210.452 L +137.578 210.332 L +137.942 210.213 L +138.306 210.093 L +138.671 209.973 L +139.035 209.852 L +139.399 209.732 L +139.764 209.611 L +140.128 209.49 L +140.492 209.368 L +140.857 209.247 L +141.221 209.125 L +141.585 209.003 L +141.95 208.881 L +142.314 208.758 L +142.678 208.635 L +143.043 208.513 L +143.407 208.389 L +143.771 208.266 L +144.136 208.142 L +144.5 208.018 L +144.864 207.894 L +145.229 207.77 L +145.593 207.645 L +145.957 207.52 L +146.322 207.395 L +146.686 207.269 L +147.051 207.143 L +147.415 207.017 L +147.779 206.891 L +148.144 206.764 L +148.508 206.637 L +148.872 206.51 L +149.237 206.383 L +149.601 206.255 L +149.965 206.127 L +150.33 205.999 L +150.694 205.87 L +151.058 205.741 L +151.423 205.612 L +151.787 205.483 L +152.151 205.353 L +152.516 205.223 L +152.88 205.092 L +153.244 204.962 L +153.609 204.831 L +153.973 204.699 L +154.337 204.567 L +154.702 204.435 L +155.066 204.303 L +155.43 204.17 L +155.795 204.037 L +156.159 203.904 L +156.523 203.77 L +156.888 203.636 L +157.252 203.502 L +157.616 203.367 L +157.981 203.232 L +158.345 203.096 L +158.709 202.961 L +159.074 202.824 L +159.438 202.688 L +159.802 202.551 L +160.167 202.414 L +160.531 202.276 L +160.895 202.138 L +161.26 201.999 L +161.624 201.86 L +161.988 201.721 L +162.353 201.581 L +162.717 201.441 L +163.081 201.301 L +163.446 201.16 L +163.81 201.018 L +164.174 200.877 L +164.539 200.735 L +164.903 200.592 L +165.267 200.449 L +165.632 200.305 L +165.996 200.161 L +166.36 200.017 L +166.725 199.872 L +167.089 199.727 L +167.454 199.581 L +167.818 199.435 L +168.182 199.288 L +168.547 199.141 L +168.911 198.993 L +169.275 198.845 L +169.64 198.696 L +170.004 198.547 L +170.368 198.397 L +170.733 198.247 L +171.097 198.097 L +171.461 197.945 L +171.826 197.793 L +172.19 197.641 L +172.554 197.488 L +172.919 197.335 L +173.283 197.181 L +173.647 197.026 L +174.012 196.871 L +174.376 196.716 L +174.74 196.559 L +175.105 196.403 L +175.469 196.245 L +175.833 196.087 L +176.198 195.929 L +176.562 195.77 L +176.926 195.61 L +177.291 195.449 L +177.655 195.288 L +178.019 195.126 L +178.384 194.964 L +178.748 194.801 L +179.112 194.637 L +179.477 194.473 L +179.841 194.308 L +180.205 194.142 L +180.57 193.976 L +180.934 193.809 L +181.298 193.641 L +181.663 193.472 L +182.027 193.304 L +182.391 193.134 L +182.756 192.963 L +183.12 192.791 L +183.484 192.62 L +183.849 192.447 L +184.213 192.273 L +184.577 192.098 L +184.942 191.923 L +185.306 191.747 L +185.67 191.57 L +186.035 191.392 L +186.399 191.214 L +186.763 191.035 L +187.128 190.854 L +187.492 190.673 L +187.856 190.491 L +188.221 190.309 L +188.585 190.125 L +188.95 189.94 L +189.314 189.755 L +189.678 189.568 L +190.043 189.381 L +190.407 189.192 L +190.771 189.003 L +191.136 188.813 L +191.5 188.622 L +191.864 188.429 L +192.229 188.236 L +192.593 188.042 L +192.957 187.847 L +193.322 187.65 L +193.686 187.453 L +194.05 187.254 L +194.415 187.054 L +194.779 186.854 L +195.143 186.652 L +195.508 186.449 L +195.872 186.244 L +196.236 186.039 L +196.601 185.832 L +196.965 185.625 L +197.329 185.415 L +197.694 185.205 L +198.058 184.994 L +198.422 184.781 L +198.787 184.567 L +199.151 184.351 L +199.515 184.134 L +199.88 131.817 L +200.244 131.597 L +200.608 131.376 L +200.973 131.153 L +201.337 130.93 L +201.701 130.704 L +202.066 130.477 L +202.43 130.249 L +202.794 130.019 L +203.159 129.787 L +203.523 129.554 L +203.887 129.318 L +204.252 129.082 L +204.616 128.844 L +204.98 128.604 L +205.345 128.362 L +205.709 128.118 L +206.073 127.873 L +206.438 127.625 L +206.802 127.376 L +207.166 127.125 L +207.531 126.872 L +207.895 126.616 L +208.259 126.358 L +208.624 126.099 L +208.988 125.837 L +209.353 125.573 L +209.717 125.307 L +210.081 125.038 L +210.446 124.767 L +210.81 124.494 L +211.174 124.217 L +211.539 123.939 L +211.903 123.657 L +212.267 123.373 L +212.632 123.086 L +212.996 122.797 L +213.36 122.504 L +213.725 122.208 L +214.089 121.91 L +214.453 121.608 L +214.818 121.302 L +215.182 120.994 L +215.546 120.681 L +215.911 120.366 L +216.275 120.046 L +216.639 119.723 L +217.004 119.395 L +217.368 119.064 L +217.732 118.728 L +218.097 118.389 L +218.461 118.044 L +218.825 117.695 L +219.19 117.34 L +219.554 116.982 L +219.918 116.617 L +220.283 116.248 L +220.647 115.872 L +221.011 115.491 L +221.376 115.103 L +221.74 114.71 L +222.104 114.31 L +222.469 113.903 L +222.833 113.488 L +223.197 113.067 L +223.562 112.637 L +223.926 112.199 L +224.29 111.753 L +224.655 111.297 L +225.019 110.833 L +225.383 110.358 L +225.748 109.874 L +226.112 109.378 L +226.476 108.871 L +226.841 108.353 L +227.205 107.821 L +227.569 107.276 L +227.934 106.717 L +228.298 106.144 L +228.662 105.556 L +229.027 104.951 L +229.391 104.329 L +229.756 103.689 L +230.12 103.031 L +230.484 102.352 L +230.849 101.653 L +231.213 100.932 L +231.577 100.19 L +231.942 99.426 L +232.306 98.638 L +232.67 97.827 L +233.035 96.995 L +233.399 96.143 L +233.763 95.275 L +234.128 94.395 L +234.492 93.515 L +234.856 92.647 L +235.221 91.816 L +235.585 91.055 L +235.949 90.419 L +236.314 89.987 L +236.678 89.872 L +237.042 90.228 L +237.407 91.227 L +237.771 93.008 L +238.135 95.595 L +238.5 98.813 L +238.864 102.357 L +239.228 105.928 L +239.593 109.305 L +239.957 112.388 L +240.321 115.15 L +240.686 117.611 L +241.05 119.793 L +241.414 121.734 L +241.779 123.473 L +242.143 125.035 L +242.507 126.442 L +242.872 127.722 L +243.236 128.889 L +243.6 129.959 L +243.965 130.941 L +244.329 131.852 L +244.693 132.693 L +245.058 133.477 L +245.422 134.21 L +245.786 134.895 L +246.151 135.537 L +246.515 136.14 L +246.879 136.711 L +247.244 137.249 L +247.608 137.759 L +247.972 138.243 L +248.337 138.702 L +248.701 139.14 L +249.065 139.557 L +249.43 139.955 L +249.794 140.335 L +250.159 140.699 L +250.523 141.048 L +250.887 141.382 L +251.252 141.704 L +251.616 142.013 L +251.98 142.31 L +252.345 142.596 L +252.709 142.871 L +253.073 143.138 L +253.438 143.394 L +253.802 143.643 L +254.166 143.882 L +254.531 144.115 L +254.895 144.339 L +255.259 144.557 L +255.624 144.768 L +255.988 144.972 L +256.352 145.171 L +256.717 145.364 L +257.081 145.551 L +257.445 145.733 L +257.81 145.91 L +258.174 146.082 L +258.538 146.25 L +258.903 146.413 L +259.267 146.572 L +259.631 146.727 L +259.996 146.878 L +260.36 147.025 L +260.724 147.169 L +261.089 147.309 L +261.453 147.446 L +261.817 147.579 L +262.182 147.71 L +262.546 147.837 L +262.91 147.962 L +263.275 148.084 L +263.639 148.203 L +264.003 148.32 L +264.368 148.434 L +264.732 148.546 L +265.096 148.655 L +265.461 148.763 L +265.825 148.868 L +266.189 148.97 L +266.554 149.071 L +266.918 149.17 L +267.282 149.267 L +267.647 149.362 L +268.011 149.455 L +268.375 149.547 L +268.74 149.636 L +269.104 149.724 L +269.468 149.811 L +269.833 149.896 L +270.197 149.979 L +270.561 150.061 L +270.926 150.141 L +271.29 150.22 L +271.655 150.298 L +272.019 150.374 L +272.383 150.449 L +272.748 150.523 L +273.112 150.595 L +273.476 150.667 L +273.841 150.737 L +274.205 150.806 L +274.569 150.874 L +274.934 150.941 L +275.298 151.006 L +275.662 151.071 L +276.027 151.135 L +276.391 151.198 L +276.755 151.259 L +277.12 151.32 L +277.484 151.38 L +277.848 151.439 L +278.213 151.497 L +278.577 151.554 L +278.941 151.611 L +279.306 151.666 L +279.67 151.721 L +280.034 151.775 L +280.399 151.828 L +280.763 151.881 L +281.127 151.932 L +281.492 151.983 L +281.856 152.034 L +282.22 152.083 L +282.585 152.132 L +282.949 152.181 L +283.313 152.228 L +283.678 152.275 L +284.042 152.321 L +284.406 152.367 L +284.771 152.412 L +285.135 152.457 L +285.499 152.501 L +285.864 152.544 L +286.228 152.587 L +286.592 152.63 L +286.957 152.671 L +287.321 152.713 L +287.685 152.753 L +288.05 152.794 L +288.414 152.833 L +288.778 152.872 L +289.143 152.911 L +289.507 152.949 L +289.871 152.987 L +290.236 153.025 L +290.6 153.062 L +290.964 153.098 L +291.329 153.134 L +291.693 153.17 L +292.058 153.205 L +292.422 153.24 L +292.786 153.274 L +293.151 153.308 L +293.515 153.342 L +293.879 153.375 L +294.244 153.408 L +294.608 153.44 L +294.972 153.473 L +295.337 153.504 L +295.701 153.536 L +296.065 153.567 L +296.43 153.597 L +296.794 153.628 L +297.158 153.658 L +297.523 153.688 L +297.887 153.717 L +298.251 153.746 L +298.616 153.775 L +298.98 153.803 L +299.344 153.832 L +299.709 153.86 L +300.073 153.887 L +300.437 153.914 L +300.802 153.941 L +301.166 153.968 L +301.53 153.995 L +301.895 154.021 L +302.259 154.047 L +302.623 154.072 L +302.988 154.098 L +303.352 154.123 L +303.716 154.148 L +304.081 154.173 L +304.445 154.197 L +304.809 154.221 L +305.174 154.245 L +305.538 154.269 L +305.902 154.292 L +306.267 154.315 L +306.631 154.338 L +306.995 154.361 L +307.36 154.384 L +307.724 154.406 L +308.088 154.428 L +308.453 154.45 L +308.817 154.472 L +309.181 154.493 L +309.546 154.515 L +309.91 154.536 L +310.274 154.557 L +310.639 154.578 L +311.003 154.598 L +311.367 154.618 L +311.732 154.639 L +312.096 154.659 L +312.461 154.678 L +312.825 154.698 L +313.189 154.717 L +313.554 154.737 L +313.918 154.756 L +314.282 154.775 L +314.647 154.793 L +315.011 154.812 L +315.375 154.83 L +315.74 154.849 L +316.104 154.867 L +316.468 154.885 L +316.833 154.902 L +317.197 154.92 L +317.561 154.938 L +317.926 154.955 L +318.29 154.972 L +318.654 154.989 L +319.019 155.006 L +319.383 155.023 L +319.747 155.039 L +320.112 155.056 L +320.476 155.072 L +320.84 155.088 L +321.205 155.104 L +321.569 155.12 L +321.933 155.136 L +322.298 155.152 L +322.662 155.167 L +323.026 155.183 L +323.391 155.198 L +323.755 155.213 L +324.119 155.228 L +324.484 155.243 L +324.848 155.258 L +325.212 155.272 L +325.577 155.287 L +325.941 155.301 L +326.305 155.316 L +326.67 155.33 L +327.034 155.344 L +327.398 155.358 L +327.763 155.372 L +328.127 155.386 L +328.491 155.399 L +328.856 155.413 L +329.22 155.426 L +329.584 155.439 L +329.949 155.453 L +330.313 155.466 L +330.677 155.479 L +331.042 155.492 L +331.406 155.505 L +331.77 155.517 L +332.135 155.53 L +332.499 155.542 L +332.864 155.555 L +333.228 155.567 L +333.592 155.579 L +333.957 155.592 L +334.321 155.604 L +334.685 155.616 L +335.05 155.628 L +335.414 155.639 L +335.778 155.651 L +336.143 155.663 L +336.507 155.674 L +336.871 155.686 L +337.236 155.697 L +337.6 155.708 L +337.964 155.719 L +338.329 155.731 L +338.693 155.742 L +339.057 155.753 L +339.422 155.763 L +339.786 155.774 L +340.15 155.785 L +340.515 155.796 L +340.879 155.806 L +341.243 155.817 L +341.608 155.827 L +341.972 155.838 L +342.336 155.848 L +342.701 155.858 L +343.065 155.868 L +343.429 155.878 L +343.794 155.888 L +344.158 155.898 L +344.522 155.908 L +344.887 155.918 L +345.251 155.927 L +345.615 155.937 L +345.98 155.947 L +346.344 155.956 L +346.708 155.966 L +347.073 155.975 L +347.437 155.984 L +347.801 155.994 L +348.166 156.003 L +348.53 156.012 L +348.894 156.021 L +349.259 156.03 L +349.623 156.039 L +349.987 156.048 L +350.352 156.057 L +350.716 156.065 L +351.08 156.074 L +351.445 156.083 L +351.809 156.091 L +352.173 156.1 L +352.538 156.108 L +352.902 156.117 L +353.266 156.125 L +353.631 156.133 L +353.995 156.142 L +354.36 156.15 L +354.724 156.158 L +355.088 156.166 L +355.453 156.174 L +355.817 156.182 L +356.181 156.19 L +356.546 156.198 L +356.91 156.206 L +357.274 156.214 L +357.639 156.221 L +358.003 156.229 L +358.367 156.237 L +358.732 156.244 L +359.096 156.252 L +359.46 156.259 L +359.825 156.267 L +360.189 156.274 L +360.553 156.282 L +360.918 156.289 L +361.282 156.296 L +361.646 156.303 L +362.011 156.311 L +362.375 156.318 L +362.739 156.325 L +363.104 156.332 L +363.468 156.339 L +363.832 156.346 L +364.197 156.353 L +364.561 156.36 L +364.925 156.366 L +365.29 156.373 L +365.654 156.38 L +366.018 156.387 L +366.383 156.393 L +366.747 156.4 L +367.111 156.407 L +367.476 156.413 L +367.84 156.42 L +368.204 156.426 L +368.569 156.433 L +368.933 156.439 L +369.297 156.445 L +369.662 156.452 L +370.026 156.458 L +370.39 156.464 L +370.755 156.47 L +371.119 156.476 L +371.483 156.483 L +371.848 156.489 L +372.212 156.495 L +372.576 156.501 L +372.941 156.507 L +373.305 156.513 L +373.669 156.519 L +374.034 156.524 L +374.398 156.53 L +374.763 156.536 L +375.127 156.542 L +375.491 156.548 L +375.856 156.553 L +376.22 156.559 L +376.584 156.565 L +376.949 156.57 L +377.313 156.576 L +377.677 156.581 L +378.042 156.587 L +378.406 156.592 L +378.77 156.598 L +379.135 156.603 L +379.499 156.609 L +379.863 156.614 L +380.228 156.619 L +380.592 156.625 L +380.956 156.63 L +381.321 156.635 L +381.685 156.64 L +382.049 156.646 L +382.414 156.651 L +382.778 156.656 L +383.142 156.661 L +383.507 156.666 L +383.871 156.671 L +384.235 156.676 L +384.6 156.681 L +384.964 156.686 L +385.328 156.691 L +385.693 156.696 L +386.057 156.701 L +386.421 156.706 L +386.786 156.71 L +387.15 156.715 L +387.514 156.72 L +387.879 156.725 L +388.243 156.729 L +388.607 156.734 L +388.972 156.739 L +389.336 156.743 L +389.7 156.748 L +390.065 156.753 L +390.429 156.757 L +390.793 156.762 L +391.158 156.766 L +391.522 156.771 L +391.886 156.775 L +392.251 156.78 L +392.615 156.784 L +392.979 156.788 L +393.344 156.793 L +393.708 156.797 L +394.072 156.802 L +394.437 156.806 L +394.801 156.81 L +395.166 156.814 L +395.53 156.819 L +395.894 156.823 L +396.259 156.827 L +396.623 156.831 L +396.987 156.835 L +397.352 156.84 L +397.716 156.844 L +398.08 156.848 L +398.445 156.852 L +398.809 156.856 L +399.173 156.86 L +399.538 156.864 L +399.902 156.868 L +400.266 156.872 L +400.631 156.876 L +400.995 156.88 L +401.359 156.884 L +401.724 156.887 L +402.088 156.891 L +402.452 156.895 L +402.817 156.899 L +403.181 156.903 L +403.545 156.907 L +403.91 156.91 L +404.274 156.914 L +404.638 156.918 L +405.003 156.921 L +405.367 156.925 L +405.731 156.929 L +406.096 156.933 L +406.46 156.936 L +406.824 156.94 L +407.189 156.943 L +407.553 156.947 L +407.917 156.95 L +408.282 156.954 L +408.646 156.958 L +409.01 156.961 L +409.375 156.965 L +409.739 156.968 L +410.103 156.972 L +410.468 156.975 L +410.832 156.978 L +411.196 156.982 L +411.561 156.985 L +411.925 156.989 L +412.289 156.992 L +412.654 156.995 L +413.018 156.999 L +413.382 157.002 L +413.747 157.005 L +414.111 157.008 L +414.475 157.012 L +414.84 157.015 L +415.204 157.018 L +415.568 157.021 L +415.933 157.025 L +416.297 157.028 L +416.662 157.031 L +417.026 157.034 L +417.39 157.037 L +417.755 157.04 L +418.119 157.043 L +418.483 157.047 L +418.848 157.05 L +419.212 157.053 L +419.576 157.056 L +419.941 157.059 L +420.305 157.062 L +420.669 157.065 L +421.034 157.068 L +421.398 157.071 L +421.762 157.074 L +422.127 157.077 L +422.491 157.08 L +422.855 157.083 L +423.22 157.085 L +423.584 157.088 L +423.948 157.091 L +424.313 157.094 L +424.677 157.097 L +425.041 157.1 L +425.406 157.103 L +425.77 157.105 L +426.134 157.108 L +426.499 157.111 L +426.863 157.114 L +427.227 157.117 L +427.592 157.119 L +427.956 157.122 L +428.32 157.125 L +428.685 157.127 L +429.049 157.13 L +429.413 157.133 L +429.778 157.136 L +430.142 157.138 L +430.506 157.141 L +430.871 157.143 L +431.235 157.146 L +431.599 157.149 L +431.964 157.151 L +432.328 157.154 L +432.692 157.156 L +433.057 157.159 L +433.421 157.162 L +433.785 157.164 L +434.15 157.167 L +434.514 157.169 L +434.878 157.172 L +435.243 157.174 L +435.607 157.177 L +435.971 157.179 L +436.336 157.182 L +436.7 157.184 L +437.065 157.187 L +437.429 157.189 L +437.793 157.191 L +438.158 157.194 L +438.522 157.196 L +438.886 157.199 L +439.251 157.201 L +439.615 157.203 L +439.979 157.206 L +440.344 157.208 L +440.708 157.21 L +441.072 157.213 L +441.437 157.215 L +441.801 157.217 L +442.165 157.22 L +442.53 157.222 L +s +78.19 230.21 m +78.919 229.983 L +79.648 229.756 L +80.376 229.529 L +81.105 229.302 L +81.834 229.074 L +82.562 228.847 L +83.291 228.619 L +84.02 228.391 L +84.749 228.163 L +85.477 227.935 L +86.206 227.707 L +86.935 227.479 L +87.663 227.251 L +88.392 227.023 L +89.121 226.795 L +89.849 226.567 L +90.578 226.339 L +91.307 226.111 L +92.035 225.884 L +92.764 225.656 L +93.493 225.429 L +94.221 225.201 L +94.95 224.974 L +95.679 224.747 L +96.407 224.52 L +97.136 224.294 L +97.865 224.067 L +98.593 223.841 L +99.322 223.615 L +100.051 223.39 L +100.779 223.164 L +101.508 222.939 L +102.237 222.714 L +102.965 222.49 L +103.694 222.266 L +104.423 222.042 L +105.152 221.819 L +105.88 221.596 L +106.609 221.373 L +107.338 221.151 L +108.066 220.929 L +108.795 220.707 L +109.524 220.486 L +110.252 220.265 L +110.981 220.045 L +111.71 219.825 L +112.438 219.606 L +113.167 219.387 L +113.896 219.169 L +114.624 218.951 L +115.353 218.733 L +116.082 218.517 L +116.81 218.3 L +117.539 218.085 L +118.268 217.869 L +118.996 217.655 L +119.725 217.44 L +120.454 217.227 L +121.182 217.014 L +121.911 216.801 L +122.64 216.59 L +123.368 216.378 L +124.097 216.168 L +124.826 215.958 L +125.554 215.748 L +126.283 215.539 L +127.012 163.232 L +127.741 163.025 L +128.469 162.818 L +129.198 162.612 L +129.927 162.406 L +130.655 162.201 L +131.384 161.997 L +132.113 161.794 L +132.841 161.591 L +133.57 161.39 L +134.299 161.188 L +135.027 160.988 L +135.756 160.789 L +136.485 160.59 L +137.213 160.392 L +137.942 160.194 L +138.671 159.998 L +139.399 159.803 L +140.128 159.608 L +140.857 159.414 L +141.585 159.221 L +142.314 159.029 L +143.043 158.839 L +143.771 158.648 L +144.5 158.459 L +145.229 158.271 L +145.957 158.084 L +146.686 157.898 L +147.415 157.714 L +148.144 157.53 L +148.872 157.347 L +149.601 157.166 L +150.33 156.985 L +151.058 156.806 L +151.787 156.628 L +152.516 156.452 L +153.244 156.277 L +153.973 156.103 L +154.702 155.931 L +155.43 155.761 L +156.159 155.591 L +156.888 155.424 L +157.616 155.258 L +158.345 155.094 L +159.074 154.931 L +159.802 154.771 L +160.531 154.612 L +161.26 154.455 L +161.988 154.3 L +162.717 154.148 L +163.446 153.997 L +164.174 153.849 L +164.903 153.703 L +165.632 153.56 L +166.36 153.419 L +167.089 153.28 L +167.818 153.145 L +168.547 153.012 L +169.275 152.882 L +170.004 152.754 L +170.733 152.631 L +171.461 152.51 L +172.19 152.392 L +172.919 152.278 L +173.647 152.167 L +174.376 152.06 L +175.105 151.956 L +175.833 151.856 L +176.562 151.76 L +177.291 151.668 L +178.019 151.58 L +178.748 151.497 L +179.477 151.417 L +180.205 151.342 L +180.934 151.271 L +181.663 151.205 L +182.391 151.143 L +183.12 151.086 L +183.849 151.033 L +184.577 150.985 L +185.306 150.942 L +186.035 150.904 L +186.763 150.87 L +187.492 150.841 L +188.221 150.817 L +188.95 150.797 L +189.678 150.783 L +190.407 150.773 L +191.136 150.767 L +191.864 150.766 L +192.593 150.77 L +193.322 150.778 L +194.05 150.79 L +194.779 150.806 L +195.508 150.827 L +196.236 150.851 L +196.965 150.88 L +197.694 150.911 L +198.422 150.947 L +199.151 150.986 L +199.88 151.028 L +200.608 151.073 L +201.337 151.121 L +202.066 151.172 L +202.794 151.225 L +203.523 151.281 L +204.252 151.339 L +204.98 151.398 L +205.709 151.46 L +206.438 151.524 L +207.166 151.589 L +207.895 151.656 L +208.624 151.723 L +209.353 151.792 L +210.081 151.862 L +210.81 151.933 L +211.539 152.005 L +212.267 152.077 L +212.996 152.149 L +213.725 152.222 L +214.453 152.295 L +215.182 152.368 L +215.911 152.441 L +216.639 152.515 L +217.368 152.588 L +218.097 152.661 L +218.825 152.733 L +219.554 152.806 L +220.283 152.877 L +221.011 152.949 L +221.74 153.019 L +222.469 153.09 L +223.197 153.159 L +223.926 153.228 L +224.655 153.296 L +225.383 153.364 L +226.112 153.431 L +226.841 153.497 L +227.569 153.562 L +228.298 153.626 L +229.027 153.69 L +229.756 153.753 L +230.484 153.815 L +231.213 153.876 L +231.942 153.936 L +232.67 153.995 L +233.399 154.053 L +234.128 154.111 L +234.856 154.167 L +235.585 154.223 L +236.314 154.278 L +237.042 154.332 L +237.771 154.385 L +238.5 154.437 L +239.228 154.488 L +239.957 154.539 L +240.686 154.589 L +241.414 154.637 L +242.143 154.685 L +242.872 154.733 L +243.6 154.779 L +244.329 154.824 L +245.058 154.869 L +245.786 154.913 L +246.515 154.957 L +247.244 154.999 L +247.972 155.041 L +248.701 155.082 L +249.43 155.122 L +250.159 155.162 L +250.887 155.201 L +251.616 155.239 L +252.345 155.276 L +253.073 155.313 L +253.802 155.35 L +254.531 155.385 L +255.259 155.42 L +255.988 155.455 L +256.717 155.488 L +257.445 155.521 L +258.174 155.554 L +258.903 155.586 L +259.631 155.618 L +260.36 155.648 L +261.089 155.679 L +261.817 155.709 L +262.546 155.738 L +263.275 155.767 L +264.003 155.795 L +264.732 155.823 L +265.461 155.85 L +266.189 155.877 L +266.918 155.904 L +267.647 155.93 L +268.375 155.955 L +269.104 155.98 L +269.833 156.005 L +270.561 156.029 L +271.29 156.053 L +272.019 156.077 L +272.748 156.1 L +273.476 156.122 L +274.205 156.145 L +274.934 156.167 L +275.662 156.188 L +276.391 156.209 L +277.12 156.23 L +277.848 156.251 L +278.577 156.271 L +279.306 156.291 L +280.034 156.31 L +280.763 156.33 L +281.492 156.349 L +282.22 156.367 L +282.949 156.385 L +283.678 156.404 L +284.406 156.421 L +285.135 156.439 L +285.864 156.456 L +286.592 156.473 L +287.321 156.49 L +288.05 156.506 L +288.778 156.522 L +289.507 156.538 L +290.236 156.554 L +290.964 156.569 L +291.693 156.584 L +292.422 156.599 L +293.151 156.614 L +293.879 156.628 L +294.608 156.643 L +295.337 156.657 L +296.065 156.671 L +296.794 156.684 L +297.523 156.698 L +298.251 156.711 L +298.98 156.724 L +299.709 156.737 L +300.437 156.75 L +301.166 156.762 L +301.895 156.774 L +302.623 156.787 L +303.352 156.799 L +304.081 156.81 L +304.809 156.822 L +305.538 156.833 L +306.267 156.845 L +306.995 156.856 L +307.724 156.867 L +308.453 156.878 L +309.181 156.888 L +309.91 156.899 L +310.639 156.909 L +311.367 156.92 L +312.096 156.93 L +312.825 156.94 L +313.554 156.949 L +314.282 156.959 L +315.011 156.969 L +315.74 156.978 L +316.468 156.988 L +317.197 156.997 L +317.926 157.006 L +318.654 157.015 L +319.383 157.024 L +320.112 157.032 L +320.84 157.041 L +321.569 157.049 L +322.298 157.058 L +323.026 157.066 L +323.755 157.074 L +324.484 157.082 L +325.212 157.09 L +325.941 157.098 L +326.67 157.106 L +327.398 157.113 L +328.127 157.121 L +328.856 157.128 L +329.584 157.136 L +330.313 157.143 L +331.042 157.15 L +331.77 157.157 L +332.499 157.164 L +333.228 157.171 L +333.957 157.178 L +334.685 157.185 L +335.414 157.192 L +336.143 157.198 L +336.871 157.205 L +337.6 157.211 L +338.329 157.217 L +339.057 157.224 L +339.786 157.23 L +340.515 157.236 L +341.243 157.242 L +341.972 157.248 L +342.701 157.254 L +343.429 157.26 L +344.158 157.266 L +344.887 157.271 L +345.615 157.277 L +346.344 157.283 L +347.073 157.288 L +347.801 157.294 L +348.53 157.299 L +349.259 157.304 L +349.987 157.31 L +350.716 157.315 L +351.445 157.32 L +352.173 157.325 L +352.902 157.33 L +353.631 157.335 L +354.36 157.34 L +355.088 157.345 L +355.817 157.35 L +356.546 157.354 L +357.274 157.359 L +358.003 157.364 L +358.732 157.368 L +359.46 157.373 L +360.189 157.377 L +360.918 157.382 L +361.646 157.386 L +362.375 157.391 L +363.104 157.395 L +363.832 157.399 L +364.561 157.403 L +365.29 157.408 L +366.018 157.412 L +366.747 157.416 L +367.476 157.42 L +368.204 157.424 L +368.933 157.428 L +369.662 157.432 L +370.39 157.436 L +371.119 157.439 L +371.848 157.443 L +372.576 157.447 L +373.305 157.451 L +374.034 157.454 L +374.763 157.458 L +375.491 157.462 L +376.22 157.465 L +376.949 157.469 L +377.677 157.472 L +378.406 157.476 L +379.135 157.479 L +379.863 157.482 L +380.592 157.486 L +381.321 157.489 L +382.049 157.492 L +382.778 157.496 L +383.507 157.499 L +384.235 157.502 L +384.964 157.505 L +385.693 157.508 L +386.421 157.512 L +387.15 157.515 L +387.879 157.518 L +388.607 157.521 L +389.336 157.524 L +390.065 157.527 L +390.793 157.53 L +391.522 157.532 L +392.251 157.535 L +392.979 157.538 L +393.708 157.541 L +394.437 157.544 L +395.166 157.547 L +395.894 157.549 L +396.623 157.552 L +397.352 157.555 L +398.08 157.557 L +398.809 157.56 L +399.538 157.563 L +400.266 157.565 L +400.995 157.568 L +401.724 157.57 L +402.452 157.573 L +403.181 157.575 L +403.91 157.578 L +404.638 157.58 L +405.367 157.583 L +406.096 157.585 L +406.824 157.587 L +407.553 157.59 L +408.282 157.592 L +409.01 157.594 L +409.739 157.597 L +410.468 157.599 L +411.196 157.601 L +411.925 157.603 L +412.654 157.606 L +413.382 157.608 L +414.111 157.61 L +414.84 157.612 L +415.568 157.614 L +416.297 157.616 L +417.026 157.619 L +417.755 157.621 L +418.483 157.623 L +419.212 157.625 L +419.941 157.627 L +420.669 157.629 L +421.398 157.631 L +422.127 157.633 L +422.855 157.635 L +423.584 157.637 L +424.313 157.639 L +425.041 157.64 L +425.77 157.642 L +426.499 157.644 L +427.227 157.646 L +427.956 157.648 L +428.685 157.65 L +429.413 157.652 L +430.142 157.653 L +430.871 157.655 L +431.599 157.657 L +432.328 157.659 L +433.057 157.66 L +433.785 157.662 L +434.514 157.664 L +435.243 157.666 L +435.971 157.667 L +436.7 157.669 L +437.429 157.671 L +438.158 157.672 L +438.886 157.674 L +439.615 157.675 L +440.344 157.677 L +441.072 157.679 L +441.801 157.68 L +442.53 157.682 L +s +P +0 g +0.144 w +[ ] 0 setdash +3.25 setmiterlimit +450.12 237.508 m +70.6 237.508 L +s +70.6 237.508 m +70.6 2.952 L +s +1 g +[ ] 0 setdash +70.6 2.952 m +450.12 2.952 L +s +450.12 2.952 m +450.12 237.508 L +s +0 g +[ ] 0 setdash +p +0 setlinecap +78.19 237.508 m +78.19 234.611 L +s +P +p +np 74 239 m +74 253 L +82 253 L +82 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 75.19 239.668 ] concat +1 w +[ ] 0 setdash +p +np -2.19 -1.668 m +-2.19 14.332 L +7.81 14.332 L +7.81 -1.668 L +cp +clip np +/MISOfy +{ + /newfontname exch def + /oldfontname exch def + oldfontname findfont + dup length dict begin + {1 index/FID ne{def}{pop pop}ifelse}forall + /Encoding ISOLatin1Encoding def + currentdict + end + newfontname exch definefont pop +}def +%%IncludeResource: font Times-Roman +%%IncludeFont: Times-Roman +%%BeginResource: font Times-Roman-MISO +%%BeginFont: Times-Roman-MISO +/Times-Roman /Times-Roman-MISO MISOfy +%%EndFont +%%EndResource +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(0) N +P +[1 0 0 1 -75.19 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +92.764 237.508 m +92.764 235.77 L +s +P +p +0 setlinecap +107.338 237.508 m +107.338 235.77 L +s +P +p +0 setlinecap +121.911 237.508 m +121.911 235.77 L +s +P +p +0 setlinecap +136.485 237.508 m +136.485 235.77 L +s +P +p +0 setlinecap +151.058 237.508 m +151.058 234.611 L +s +P +p +np 147 239 m +147 253 L +155 253 L +155 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 148.058 239.668 ] concat +1 w +[ ] 0 setdash +p +np -2.058 -1.668 m +-2.058 14.332 L +7.942 14.332 L +7.942 -1.668 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(1) N +P +[1 0 0 1 -148.058 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +165.632 237.508 m +165.632 235.77 L +s +P +p +0 setlinecap +180.205 237.508 m +180.205 235.77 L +s +P +p +0 setlinecap +194.779 237.508 m +194.779 235.77 L +s +P +p +0 setlinecap +209.353 237.508 m +209.353 235.77 L +s +P +p +0 setlinecap +223.926 237.508 m +223.926 234.611 L +s +P +p +np 220 239 m +220 253 L +228 253 L +228 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 220.926 239.668 ] concat +1 w +[ ] 0 setdash +p +np -1.926 -1.668 m +-1.926 14.332 L +8.074 14.332 L +8.074 -1.668 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(2) N +P +[1 0 0 1 -220.926 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +238.5 237.508 m +238.5 235.77 L +s +P +p +0 setlinecap +253.073 237.508 m +253.073 235.77 L +s +P +p +0 setlinecap +267.647 237.508 m +267.647 235.77 L +s +P +p +0 setlinecap +282.22 237.508 m +282.22 235.77 L +s +P +p +0 setlinecap +296.794 237.508 m +296.794 234.611 L +s +P +p +np 293 239 m +293 253 L +301 253 L +301 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 293.794 239.668 ] concat +1 w +[ ] 0 setdash +p +np -1.794 -1.668 m +-1.794 14.332 L +8.206 14.332 L +8.206 -1.668 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(3) N +P +[1 0 0 1 -293.794 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +311.367 237.508 m +311.367 235.77 L +s +P +p +0 setlinecap +325.941 237.508 m +325.941 235.77 L +s +P +p +0 setlinecap +340.515 237.508 m +340.515 235.77 L +s +P +p +0 setlinecap +355.088 237.508 m +355.088 235.77 L +s +P +p +0 setlinecap +369.662 237.508 m +369.662 234.611 L +s +P +p +np 365 239 m +365 253 L +374 253 L +374 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 366.287 239.668 ] concat +1 w +[ ] 0 setdash +p +np -2.287 -1.668 m +-2.287 14.332 L +8.713 14.332 L +8.713 -1.668 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0.75 10.5 m +(4) N +P +[1 0 0 1 -366.287 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +384.235 237.508 m +384.235 235.77 L +s +P +p +0 setlinecap +398.809 237.508 m +398.809 235.77 L +s +P +p +0 setlinecap +413.382 237.508 m +413.382 235.77 L +s +P +p +0 setlinecap +427.956 237.508 m +427.956 235.77 L +s +P +p +0 setlinecap +442.53 237.508 m +442.53 234.611 L +s +P +p +np 439 239 m +439 253 L +447 253 L +447 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 439.53 239.668 ] concat +1 w +[ ] 0 setdash +p +np -1.53 -1.668 m +-1.53 14.332 L +8.47 14.332 L +8.47 -1.668 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(5) N +P +[1 0 0 1 -439.53 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +70.6 210.041 m +73.497 210.041 L +s +P +p +np 61 203 m +61 217 L +69 217 L +69 203 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 62.44 203.666 ] concat +1 w +[ ] 0 setdash +p +np -2.44 -1.666 m +-2.44 14.334 L +7.56 14.334 L +7.56 -1.666 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(0) N +P +[1 0 0 1 -62.44 -203.666 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +70.6 157.941 m +73.497 157.941 L +s +P +p +np 61 151 m +61 165 L +69 165 L +69 151 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 61.69 151.566 ] concat +1 w +[ ] 0 setdash +p +np -1.69 -1.566 m +-1.69 14.434 L +8.31 14.434 L +8.31 -1.566 L +cp +clip np +%%BeginResource: font Mathematica1 +%%BeginFont: Mathematica1 +%!PS-AdobeFont-1.0: Mathematica1 001.000 +%%CreationDate: 8/26/01 at 4:07 PM +%%VMusage: 1024 31527 +% Mathematica typeface design by Andre Kuzniarek, with Gregg Snyder and Stephen Wolfram. Copyright \(c\) 1996-2001 Wolfram Research, Inc. [http://www.wolfram.com]. All rights reserved. [Font version 2.00] +% ADL: 800 200 0 +%%EndComments +FontDirectory/Mathematica1 known{/Mathematica1 findfont dup/UniqueID known{dup +/UniqueID get 5095641 eq exch/FontType get 1 eq and}{pop false}ifelse +{save true}{false}ifelse}{false}ifelse +20 dict begin +/FontInfo 16 dict dup begin + /version (001.000) readonly def + /FullName (Mathematica1) readonly def + /FamilyName (Mathematica1) readonly def + /Weight (Medium) readonly def + /ItalicAngle 0 def + /isFixedPitch false def + /UnderlinePosition -133 def + /UnderlineThickness 20 def + /Notice (Mathematica typeface design by Andre Kuzniarek, with Gregg Snyder and Stephen Wolfram. Copyright \(c\) 1996-2001 Wolfram Research, Inc. [http://www.wolfram.com]. All rights reserved. [Font version 2.00]) readonly def + /em 1000 def + /ascent 800 def + /descent 200 def +end readonly def +/FontName /Mathematica1 def +/Encoding 256 array +dup 0/NUL put +dup 1/Eth put +dup 2/eth put +dup 3/Lslash put +dup 4/lslash put +dup 5/Scaron put +dup 6/scaron put +dup 7/Yacute put +dup 8/yacute put +dup 9/HT put +dup 10/LF put +dup 11/Thorn put +dup 12/thorn put +dup 13/CR put +dup 14/Zcaron put +dup 15/zcaron put +dup 16/DLE put +dup 17/DC1 put +dup 18/DC2 put +dup 19/DC3 put +dup 20/DC4 put +dup 21/onehalf put +dup 22/onequarter put +dup 23/onesuperior put +dup 24/threequarters put +dup 25/threesuperior put +dup 26/twosuperior put +dup 27/brokenbar put +dup 28/minus put +dup 29/multiply put +dup 30/RS put +dup 31/US put +dup 32/Space put +dup 33/Exclamation put +dup 34/ForAll put +dup 35/NumberSign put +dup 36/Exists put +dup 37/Percent put +dup 38/Ampersand put +dup 39/SmallMember put +dup 40/LParen put +dup 41/RParen put +dup 42/Star put +dup 43/Plus put +dup 44/Comma put +dup 45/Minus put +dup 46/Period put +dup 47/Slash put +dup 48/Zero put +dup 49/One put +dup 50/Two put +dup 51/Three put +dup 52/Four put +dup 53/Five put +dup 54/Six put +dup 55/Seven put +dup 56/Eight put +dup 57/Nine put +dup 58/Colon put +dup 59/SemiColon put +dup 60/Less put +dup 61/Equal put +dup 62/Greater put +dup 63/Question put +dup 64/TildeFullEqual put +dup 65/CapAlpha put +dup 66/CapBeta put +dup 67/CapChi put +dup 68/CapDelta put +dup 69/CapEpsilon put +dup 70/CapPhi put +dup 71/CapGamma put +dup 72/CapEta put +dup 73/CapIota put +dup 74/CurlyTheta put +dup 75/CapKappa put +dup 76/CapLambda put +dup 77/CapMu put +dup 78/CapNu put +dup 79/CapOmicron put +dup 80/CapPi put +dup 81/CapTheta put +dup 82/CapRho put +dup 83/CapSigma put +dup 84/CapTau put +dup 85/CapUpsilon put +dup 86/FinalSigma put +dup 87/CapOmega put +dup 88/CapXi put +dup 89/CapPsi put +dup 90/CapZeta put +dup 91/LBracket put +dup 92/Therefore put +dup 93/RBracket put +dup 94/Perpendicular put +dup 95/Underbar put +dup 96/Hat put +dup 97/Alpha put +dup 98/Beta put +dup 99/Chi put +dup 100/Delta put +dup 101/Epsilon put +dup 102/Phi put +dup 103/Gamma put +dup 104/Eta put +dup 105/Iota put +dup 106/CurlyPhi put +dup 107/Kappa put +dup 108/Lambda put +dup 109/Mu put +dup 110/Nu put +dup 111/Omicron put +dup 112/Pi put +dup 113/Theta put +dup 114/Rho put +dup 115/Sigma put +dup 116/Tau put +dup 117/Upsilon put +dup 118/CurlyPi put +dup 119/Omega put +dup 120/Xi put +dup 121/Psi put +dup 122/Zeta put +dup 123/LBrace put +dup 124/VertBar put +dup 125/RBrace put +dup 126/Tilde put +dup 127/DEL put +dup 128/FractionBarExt put +dup 129/EscapeChar put +dup 130/SelectPlaceholder put +dup 131/Placeholder put +dup 132/Continuation put +dup 133/Skeleton put +dup 134/LSkeleton put +dup 135/RSkeleton put +dup 136/Spacer put +dup 137/Cross put +dup 138/DblEqual put +dup 139/Grave put +dup 140/Acute put +dup 141/DoubleAcute put +dup 142/OverTilde put +dup 143/OverBar put +dup 144/DblUpDownArrow put +dup 145/DblUpExtens1 put +dup 146/DblLongLArrow put +dup 147/DblExtens put +dup 148/DblLongRArrow put +dup 149/DblLRArrow2 put +dup 150/DblLongLRArrow put +dup 151/UpDownArrow put +dup 152/LongLArrow put +dup 153/LongRArrow put +dup 154/LongLRArrow put +dup 155/ColonEqual put +dup 156/Diamond2 put +dup 157/NotSquareSprsetEqual put +dup 158/AtSign put +dup 159/Solidmedsqr put +dup 160/OverDot put +dup 161/CurlyCapUpsilon put +dup 162/Prime put +dup 163/LessEqual put +dup 164/Fraction put +dup 165/Infinity put +dup 166/RuleDelayed put +dup 167/ClubSuit put +dup 168/DiamondSuit put +dup 169/HeartSuit put +dup 170/SpadeSuit put +dup 171/LRArrow put +dup 172/LArrow put +dup 173/UpArrow put +dup 174/RArrow put +dup 175/DownArrow put +dup 176/Degree put +dup 177/PlusMinus put +dup 178/DoublePrime put +dup 179/GreaterEqual put +dup 180/Multiply put +dup 181/Proportional put +dup 182/PartialDiff put +dup 183/Bullet put +dup 184/Divide put +dup 185/NotEqual put +dup 186/Equivalence put +dup 187/Approxequal put +dup 188/Ellipsis put +dup 189/ArrowVertEx put +dup 190/ArrowHorizEx put +dup 191/CarriageReturn put +dup 192/Aleph put +dup 193/IFraktur put +dup 194/RFraktur put +dup 195/Weierstrass put +dup 196/CircleMultiply put +dup 197/CirclePlus put +dup 198/EmptySet put +dup 199/Union put +dup 200/Intersection put +dup 201/ProperSuperset put +dup 202/NbSpace put +dup 203/NotSubset put +dup 204/ProperSubset put +dup 205/ReflexSubset put +dup 206/Element put +dup 207/NotElement put +dup 208/Angle put +dup 209/Gradient put +dup 210/RegTM put +dup 211/Copyright put +dup 212/TM put +dup 213/Product put +dup 214/Radical put +dup 215/DotMath put +dup 216/LogicalNot put +dup 217/Wedge put +dup 218/Vee put +dup 219/DblLRArrow put +dup 220/DblLArrow put +dup 221/DblUpArrow put +dup 222/DblRArrow put +dup 223/DblDownArrow put +dup 224/Lozenge put +dup 225/LAngle put +dup 226/Diffd put +dup 227/Expe put +dup 228/Imagi put +dup 229/Sum put +dup 230/LParenTop put +dup 231/LParenEx put +dup 232/LParenBot put +dup 233/LBracketTop put +dup 234/LBracketEx put +dup 235/LBracketBot put +dup 236/LBraceTop put +dup 237/LBraceMid put +dup 238/LBraceBot put +dup 239/BraceEx put +dup 240/Slot put +dup 241/RAngle put +dup 242/Intergral put +dup 243/IntegralTop put +dup 244/IntegralEx put +dup 245/IntegralBot put +dup 246/RParenTop put +dup 247/RParenEx put +dup 248/RParenBot put +dup 249/RBracketTop put +dup 250/RBracketEx put +dup 251/RBracketBot put +dup 252/RBraceTop put +dup 253/RBraceMid put +dup 254/RBraceBot put +dup 255/Wolf put + readonly def +/PaintType 0 def +/FontType 1 def +/StrokeWidth 0 def +/FontMatrix[0.001 0 0 0.001 0 0]readonly def +/UniqueID 5095641 def +/FontBBox{-120 -220 1544 923}readonly def +currentdict end +currentfile eexec +D8061D93A8246509E76A3EC656E953B7C22E43117F5A3BC2421790057C314DAE3EFBFF49F45DA34B +424A961BE670A194E7E4BF0FF295DEE23134A14A7C08B6602621D885EE631B1D8D3003CF2093E039 +4D77FCEFCA8BA8965D1783DCAD9EFE6C7E420CF7B898933309A89F84721ADE7F3AE4443C5EAE8155 +759A9EB6F738F7BA81C192EE45DAD00F398FFD6904111BBD91BFEE328004F2A18BCCD98DCDB2CE23 +961B00F204E50EA022D00CE2B68E653626D4BB5AFA334A0D657307416FAF7AA8C43ED4DC541F1B7B +B7500B3F423D9D369F8192FD00A59FD5E6ABC70F788FB70976CC1907DDC309F4B690AA2D2BF12CAE +C493958CC0E76CE9EB5FF8BD1F1650F659E5C123EE455B7D77C39952C212AF319BF19A91E36DE52F +0EF84B602704BD6C7C94E1B0E067210DB919F6231755A2CC5D5FE129279B43A2E2CCD27F56F00B05 +C8AC10AB07FABBEFB3509088301FE78CAF8B440C5BA2FFE06BBFCD066046618F3B6AA2E4B17B296E +1F3F1560E19CBAD54E16E4B7A65622E468F6BDF97C50277E355D1DD8843D0A449A147FFBA071BA99 +CF70D7576DC18B96FEAF8070BF25F3A3141F873241EF4A07F332306B56F1D5909A4F233A9DB3A08E +E43DF38DD6DB2D6DAB908967A907303EE1FA04F048FA6EBC531738C170B8B0F095FF3B05D14C2BDC +272F7EDA8926A77D9CDA49A90AE1387A51A24ECDB2E4E287B0F95A83AD2EC0310F9B6F396AC10479 +835035FD5D4C84D91917FE8A8755C976504AB1A830ED516B5F325EA4ADFD115900D23039A2BC84EE +D21CC21E2BBE29A5E0CF28BE047CBD515DF7785F37DDD4474042B102A1F28193BB8567FF6FDEF811 +25CE9A504BE5011C0010DCEBCF321824C9DA249D8DB11F79298F7B674CEDB6F33C111F8B0115E407 +99E0FE1B6CE9F6B2A3EED1827A9CEB453D643FE5CE2540DCBCAF7B2EA2C8F0AE9434D4BAAEAB3488 +FEC7541D57179BDEAA0BB6145EA783E73E70E0AA71A4FA58E81EB510E9FD2CF4ACFBF28E48CA313C +CF5ED2BE032B7C9A07ABBEC9CCD8D5AC9F775D36BECC252B9FE01B6AA79A70E22478904DADDA08BB +7CA3B66F3A83AEEBA37406127353790348AE5FBD144ABD8E1B32D1BCC70E2BC22A63E854D38317E6 +BB97C52A6C9B0C6AB5C336CE2D417A714825DCD237F7486C33925A995CD8AD3B359176C4CA775FE2 +2C6F8A7C7343F31F39D1B9A5A744973BF65D655DDB59E3495B28DBE2A877EBB32A22A4AB8EB13C67 +02B239F6932E2DC8B4B88915923B1C2AFF2F876A31F765B91747D5A858BD632B0BE4F135AC484016 +AE1BC01E44B6653A58EE1380B6DF24AEB73220A36FA8FDE9A152C16E049D2F247A9AA71CD2DF3D9E +653054FAF518BBC1EEB43200DB0BACA93FEA9445FA5F32A99926C4F1F2370B2E3E03CEFBEECE5D5C +DE299FE1641A9CE0C90E42E7C465DF8C19C153EA76C11791F8E112853E708CD0F6EFC22E44808A44 +3686442645D643E7118D128BF34188FD313E53B8E951E605E96825738C4DC893D942C145C3E1153F +CDED16D1EE10374626F45781461FFC94C77D431BCF167FD29136A0B369631E139A59B06AC5281B3D +52470B38237B469C0E49CBE74C82A940F8AAD10E05C5DD2A8F3218C4BE40DCED0982770869190845 +D2E8BA2A1A2F9CF16DDDE418B6E2013C3258FBE9AFCDACCD57B01C4FEF3526C06FD5BAB6F195C557 +23A96FA3E5CDB2ADC9AA8989DF78F163F19E26F3B9EAF60D44B745FCA49B09D9CE5CC16D1E2C78C5 +A28F14D3374956E9721C0542A01A731A3D5A771C7B51FB72960BB22C99BC8F216530AA86C1A0346B +3D986EF1DF68BCC46EC4C809C6D54FB9C50F225ABA2C96965F4DE77E2C5D131318231C6773F0976C +DBD016B803C38B8923822BDF160FB87BBE744D0953EDEBDE87394282B9950C89F58824D731355E8F +A3CE364F742E3337A983BD1219CE8787CFA6925B560001F6F78343647821D408B60965966C16C454 +394D33268483F51984614FD9964CCE5F1AA4AB12144874A72D3FE97F4416ABE4213B4EDCA9ECF73A +937B562F978E059705E561A6D0C8B89B59B4CAB2248BFC4A6D3D98F89FF38A4D1C3E5A4570E2E2E8 +D328772E11DEA2116C260B13328045139A819F5489A2C302F311898230CD26DD03E6CE3BE75EDB86 +0D982FBC8E1E24D1F83E8CA64174495B2F32BDF0505FC96E9C65FDB0EB0C4ADA410F0A1C4BB4D551 +93B1AA5EA1F988882A56413F77CF24FF6516CD5095077BA566116797895FD7EA616D10510957025A +1DA05064FD9176303A248C94CE2A9875C03C7D8755A1A8010B712B64BAD73BEA4195D51A328F076D +12C0E52E87D98A5269D157D544CD7B4E1CAAEA6BDD59F08D0C5FBB84A6B099BECF8BEB721A874BAA +1BD1F8CDB5ED5CD3B49A763EAA36D33837667A6643B83537EF5515DBF7659E28692E0ACEB48FD051 +45534A443092E0A510B05B5B51B402CB3372A4E8BAF98A7FEB6D8BEF6B364A5EA0F3FDF301A44EE2 +3485D4493E7B4C33E0E352202931188D156983C40F7A4B615B6F5281B33FB32A2E6A0F7AE84BEA2C +5C3635D7DA17371D608847EB402270B83293EC09E4B1D85996C1CC81196BE590EC9EF0F258364746 +BC4763976FDDB3707D37AFCBDC0F4EB2B1502F137EBBB1541B992EAD43B5D64CCDF505FF2F127115 +4857C69B689A11760979F6D2BB032CF0DCCBB33D2B6F6D6BB29A1D0E371AA5869A408225C0AFF523 +CEFB08A7D3D17DF9064010363196CC569C436E720B1B4573CDAE1CD93A10FD2D4ACB14E47046B5B7 +66B75B40BA97027FEC4218980B002FAB60A9F1F5A37861B9A63F696732F8127B2C6F036BF32311B8 +FF08A489E88218092D57A99C8981EF8CBBD09DF49CC836B9C1968692D1FB551F47619F020289F1A3 +D3898A96DC1C7D39A21B49C2D0DD9508CB8A8BD6A5EB40FF86F555FA32017B67AEC07BECD659E8C4 +8DD1D43E8D1EE08A117479015F78BF20D3318224572D9C90884856E4307A8AFFC83EDD317B008484 +3BBE8EB2A4E2D70077A639FE3637C3DCF87C67F1BE62E63CC67BCEF8EBD07E030524A53DD440F2A1 +3A019B7AA89E155AAD1497974258A01DA45DE1D94BB9F925290FE9BDDA29EA3F2BF1E64DF7EBCFC4 +23AB2C7310D9D87A5CA673EE95189135E1B134B431B231428FF2BF64C8F155CBDDAD17BCB524CF7E +ABD66B75705BFFB1DDB27B72D681D7AA19FAD0FF23EEF028B606DD20D58588AACB299A3CF38372A8 +E7494A65227C1E4A231AC7843962B8A4A7BDD90F3F8B83277E696F3514139BB8DD207641B62286F0 +0E32C7FAD7029DC0E092564C7CE1BC5240FCBFB00A06F075D9536F0326CFFBA958BA7A1A11AA047B +B14E7DE16761BB0FEF19ABE85154359807C339961B9695CDED59E489CA4D9BB75D86D9EDDF0502BC +0B4EC36B1D71FCD4E03881ECEC703E5DA23B3F5DB98CB8DAED81D5BA20B844A92A4B92FE5B082952 +6837237C7F994786878404BE744D0953C676E52CB05FCE193E8827F977B31929E36E320E770A6F1E +972B84AFA21E01719759DF0132C5CF9692BAA487E86E8453C09FF97642600D1024ED5D6C7D5B387D +CB5E6147D20B4A14D7D485B5747D765C5A1CA40B9416DC4EF5DC08F43F0706B27B1C90E901556E1C +EFF304EA8DF8D727E4B7E7CEAD14E4FC7E76002DBC37B92BD0E58AF29DA7DA91E6F99DADF1A9CBDD +04648E2F5B811F2AF0729646B17D32D7EF25AD47E267EE53A61A7CD744C9ABFDB1EDB71C0705412B +15769188CA1601AF0395796FAC0E2139EF90FAA65D27AAEEEE319D2831A56CE82203523097D0574D +22742D17E519D95560B8445B5B29033BF3454B94F20C23EBE8B90DDF26D903F1D66CB39D3A64579D +39D136C537CCD9FF2D6ACE5F79DE696C7330C0C4EA179D7911B7B67858D86CEE0833AB1E105B1993 +2A9BD572C41E78FB4A2A2421514DC2066E2F56101A9027F4BBA5D48E5DA9218E81CE46B95B50380F +560C67A5D547A8D32610AECECBB6F5A09DF44994F6DAC64F7399C07E182033BC1B9A32B69C41FDFC +7E1DCDDF273F045F154D727AFEE3CDB816CF2ECDB6733C579276353DD59C9D2AFA455935C1FCD0AB +7D57F9DD79FBCC7A45E5E5A501FF17EE1C5FF0823C6FDE29C60F85F6786940D8E3540E891B5BF7F5 +D36C57AC3AD359BFAB12B215E7FC94B933386C250E6B236506FA3D151ABAD893A817772071C09144 +6E5FB23A17591A4CECAA46DD46E3C41B4578F21D8894A59B72FAF6F88EE6E514FBD2FE69447D2B59 +9A6AA8BC5C1FD5C25E50BFB5CE5DBF5AD5771BC42FCC3706B6E9F7E4FAAFF2E63ED1684C5A4C136D +609D03E31EBCF31E864AAA74D8DDBCA52F85CCF14AB739CC55617EFC964D6CC6988AA45245B19CE2 +B63CB865DF1F1DA4A200A4A043C5CB706CD303EB31C32866ED92077AB11BF136D158840EAC67E7A1 +1BC2BFDCD5894AF735D677E1AC98BF3F19F69AF75355F168632037F6EDEBF61BE5854057AD05972C +7DA8D37AE65D35738547A9D835C6351D8D732F8C0DC49D7605F00A6A6045559D3A0B0CC21DFDD75E +2FCF25522001741EBBEB5CC97DDBD9DDCE490FE3CB186C101940DD02CACB685ECCB8C1DEDCDD4156 +F5F9F6B3CA53EC6E7E8A2F8159451CD5479D91BFBF91C6B32A1E0053017369330EAD2DDE323BCAC5 +EEC91A595473F447355B1BDFB873D0B538BF54AFB8EAADE5309E1B74283218F59272E59619D66405 +E74B5A9D6323CB76AF00FB27FD984F740601248C7206F59EF7FF4E95AF95327D12C47D2D34CBFF33 +29F28236831D7F0FD9D633B940D8282E1F1D5D95616CD839C6B41A7195A22B7A320864E48943CE99 +C68E3591A97689986A6536C3A1C37DA9838FF71A8C1A03094537C95D3585DF5AD9292874D8D05720 +030133B02E2754DA24712B52D6D8871422C709532B09D147EC8ACD861FA5392A82A6C2981347E327 +1986017B8315822B5FCB19A1B31BF6909E0D6490EC823434BFCE031045D20FFC675A73EBD7A39A50 +44C3B40906186CCF6E8502CD7915F053BC8CF6BE9FDD9A0330AE943D5C9A11D60D43F5BBE8A045EF +CDB7D19C0053F4F082303442C35C432E0DA4408C5917D24A6658DB807BD754AF140CE84A34F79851 +9323060D60F4EAC0E82D3E17042BB6729C69A8B8774904C6214C2EB016C528DC1428DB41075AA6C5 +4E4D849901838C6B6DADF7B67CD0CBC6EE1B415B44654D89397274D4A6AD2BA69DD81567F5B802F2 +684DD3516ECA0F6D66046EDA0B2B38F420A238D67D98676925ECBE6711D64DAE5DBE8AC5473A2EE9 +97AE65F0715A2E5DB2C75F20D9939EF58692EDA3AEA81F25AEC888327CFA6CC13D496714E63A1A87 +11FC50250D6D23FC7A8017038281F659A685ED7F1BB1ADBF8833ABC6DBEC0D96D08334E58C67E0F9 +0046132F0D4FBCB9CDF52EE74561498B42701AB826A6DD77F46C14753748E1EC66F4BD3583FCB4F1 +DC91050CF18B0D51BC366549261282779FC9694A7B987973C0D59F65CFF3CDB88D23952E46F5EEC1 +BDA0DC354188C11B0FA191F5C11A45BB9093701B33B8E7AC1A8621793D5326E92CDD3A76FB9B67D6 +860567B9CEE7B9F9056C6E9971F642DC0BCC1D2882B5BDF1B1CDCAA3FC61775A74E70CDFC128DE0F +9606091BB8E53905108EE77A1D3C506550FCFCAE454B020379BE3A7D6C62D162697AF1383F7BC8F2 +34FD616324C1E56E3E440277862CAB2C02395D9937816117F71A6828344182E6B5AF2799E29A6611 +9C0543E135300E44746EF2EBA57C4EABB1A15D8AC6D037F4BA2BE1EB4D1A7496F9711FC67E56D4D0 +FDA4D810B5383A72ACA15872DE9F3A9890E33759CE4DA493691BCA47A92E42D950DF588C3574D6FC +72B5AF7DDE915E3F5925B3E97DEBE6A4208CE7337754045607679296C4EEEA3A3EF1915E85EB1A32 +F1BBADB2904B09806631E20BBF5E57AF11BC784C75BF049B4BC7E479F1D4AE7754CBED7B11ED80A5 +2DD0006FAE5CC23A7C234CF628B42F4104A490E7D59E8B1F2A1395D37641C478FF8CB9FB602B29FD +3E48D6087CAEE48B523887F27F69DB32BF80B760B030A6766F8F9B19DE70704DAF81D3DED2BF663D +89B5BD8AF253BB8FA59DF84A52FDED83D2737532B6D66AFB9EF0446ACD44BFAB797AB9FDB47F2E18 +8F0A55887853772EBFD029B5FA0AFBAF10A88D464BD6F634C5839B230D583E36671A51DDB1EBF471 +8ABB84D057729D514751B0EEF4F756B91DEDAD7D9AD529B3886008F1F32645F3A205F304B2A8D808 +D37A38E389B8587E8D3654DC374568FCEBBA160395BE1D132B1ACB434164525FBF250A5AA30F3520 +F0F2A75F9D1B7C6EAB0223C209A120D894D6ECA336B57B7F2AB0E2A94E6616D7539010C700311966 +7B4A9EB3A812FEF4D100AB4C036401D21DDF8AEB393074638D154418D3A7AE51CD1F0C2D5CF4B475 +3B582D5071F91E6D3AFBFB09EAABBEAB2A60A5D388D16232939B5483CF7D5C1C9D05DDC85B266F39 +6F61E179AB9FAB644040014293EB53F2B50E8751F9D92D1DAE8DC89594905A39357C0098265FBD24 +E407F9A22027E36D5608FAF15BD9E354228E3BA943EC30301ABB2CB105F3B6D57C001EBF8328A8CA +318A7B1B999AE8BF9E2FD530B669640116149B622EB3C0A8CCDE62449DE9D39E0F5E5E01CBBF4F5E +52B33A2BD60B52FA4A5CE98B7313FE4C3FA30FA07DE4516109C3EAEE97ABE430C505647DD8B3DBF2 +BB4B3A806B14A9E0F629863F15D12A1CA62E8E055FA6ACABDE1926D3231CAC271C30A3F7AAC6084D +D88628B943052B618E4E39148600AC1EDB57B0835C291F73D29C51FCA3C5EFB1DB18A5CA47433B45 +C57EB3CB28AEBC68E4171E6DE357793B0FD2A1F4E76180248017112674DAD7ACA6ECAAF209CA174A +5603CEA5CE528F20919443900636E2FC79C303EA7B467A3F001EA8CB849BCF28BF40A3364C08ABC9 +B5D935CFEDA827A8C3FE2F6ABA54017D2AD0393A05AE21F7AE1F58AE1E992B5C889219DA157FA7EE +92E291DE2F9DFC244F2CF0FDCEFCACC487F0EA830985B687556D5AF53B81814DE9CE0C6C430DCBCE +51EBC6091B4E0227732E07DF20D59E9EED4D8A83761CED84CCE244BFD6A39103A2B48A684AEC9210 +5C94495D54FD6690AF857E5A3915E6A15689F1816F75F1FC8D5769B240639B339EBE54BC6D84794D +50F4EBE892530351984C6F8BEBE097CD46F4FED7D46E32830A16F77C13F13D17003737482F02D1B6 +F04C57B4C2B1929AA315B4BE1C7C9CB55F8981508546B67E4EBF84B6026C355C5E4E07CD98C95F07 +56F6643FB1DD7D8C77C4AF4C4F23602DD3F61D1C46583071AC460E74084F7F7CF812BC53975CAAF8 +B3C1160B5D6864AF08A1733FA893CE4248C8F8B46AEFCCF317DC726BC1E5F2445E462E0591BEAAEA +49AD8E4E2D3CF07F900EC46D596E9CDB3A8710A0B93CE5DA9D35E612596A6374F35AED0EF55DC46A +8E14A91163B87417259DE926BBC3FC5423FF0AE2AA6D740BFFD26981A57C8C1D97FB04A90A567296 +B07437F94C8FFF4709213DD5D8862A777CF3F97723F43A4F913F4A30F7554ACDAE34713654E21731 +C594C3718C0547FCDAF7BB1620B2D6BB19E65596A585290CC43F50B34A2FE6EB1E495ACFFB16DFEE +8784B66FCB9359600412969E2BDA330C421C68345A797C49728450A6CF41C926AE8EBBE80BD97863 +593C3E8AB5415A8BA625957F242378948F5C7EA9C2641312E8E46955FE5C1A1824C71D3B0C9FD211 +7CC965DA7C21D257587F291AB7C594459A11B977A278C74CF5E6598B0C75ABBB2FC1B3D167D7E31D +B519F4A0BDA650A3FE7F1A281DB9B69B57A23656BD820B22D0E30B07361FE7C90933E24A32B6DE2D +F240635D543315226E32487F264AFE83EFEAC74117321A82A92F4FC7B259F26DBE74C966B4CB3F4E +3499E30B9B326F72D73919D9FA9024AAC0B95B6751AD4CE569CC3DDFC399428DF1298FB1733FFCE6 +240FB3BE3A2837E1A66E08B784CDD131B5A61423508000785CDC5610CE4DA1DD314131767C8857E3 +3D9741EF3FD7B8D0AF0371CFFA3DCF74FF2D3B421AC0339EBC05FB4B61A2F46D6BD1144747AD148B +7968D7DF4D0F742AB39B0A04A88D66F5CF9876970879879872BFDA0C56C253DE5C72016B4990CEBB +2455DCDEC7465EE7C7E1C557B570E9F3EF3E31A22DC8AB9B17F34F48E135BE7820ACE383DB7C5B05 +8A9DC855C6850E49AB7D7D00ED2D23C50AEE671B11752F0A682EFE179CECBFAB47B76676AC0E8FD1 +0A47353D3AC3477A59B0F3CAF22849DE97AAC8B79935F7C57F3052DE7E13BA0FE7CEC4685C86E841 +EA8C7D6734D0FEEFF538CC2AA1414EC9126637E169FBE4ECAFDFA29A08451B25954F0094710195E1 +69E0A6004419E432E9319BE2AEC8D93407B25B758336946C6E30032657DD857BE9C0A05F487423D2 +0C2772D4416D27FEB5CCC971DDEDFE9B8C2D7DF9DEC90D0868D5DD18850BE567A04D08310883D9B2 +D50B817D0690F8550E238C1139C0A117B48199B1B4D489C9E52C58E0CA75E6C386ADD447D8AE52D1 +D979FD52A50D82BBCB8867B5D49C19BDEC414929DB67A00AF7C6482A87E46BD11D8E4E52783E5845 +FB2CC7439F99FF6552C7288354B436B1C361AB8C2932F77B14A50A7407FC0BCC29662D710248CA46 +AC42A03FBBEF9C972783942F3721BD32BDA3273D1E353D9E013D4CFF630BFE4E7C2963FECFE350A2 +860421D17D6ACA888FA26403726A0E16BD10638D8511A2C8A36C99E9B72A422038E1149BF88A7CA1 +B2DB196333F3AD32D6FE28F18FE1ADA06FD25E4597936A924E71C188B9E8779BDBA928A182042C96 +F6A14A6FAB5C1A819DB8E9374E32F77393734A65606809E90D188F749180A3E6CA7AD8044E168677 +15FDFF350D70B14454B692715DC3AE2CAA561FB953B478E873EB19C41A8D83288451E4E910D5988F +33D83C5974DD0EE4DF2E6C849C500D0714038ECB1E9336D71D852889F2FBCA6769949699BE6BBF19 +A9318CCD7C845D0EC7FF3CFD871328CF7B45E6BBBBD16094E8ABE4D3789DEAD2C930AC8D810C911C +03AF2BDA4EBA62810F19B4506592005ACFF16EB58E33D6A71EA4DAD28A2D7B10FF89ACAB4BCC0F96 +E622EBA20347AE04C62A298257D1623977D185BB46B42CCDD733F72B37072B8DFAA5D7FF77E35618 +3F6D25EE1D951F7EBFBEA8FA17A859B8545BDB212A6BFE9C3078C32124C4E5D4DA09404E40498F76 +7B7164C13E12BF006EE8DE408070296EF0A08AF759206DB3C445BF49EAB18ECDE1FEDEFFAB653FDC +B13FA4649B35D12266FD42D0498331560E96F54D4238678F70233F56097E1944FC671D6BB1AB66CD +E0E9DC24349E44D67C36B3E3A00B07755749A597DF31C25F28D55779841BD3F6221BCDE389852DD4 +590E075C1298B36181D9F64BDCB54A556C05A9EF661EA1CC7C7549A3E1CCF79B87A6E71C3ACDECC9 +C4EFB05B643744407029258B8225DBF960DE529EEC262D1D3113F6CDDBCF4BDAB706BF085C0FF2EE +E8420CF755E46B053B774DF37C5683F8432EEC183C96176BFB24B3F478FACACBF1FCB73D83D4D857 +2D0F15783C8AE95D7CE959D794FDE1D973867D8F30A301598BDB7F2824B2629D64B88F0FF4498B6F +3728CF8916EA884C5930677E7481F90C85ED41DD28AA00E714D3A4F2CC136E417A4C591C7944C409 +70D2BCBE410A940937C3CAA118FA32317444B401968B8ECB2F0B3C8DAF6D4886C2015000A71FDAD4 +066B82312A4CD1E49A9ACFA05C3E7CA5A5CB3FA713CA0AD9E66A34730A36612C72D1F803D4CB1501 +9184FA2FDB3E5D067BC64B29299D5531565264B70FFFF86F5A5B03848E55D73337650208D21F35BB +D5C14748CBE17EB3A7E02BE53136DC69E8740C597CE28472CAEEB96EF2A5752CF17CFBB82F6C104F +2BBB50C216C49E8AB7E661207E1742B35392752051A1E409BEDCDA2654CB5155B770C8C5E4A2968A +A35CF1924D0C74A8D23AB811B7DCE60F1EBC1494A295C8D670E84B7B886A6064151065BD96F2D364 +7BA049A444CF16EB6432CAFCC70FF2E8B354F55A192C94BF08D156856A6512403F5140DF4C8D254E +DA59B2B3ADEE19A35E9A61202B711193A7E7BA8EF427152157DA5A51084EA46510864E1CD7B4FD11 +16E74D7BA769ABCFAC556BBA7CC528C18003A2AE059CC97C18263500667F6A9A8C421F2ABDD73EAD +82D9D08940DEE1688D4AA200ED80C3AFEF6A4979154D99478D4F4EB3738333E8581A755190D87BE3 +09A319ED170A107512F056E4C619D4BB1615BA6C0680117610A26462F267B76F9DBC9A7A3AC08C9A +512A44697082A2718B62FD1D565010AC96E1006F958070AB7567A33E1FF7BD450681DF6BD4EBD265 +CF12726A7EFDEFBB9BA1E596BC5C2940A4FC9DE60116C9082138F1C672977F4AA6B7986ADABBB2B0 +651A7B0C06C4BD405C58B8C81BE075997E118E10FC008AD1F9ACF9F6AAC6C9E3F6DC7FCB838720E8 +D0D9BB99F203EEA2566F18232711E832810F10DD4E2DE44A0A81603EB1162E1BDB60AA1E2D771EC2 +E6E2C3B39508E5CA03A1E2A7F2860BC6F0B57240880DF54C325F4729EEFA1D2753D57F055CDFCA5C +E9C6630980C7121FC21E2F7223E6111C18FFDA0A0A7643A213DE9525AE138814C4183BF03A26A36F +EE9199821464C845A746945413629DC53F5A2B0D8CE9282B580ED662F2F07398D6A8B7093CFCC6F5 +E0F5A7926D2862AD1CCACB84D85902038551EE3EAED02AC7754E3B65818C530A0252C049A68862DC +A459DDD937F7BA64DB16AC86885B68AF46422D7C4923070E20CBAAC9F14E43979C5D5AC4C9321016 +7CCC52E7DA272146D68D8F61DB4D60063E74F6673B41ACB601DEEB1DF73517A8C5388F00E8A9D236 +9C85DBFE4C1E9864AB52E09F465EE98C194151E66CB98E6981EFFCADBC48532E9795290CF745FDA9 +CB7FD40BB77C148A8B90C0CA50176103E2ECCAA57030F7C0882F1E891F9EEBA077AA4992FAE38C75 +5470A4C35454EBAB45271DD76E4DBB2D9147817F7D3FB800EA136D3F27C84B4D45ACEAD13D3C91EE +BD6836AC04F95E578D1B7B8CE5B718E42FD1BBE91EF9A7873F0C6DC59AD231D08CEB4AE312F83F1A +9310155D2C4F216A6FC72385C899B5390EBADE7CF7BEB028F73DD76EDEEF639E9EDE034ACB25BA57 +8C7BEC3581FEE0B221F7340570489596FC60EC805405E0D2ACF0687A62A75358B3878972D4C039D9 +07D868DD00C5F7F3342384C09B01F1926F7D37C2B862FC516361253CBBDAB354898B31B1FE58F773 +61517F2C2E106708AB659D95CE3E9E7777B7327DE01AE2D1E0C84B9EE3887C094C149700CB095D5D +A5FEAF1AA9027AF257576A7370D79FF4DB98511AA27C5C083FA0CA60A3A5E66B8DA9A22FE5DD3DDF +C529BEA71E83881D8B010D140AD686DBEC9AF4B331086F6556B695CAB14BF24B5FE64382E6F9BC21 +5E9EC40C0914B1E27BC25F43E6F555B551726A8D9CD590D5AD3732777EF3B00CBAA89F8E8E0E0347 +7D45B00181A7B196FD4919F110C0F2C0F1B41231051AB5BC8F8025ED00C10F2C2F51E97B622A4E84 +E6AADA59F896F83EFADE779B37FACC56BDCA1892A3BD965B65D51B55AC1D73ABCD16D0EADE55C0BD +3C1BE9FDB323E9FBC9835C63F757051E29E773D3491706DEEBAA0F1182F0E02D0CB701661B30770D +94E240E1358195781006E18CBFC2D83F229A89C3066E35CAE1384A0266D5A37824A089E855F11209 +9F723AF03BC1C8B1C0BCFFDEBE3EF73A40BF83F5E038B63267DE5413B88D30155E62EDCFA35C0047 +0178E5558CDA2B30C4EE2A9854C93E0E484D4363E3614E5BE829FAEAE51935386D20DBFC00B42952 +7F56FB045EC4D97B3D649415045337AF44BCF4AD9B9F9BF3EA72151DB616FF8F6B13EF66516D9640 +67460FF123C7EA525A97F1D04BDE9D3D495602620659F6E5DCF1AFC5360D1C371BDF9984C4A7B462 +180A3CAA7098E0FB0BDCE694806BA466883BD28D77DB4CFB6635BB7DB45B4D83AAD4260A4CA0D411 +0E251AE7476A95327BD6AC1AC88F85CCB705FBD09993B9E2990D51C37F1110F78B887C54E4EFDA80 +4ADAE5D81477913B6938FE1B39913C6582021A1ACA834500D9D75C9942CE2375D0A2A73805751EC0 +970D6FA62D4354337A43D85DEA6C6F3334F40221FC473DD79344D028FAC689645963B371A55CDA92 +F6BC989F4F1880AC11D8A056DF7A0EE60471800126526488B95B785698D2AC488CC027D31190ECE2 +54F80757DC9B4FF18171409C457F5FC58DD1D43E8D1EE08A6AA2F374B7C245B7A21287DC333BCB1E +EB498A2BD723EE6BB30B76E574773F70A61F1E163A25941531C049ADEDDB59AE75B7A6725D997752 +10ED356DD93D0F0AD7EE6495F07869C570125125BC84946F6AA1152CA18FCAD3B68004A7F7AFC6E0 +9EE6E1B365A4DA15DA95AB980A566DEC7394F385FE041B3B7F46D62424F4726B55DCB4BD3625CA68 +1221CE01DAE78E24F8045EF21F85C13D27D1F0F1B589F936F62F699B93EF05930C6F5730B0AFDB7A +7A829F2ECBF0DD196ED66C7E4E4E7E05B565BB98385F017D01B1305856DB747C3634BF05DAB08836 +029674ED3A4053CC3DC696F758F86970F94034B4F0DFEAA4DBDE449800DB040B55E7FC83E6B5584F +A00F7C70ED18524037CCB48A22A5A69506A825DED81D98FE93177DEEFD42B485013D4C4184CD690D +714530B56E355FB01BC92DD90A7AE8095748A872F79121A28E965E6149F37C9D5F4DF64574F605E1 +B929174AE0CF533F77EBA0E10862BBAC46BEBF583B951BD0BFC5128B08CD998DE897033E7CA8B198 +625D865701394543845CDB7A45BF82DD8B6319658E28B34FD319E38E650869742BD2672623ED9810 +8DF14CE5C911AE0AF2035B4F9CC0D1D84176CF3AEBC4834E8BBF07B7720521C4E6C081A962FE29E0 +700C4F4ECFE92C39BEDD64C3DDF52959A4102CC242C35F56139643F22613D675F970CFDF8C6A57BE +9D168C9CDF93915F66A5CB37DDB9B8B729F8131391F9D6EADC8BDD5C13D22A0EF967622F3F7C08DC +C422E3D41B3BDA6B4520C98FD08262303E042DF37B9911C1447F3DC8A854973323E1C380A02DACDF +8A53A0D1EDE9BF34A50E8B76C5AD5B15F8E25B2783BCF4B1247309E9B70CC951CF3D98C1A25E9CB7 +11235352F3BA91FABA828F2D7D91F0FFC50852860C531C20A5FAAFBCE1197CA367F0F84DEB86A8FF +A9FF4C252EB2633AA2BDAB30F2094721F482CF926DA3299452177B79251B311AA60D4CC82F5A9F50 +E942703877AF1C10CD92DCFD16CF19BC7314FDA5A85284BDE964DE2BEE782F4D52D17FD2084E0A95 +59EBD5AADCC74A6DE64C1F799385F5EC2E2F5F869F78F7289A26B03A9FD906934C3A6BA4A7B36E7C +3B77A7581BE9CD62B49C34572A870053CBA85DCDB9FDDE407B29CB3D02AD1C752B54DBB995DF0F8F +CB117CF8500B97849275A4625EF9937AFD7C8751B9B021E778F7DE9A9B191BFC470823FB8EA919BA +DB8B68755DD83C6905B118FA18FAAE216E2494FDEE9C1125C3941092C592DEC7A5B0C763E7E0D3CF +DA02AF9FFCD234A01D75C661603820C37E9A681356A6DB424F5F991FACCFF78EAE3518C0747C35E0 +8EDEA2E108CBBFFA0B2D3BFD612B5743AC95CC4A0580A6739BE4EDE6CB1E8B2F4CB5C6FA4572329A +06080E0085748067462F3EAEBCAD734DDA18BF206EAEFE9F882807694B80A63AF2F40625E98DF55F +BE08AEEEC2C1BFBC16F1BB4D391B17122EFB5FB539005A613EF5C9F154BD50F05862F813F2083CEA +149FEDC651191259BA4FAA658A42AF151B03A7B553AA79726A44AF9175A724E0D65CE84F41F3B7B0 +E0B684656EA56B4E7E654946AEFABDABCC4F3876B3C3A67373F4133FA8498DCFEBDC27476FBB28C4 +003FBFB063267FEAB2B2BB8DC647345803B2723DBA8905AB0741137C43768B434CE118954AE55DD6 +61AAA1BB60B87ADE97D4197211A0C70CDD7855783B7C570FD742AE5A5940A3D90F9EFF687C4F1E4A +D3C4F5C3B9FF20B61C0076B9FF060EB99860F31C41B5AEC0D2C4DE626C6059678DFA35BAC5F2F171 +A3BD00065AB2007EABA499E4E7F30EB994F8BA757FF9BB0C9A2E3E384BC1DD4B62036E2D70819AD0 +A535816761800CFEA50681AFBF0057E69A0CDBB4BAAFB8B15F7727BE1169BDD4FAF692F9CEC12893 +E4E4425DE4CB20F330951EB5A7FBB0FC10DE78A7F9B0EF6FA629CA13E8C2F616A1BD42B1E26E2A0B +533DEA701AB7BA93526E2439755FB9BD5CB076C3309E73807A5935AF6CDBBDABD5DD7B036324130B +8BC40163FA0999364553CFBE351509F8A5167F205E228ECD66EC9375D0E0713563AE75B6C23423AE +63EB67167F2F4512BEFFE96B90913E6920F1F8B7139F1CAC9E38482B6CD26576776A01103FDEB164 +A176023370D08906E2EF27E1E8B6F6C27EC67A86EA36A6C82B681385F3A60AD53A8512E0880D7ACB +5567F2654731CCC1796C47E18DD6CCE30F65E27DDC5A3E56BFA0460DFC3F9FF1E98B7BDA9DDCC615 +718D7C6CD8DC1270E70FDD4973B287396E2B85ADFCC83C71DBEBB97346E858CFDA78713C0EDEFEF6 +B84984D719C4729C0A3F2A7422DFFBB2AA5FE61891D3D16CDC1BA4A84E7A74B0076FEBE0C2C74F4B +B9314E5246D7E67DE23466D47C8AA93AC4859B347D8CE31FCFB4D41137B51C41BF19D626A59D0999 +FF2A4FA5FE6FA60E029494EF99C8B00700E6E752F4F9ED52F2AF5845563ED8AA5D4E19F82DC0F87E +255ADA53AC62E3D7BC788EAA135431DFF49F2D3ACB61798353B27409D149FD635690F8AD324804DE +A99D12B02F15D9C6DAA01BE2C1512BB8DBE86EB31D7034866C10558C008D69DAD8830745F2BEFC2F +FCD957D0FEC30BFEC54F3C440F3A99BFDD7C6D0D657402A064F2656694E5F5A5524CF4A7A2AD4625 +5DE9D2E9916DB9DC2C39986A221C31F89A1884ADBF7DD62D4EBD47957E7A359F2ACFD38E073E8502 +5F907941ED233EE3582AA955CEF67A8ECE6D8B301EF37B7D40ED84FA9DD604C74C8E870F9C26A2D4 +DEC8F03563D29E1DFB974CA191D4696D877A468082951B02A88884B9B760961D9C37154F32D54512 +4F0E4357B68547CAE9CDB571089752D7881613E7FD8DAA8CFB98CA9E930B48B78AE13523E43A3568 +7B42DD2F0A99034ECA1DD782DA692EFF6AC99D6734DF1AED3616B198E6C242EA7A9954B7337ABA3D +13EBF06B95E16F19047AB0EDBAB6A8928D81003E593C1F134B0E2B8C250EA251B59CD04905F57016 +1662514225C393C42BCC172DD23D8871908522CFA5CE113EC05F39E4583EBDEB5DA425E4627A4A2B +D5C511F9C9C155BC81D0EFAFB0D0F2E96BD49A5C942933336EDF9AE0CDCBB159761DFC50F6180FB5 +024D2E5C2A20233AFF282FD3B1AAE9B65D2989BB8176AA8C7A1F58E43A9AF3A6D0168CADB6930706 +C4F974282D4A23F71B0A41C75086DC1C45CB98ED68ED0E4FC62807EDEF13C6C85741B11FA957D791 +D92B750F3B7BDFCA7E148149E55EDED66700483C4D5BFC3973580F7199FD99CE6B358B508FFF5DF1 +78A5E495977D851B0B06DC7F6B38388D5C94BC8934584D8EE2F4E0CCD3332A737BC066F042B14931 +57BE93622E346FC6B293B8DA0D3EED02508AD2183454FD4D5D21235268834B257EA8B06117F67589 +3E0505E64709FDE03F2D5C82B163C29629EEBF5D408547AC363758D8D134AD7B9A55AD9C7D90B67E +6DF3AAE6867759D2A75993265118BF6C5A261C6D80EF039A0163BCF8E6B40E643D1BF87BD2592BFD +6A41EFDF9CFC2E6DDCA83FEC28B5EEEA2E363C20AFB4A13B5EEB2CA3BAEB2CA6F4824AF1A59E4CBD +884CA4C62852A095E4B13CD018669AF3898DFC4840C874B97408C811E431163887E5BB8CEAC12FA4 +A1EC50E8305137437441AE0FDF8FA4DFFFC77C05FCCC41696E40889A02DC4B1267D5B892647D6AFB +BA549F737543652D3C3A051E36BDB692DD7EA0D8964EEC27BCAE8EF5FA853C54D67A8ABEF7B01FB5 +AA473DF48CFBD3D697C9B55C852773A35B9A7C97908DB701AB66DCFB274A824B60E275E1CB341147 +36B26E50EFB1DF01F01688E09E096533E95B3AF5D09D7823DED38487C58B4F10D6AC76EB48731CED +78AB098C452AC83CCEDFE4E8E4AEB4A93A715306A096F262BFDE5036F38A3B733B1A0134904A3EE0 +8A9F7C2723FB5D0535C5B57CB80C29E292A49AF936DAC66CDE5C01640490109E679FBDC13F892438 +D70CAFB12909FD2ABFEAB23ABF6D129F5628B36FA00548ACCC39C8312030DBB87364DA44FACF3818 +D4C8ACFE3302B1487D5CFED16E17B05CE9889219C13C9DEA28C9BAE5D788578C309CB7781244E30B +7DFFFAF5A9F594B8781F849EB20B1F3A346C2D697CFFEA6AB4134DD46C86BD0F89AB3EE9DBB2F87E +988D906C21A43E5ADE58BFE82D4D4009A39EA3D1E779FC374FF21B86BE48AA4A33186DFA0F97BBB3 +218CE963643EF2A35788D613DFF934139B3EEA36377E67A713D20BD3DF04720AB49834E3FCD78908 +1FB726CF642A5B87D5D23609661F7D694EA0256F3EA0DBAB7C2CF79CF89CA0FC9D25281EE0FC16B0 +D089DC5B156462343A375F1EA2682A53F70A1F239D58080F4F884DBFFA68CC6D63365A8CC308DC5C +BC2387BF3D3107C95FF4DDC3A902B31C3F414355748B10518EBE00C92682CFA413FD071A16B8D129 +4021B0ECC5025E33F6116C89C7B983C6BFC31C5C8D7FB5E5E81D3AC500123CC05B3C8DE01357E192 +0DCFD172EB4B488CEB9E1ED5FA1D235C96FAD22B319239FDBA08ECA2C5C1192B4D7A797ECE135228 +6BBF1E59AB3B54B8886E67A82AD971DFD1EB21CC5E3512CA922F9B870A48E6DC94F94181E422D274 +2D3A14FCB3939FC8C1D62CAF79033D6EF4DCC93751BDABE588BF5D97B52AFC5084C5BC17246FF977 +7AA4D738BB9B15E534ABFD68848B879A9840EEF4774734F0BACED5E7B6177DFD430E0497E36D1077 +7654F351348BCEAAA18C3B362B2791A006782C25C9D544CF1594EA623BB4C782C6AABCA95F9CBB1A +8C86318834E1430376406D2B6CD5AB09644361B83AFF66C96CF549C2D309F7439254C6C3A5B210E8 +23F83647FF420BE66901C317349C1B305014EE7E9F90DDA917E3F853F1A8AF3DF1528A81C50B76A0 +F02E933229C2743BFA639003025697612BFD8575DCEA0BA5FFF805EEB4D9FBFA8D2014BC239E9D5C +4C87E36D1C83E010B92F43C06733976BC84AAF1C05C0A0CF45CA7746ED7E1DF5A12F2401C0FFBEAB +EFA199A7299E4BE5089C2CD83E7838F163F6284FC299B213513F803E93ABD8D759595DBF513D68BF +96031B9FB95A945B7C9153B0B315436C850FA5F1415AA2C9565F6FA39E9F5C5FB265CEEEA8C98E4D +00A72CE7F9F6677DCA7E58C1A8C111A9C6C44781867AA5FC71F36486AEE73FB81C03BF4EC728E43D +75564244AACA3D66B6D36DFD38332AD05F150D4972FD475FD087E13C9312D5A17A83411B45740153 +81CB568CF85BD66428FF9EA2C07E7BF8D0AF4469AA367DAC0230650036240634AB81766E832EBA8A +2D8BFA402DA804D264757E74B3465EE21A1EA1C92929444DAB2EC83050AD169F257B77D3F4B9BA61 +B11361F5DA6DD2DD933E101B64F9DC82945A2D421807F09F3E587D4B13BE0FDD6D7133CE890C3AC7 +1D0880418880362E27635986795E2E8426A0A7D7E8E5C41317209D957B53B6CB9E4EE7C3EEAF3315 +E006B7FD90E7A58FAE5289AE513D7751201459BE029563B58D967AD24E90DE5E96357D37E86CAEB1 +6059CD8593F92617AF636C7D32E2B074A40B6A1C40828313C8DF1BCBB002DF276D424519EEE2F234 +FF9B9B27126996834BEFB6E05A7BFE958B4AFC810E8B77F0EDDCD43E81549154F81E282276A7133C +23650ACB159EFEA53ABABAE1C1CCEF5642898A5605A285205DB40DF6C768029D8CAF85C520AA66CE +5BF1E0A0520CC94917FCC3118953403A1B16312096DBD56883F03E78A14315A5F97345E13C363C06 +3A01D49492349D60A9114A77BB9FD48FC0C3AC76D190204DC69C3ED4A265B8148F2C5F2E147A504E +4F373637C065FB894446031F78C4BDEA68088681E0C5099CAB1D13833FA87AB96E511013A9B3D806 +E71EF6E0A1442C91FC2A1795A13145ACBBF5D18880695EF11832ACD328BDE6E0A7308B12759D12C8 +6B558CDA038590787704BC1BF49EE8C788C41594332624D56082ED8627EC110233CA328D2A0BEDAE +3511719EDAA726F338D324D1577593948A8B9F0300F27FD4420638C6972EEE2D6248B87643275DB7 +69F72E8F86803184035F6A539A7CDF43A79886ECF110ECD7053FC04EB5E51B3C7625B3BF95C0F5EC +044FB7226281BC723988AC2498ACC0489DF0BFD1DA82D04FE3ACF6B63EB269BA9489F8D5D07DA9A3 +AD04BA924B99B9C1EA64AFE7BB2886513EA6730462D4FB5DD82659C7DB7687F4CD8E006581A15EA7 +715E274C9B89F66F1ACE9C2AE7698FCF7479A04F2208DEBA6DE801A6D184A8A9AF6BC1B0E37CEE3B +323DC4EF93EAA8219F946DB9F4D9C133C6CE0FCC6884F9C2F3A816C4ABEF44DD6256E7BC4574600E +1D825080660BF6858B415648258399839118F3C11410C1C29B3C208A3B54AD5D7484708DCFBCA04C +849F2AAB79E4D96328D990C63DE05DC8E804DDAE255F94FC3D56270BAFF6F86190796F91BFA018C4 +FE4FAEC3F1ABE8ADE43D0DA18E710BC1F419F77DEBAACD3BE011BB93E111B18CCEDA8EB0352934F9 +690F3E73D71655191F150BC3788677D1FE46070FDB354BCCAC8C179009553A7D67C87518131A4D8B +4FFF85FB9485C9F30F4CD31EECBC4A44CC267F6C57AA05A11C6FCC09B5CCC83F189F6A32F8EA56F2 +2D20DA4D4008F08EFB1487675CCAE22BC9494441682F4E46839F0F4D2D16AD58AD0886C60C925DB9 +C7D9AA1A7FF41C94B6289E1B72382789006F40B99B78B05ED1FB1F715CE4C0A1078AAC02EFBE6306 +F53F5F7E73DAD249995DAEBF17E5F55082CC6885A54F93F1A935E0389FE54E8B1B6C5ED19D483620 +A697873D5F18CE9A48E3C2C1C871FD4739A78782D8112602DDF8D4FC497C459067A6B118AA998740 +6C8DE97C2F09CB9D388D341EAEC0A5BB4BBBED92BE59B273C77F3D6965418669BFCE0C43D5C86275 +D8E658BE1893DA8E698DC858CA459711969B2CBF4CE294071EA572496575CC35CAF57ED49C2FF1AC +CC21E19D189B7C2A1ABB1AEAD7185675413C224CC4C0E1AF4EB76BA9F44148A95D8609838B967784 +2391DECB30BB0FEB92CC890F224FD2E9E9D34466D6091443CB69E845D4419F9A04664706FC8D2D15 +9002422367F39CA1B1CB1A6C32A65F46230CA2376C3E5125CDFD367514E087E59873EEE569B7F376 +227DB126060F063662F118C7BD01946BD04172147B601BABA952A01E0AD31AB1147D48FC2C3F52BB +75D9618E6F03F1F1EA393AF0E8474025F451616C4ED236ED831E14F40BEF5B86806B73ED64AFE7B1 +2A3C1F5036BB9AE79862EDAF13BDFFF06C94939AFDA1A749364BEE73449520111CF56181527F9568 +F3189652FA7FFDF4BA1086DC5992C6E0282B6F88D7CB73F485F8A27A77453C151B0BA40E294653A9 +73298EBECE8132B440ED4B4437283356B79CADA8198512A45044A7AF04CD02CD4DF7F47D5D1E7FE9 +52C346B01D03D1F69904D1AB5E8C433B0615E88CF4D01B3C96361F82B5CF7CB4D92FF3971E44F0C3 +317D3C5B0BC8DF4BCD4DC63474D0E0B5BEFF3177E2722D4AD4AC4B4AB6269EE948BBAB6019ECF2F5 +846A3D215F6C0D999D489215D4328875DE21F2CA243CF184280B229ECA4B8A9C5290973503AE5883 +03C67DDECC577F12B41F0D4DA266772867F9D93E1863BF76C6AEA5DE3FC6567EECE93D96F717E39E +DF536ABDFEC14DF6748DC90A2CDF6066246DF69D2745D2944123AB3A6ADCFBE7C74EA8E8D712AE86 +F76B3059178E78FB2FBE8D1F25831C70F58FB6B5922B371A27501E7463E01C844A2226CBA263B570 +4E5C4D3E50FE31435437E1ED39E6E3BF47B4E2C4588274F044B3294E7B2BFE302E76EFA3CB74108D +A6CFBBBF383F5C456128ADB5E667E1F7ECB4C3E00AB8937769E5A2830520E9FC0A1DA1662F881ECC +DD7967647B5841D8FDE1DF7C9F5475523F236005EEC0DF307BFCEF379355C30A83EDC96D6086E224 +388DC7B5E951B819347AC5A1F9FBE7EF1907726EE7E972AA1DDECBF7F72658C20FDE99FEE686D7E5 +01A7759DEF55169938F34978A6BD4DB49E494883F67E868A9AB177FA8E6F81157A95A03B4D9DB572 +EC1CFF33B450BC13E00830BBA20AFF928CCE04B4F79F3795DB54A4A8B5A2F3CB323194990050CBBB +C7CD32103E0911160FF4DFF135A77DD0CC15867B994CC88E1EC10E3A097D329DBB90FBB62981FF61 +C2521F9AB4B9393C6764E5B4361D0FBB1938456CE437142F0AAD9341588BD15EA0F6EDDACF12A62E +C025F3294AB1AC45719C5EFEE94067390D579AED4D1D36041D358CF1A24446176DCC808CE2D6CE02 +7A2C2F6E517A5ADAC722EE94A1710BF61254DB4693B30225C12B9C4F856E1D24075327017D6B288B +B52EDE713B3710778A565EFF6C89656BE3C5F590F6ECA600390C1BFDE9B0EEBC2E4FBB9E0E2F405B +39738F7969F64E8228494B298C3FFF4C7DED00B0EEC336B7EFEAA892C4A80CE9ABAAB4318FB34348 +93AAE6A90FF00B892D1DEE254DACDB268A6308E91FB628A98989956958C9634896B878CF93F4E0AB +A0170C1B7BE2A0C4A0D514D7BBBC4CA114349D4D4985E96DB7E2ABB752C7828A9E36D9B0B4551DCD +D878C06C3C68D2C214EC8121F6675D8D03545606B582B09B76B6D8608DFCED5C4A721F7008FC2014 +1D877353E8BA5DEB1CB61F7C956D4A9F8CFFC8C4FF81B2660AE4BE45F7A63141BAEDEA05C43CAC2F +A04163ACECAEF90F61E0473E5CBF1F1994076B6A72CB5C33B17DD57E2632F7C6DEF7837F8A939055 +CF357795865D86C7745DE54C6079C791850B20C0C7349472FB6018521DD5924ED1505A1B8C8F9CF4 +C892CB40795C4ADBA3CC11C8A52A1DCFA8FD334D7F3C344CB4057E80A4B66AF6A97799F8DE817CDD +0202870BBF42E76C9BABD2D9B66D10F1A68388AF1A511887FFC50EC7D07581D4C4FC3F6C4F7EFCA9 +4799D74132B5EA25DF0C9557902C7EF1E04E612D9E40DBA459513E584C3A3EE5614ACEBA165E07A7 +CA3394969C2CF1FFF28B1C7DA85451270DF0FB71DE22A03FC2F17531FD59B12B55DEA7F5F56B0DF9 +34C96E26124342571BD04FAE6A12C6EB0E21F06275605FABE91C6EDCF55B298BC4CC52891DD90360 +AA5FDC150004BAE65225FFC42D13026F9C6FE343D7CE2F52229B4846F6E23BB2BBDB6DCF60F07A74 +8F19F74A1168DC5C67BEF840A3C68CA7A4D8CE7F94610F4CEE989459D0CDF1B194C63A2B82479746 +03A89150C4C6AC67AE3A1341F9516887C6BA254F81C5B552C527765A52ED5C4FC45D575F606E465F +7C2EDD2F5927319BE737D48099C333BBA84486F5F8CC0B32052DB6E57DC55A68019788DEABF8D649 +A531C1880D07E425D55D4DD4F3966B2FBD2A0B55E5C429051DCF0E3B9CB1DE6A5B3DA05DDBBDD3CC +1C81877AED2BD272FC7A10707C07DAA54FD6CD37E15C247E3100A1A0C527727B73D45C8E02798C44 +1864A2D1D30FAF94121F0CADA24753221C85DD5B43E5F00FCAF73A1E531D9453307581C6FA28BB5C +54D93F149D3871D2E017E6E7FFF7B0BEC71B83AD12AF353CD13311D3A6F16C51938986C9B6A24EAA +06B8BE6DF27A5090B3B120D1D1E064C6C1745536C6B0AE5C899C3DCA91BC38B7E900D9614F291B9D +BAA85AFBBBB57D58C3E1FA8713CCA1BF4EED469773EB4B9605125C08A8F7E998E37BD893F3533232 +ECDF47D26E8B2A0437220FAA760DC8E90FCBEF59AF6C1C55FE1A28A4C98E2A67DD5A7E55BD4FE272 +15533A56561F0D80989BFE15B321976CDEF26FD6530EF7A368A7239CC55D7AE2B8F0E980DF63CFE6 +F562F3ADD0AB906B60682BA447CE4A86E6D5538E13C6847791D8A16F5BA29E5840847A7E33AFF57E +BBEC1A5B329A461FD0E858DE5163A2120BA12839C3A216C44F364452A2D6401EB549791012C4B65F +4FEECA2B73B2D88CA49B44493802B01A23321470A2593A8F8ADE3F88D87247851561372137E11D10 +11A733C671C71D33EC939B05060C73697EE577A8F2BAE08309585887E5F314BCC642BA2715B51E0B +4D093F6B11CD37BD9728EE90A0C92D15BD1105637052F89B417F6F36340588601C9C2BE9526D01B2 +E88EEAFA300E38B0EB5E2B54341533B31DA1193588974DC054FFBDF374960D28F0C8C1AB8505CF5C +64988DB86E17213EF0D9D6D52ADB1BCEBCD02B5E16F0866D21D7C0FE108D551E695CEAFBB83AADC7 +362727C47BF24C482EAD6F122F1F35923DE5D6A248BB36433D044F73C944E6CE4D9FBDF0417ED53C +8F56B6D389519E7A539D6BE9444A4747957FEABCFFDCA5FA54BFF46F637B3A3299988929CDE008E9 +E3C4CCA97054A822C4AAD01ECF9861AF6238E6643358B0EA141B0E161C6ABCB45F38740B344D4112 +9D4E898DAEA8F2065263D68D97966FA24BF88E61BD86CF2C7BFD1F058FCF04AEB88F3A30C9D446C6 +3611112CCF30F163C103D6C7C5AF946ADD33B50A58FCD8EC612268F7E119BDF387104F22E4C2CAE2 +DAC407F206186F12D93BA87711FB05E6E96F17DE333305196FA7C33DC06255828744B590FA0E67CC +E4B0375276FC7AB9B324978B15FC228DAC17F955C7B3D441C540192145FBD002EE20FBDF6F397F95 +336C0A056609E28430E123A432EE91594E20A8D9E5774FE8768C84CC040A706D8D3590BBC9AFE373 +4A5536162846B6B7BB7B248924F1DAFD768B9546BD2A2CFA1203D6A3FF45C8EBDE2355F01744484C +DA9FC337CF7D9D54106407F54FEEA81B77BFEBEE088F344CA2E537644B615D8D6B6A79D03F6CBD9B +573FBA87EB00410B5251A29007CFB60E711F642C19847F58333B48F66B6758E4ABE524A4671B0635 +C491341B3CDEE650CB9F774A5B6FB92AA70FEC7D9A057084214D81EA5A36A7F8EBE9922A70F2B102 +121736E0BFB178A08ADC0A58E2A9DB347FB9B0BE707C038CC19A5519C3FA9AAEF660653086191E59 +320D0E696218CE1E8EA7DB5FB3318EEC98C130F3F1C45D0D2401223421FF99BD6E1546873C6F12C6 +F45D2E1C3EE41634F5A415FC8338A18EF2299EFCECEE00F9025E79610168548BF2A52623A479EEAE +CC55BE5172B0CF07F9B04E2B2AC08A2BC839DFBA9680F2BB2E438519BF3B434C71B4AD9E64262C76 +9F6C1AD174FCFF3F41B5BD7652AB296C543F323F0A16E88C6D1FD3025C33794551DCAED303B57A87 +CD1E7FA46E16ED357CE0FEBAAD2678A4D84E4D6B1635F412465C0F3E7246407BDDF934F0E2E0F5D3 +EFE318D7D63A7B6BD0CD5556B6DA811D521408EF336CCE2D2B6777AA472A9EE0DB9FE0D6914059FE +25EFF5D7E3D2A6EB96B23669118B9EFFEE5B7FB8F1EEEA355998BB48430156F14766FD77605BBF80 +CDEF19879E8F8C07B6998AF145B0AED86FA94A952F2F49D2891D41AB0184EFA8616B139E640A3A9B +D808BDF79E283B0CC4686935D0D96630D590A5F4A7C71C05E110BF3CDB5150D8CAC418AB25419BCF +DD5EF0A2015305561CA26494E267BA89892AD21E0BFF44D48E330694A1CE12183B9A7E4E25D78EA3 +498EDF9A22DD7718ABD06DE2C28D7762F609A1E9A5C40A878FB8AB33A60383152A119B9FA077B109 +90AF19C261CE43AFC116593C30BC27EE4D8CBCA0C0298BB84327FDBDD93F073B1E06F71933C0323A +6E7FD2AABA783E9BC995C4AED621434B82B34424B768B4427EE65228E612581B0B8A7AAC3149788D +FDD106A6AB93E01771802AD93B63EC386B057690E5C34A409421E532C6614EC61A0197C13EAEE438 +35ED9BB38CF811E39CF8F154684E3D8B12E3B673152B82DD9B15A2A68A6163D2CCD72D3117F7C24C +F1575671832ECC4AC6B912882C0231050D60ACDC7D6F36C6BB4E32B6019B32D1DA08C7ACFC1DF451 +3A338FACC16D297C56C6138FB02FC7FB5BF7B9DE4C61B63C6B37B0F9CB42E6FF86693BAE1CEBB60C +9C15CB6222F547E0D0776BC5545A73A2ED3C7799F0CA3C2D5EE17849DFB15C6ECF7C2846AD10870A +00E0810F35A57770EEE9D49D05B54500DF164A02FFC324CCFDB5F828AF7307DDFBBE647E98909C73 +A3CC6BFB360042823C678EE6ACA0D658C12776F2A573656073E4F40505F5CB4A3D340B034B0FBC29 +C3B6B055D23F0BD47E44B441D430B8703883AF8EBA79081D528AF5646340A27291472B8E1F19C8BB +B4AD17C7EA1FCFCC7B52E6EBAE0BC8204AA52C08B3A63B2F07FBFF20092139143A24130191C41D2D +69077D71FC204C5FD41275DCEBEF7E5701BFF6C0A4217F6F60C2E37697C7F1C35D2451B040BA0D28 +0C9D23AAEF592BFFB436165C314C3CB75223D15694B6EF312CBCE8035A1A9172365FCCD119CD5DCC +569B84C6BBC5AC9CAB6942096034523671156FAE2012F6A24A001DCB2F35A8A031A2366CA98F1E52 +944B58FBF1852710CEE0116BD2C7D68DB956B15FB6AAA147155E9F179E67357F231F8252D728AF12 +49DAFCF6AB4EBC8637E1BA10D27555D2FAAC9EEE5E51C8BBC793ECC6011C4FEDDF7116E719147927 +0BC11D5EDD9215A4E8087A6A16A591BF7ADFDB69C4A03361C0DD078017DC5851BBF60E06A86C6C0B +E08087C99F4F9002ED5206534913353AE16C4F358BC1564A442CAE506A107D1FBC0B9ED99AFC6633 +209BEDBBB681CCE475645E92155285C00FD6985216CCE60064946F94778F7AA85ED87F5762C20FE9 +DC007954281BD6FCA8554D2A0CA5B76A3ED42EE5F44F3B276E574F64B20E1AD489753903ECD20B4B +EC88ABD1E1CF5D06AA1815AB771E350EB6D04078EC04616B977CDA8CE88C483DDEE9F28D58366D3C +224C41D19E550B5ECB9775F569C2D391F61C4667A9BD11C69A88473AA7884B823F762195CC403823 +690A32087893C29C63100A5935842F6B612C95EC9B5F07459608786310C8AE65DBBDCFED21B43191 +874E20D08F12E1384DCE1A990CA5F07DE36BD012DC9EC558FE7CC44494F48CA3BCBF1F1F11BB98BA +EBBF8691F8590F84AD849923E656860EFC0EC27496FE6D6C185791E3261027DEEE4C57032547F94E +7D593F7885526AAF054BB850C02E863D831ECFEE61A781B89867139889A362F95F48A837ADB955C9 +939F609D2EBBD56775151D2EA4B09D38FC1D824A952EE7E52849260DE61F07333076CF887A3AC2F4 +CD088C29E47715C5242E2366CB493B2FEB38C19FC159EC50EAE88409CF0A30C0A6823C45D0532C2A +72E45A17916F6CEEB475C4D4A19372AE271326697CCCAFBB43D92C25A052797186FE8314FB41FD94 +2A4B24548866BAC19A83BCD2E6979FB3C7B70B075DCDAB3EF6B6181A00A98AE73B6D968EE2DDEF07 +503A92E4FE1E0B67A90A2F351D600DF960101A15808AA99B3C680A8F50D5CBBC96D98A3A2AD5F14C +43857F4CB9DF9372FE74B5D6CA9CD801667399E778B56EF702A56F0F393137B20BF11BCFF9DFC0F0 +4E67649D07A2A5F4C42C9A929231D5BACFB6B53210E9FE311642D8BAA7358E6A7370B6CE921765B3 +68A354B42C8877E59227146409DA83E407657BEB475329F228CDFDD11BC73123234649AEF0E2E9DA +76C12AF91713828368FB778905A6B7150258695D4D9DF6111E1B28A9462002D7C476FE44A9B13F32 +9FF84930D213787932A1BA01EA608ABE7054FFDAAE2176EB960005E5407D7C1D39AEF8EAD8683A50 +D93398C584312EE4A12C07E9D55AE9981D7EF57D66499CA93EA653EDDA1BECB494662E54CD7ED8A3 +0B2A8522CAB12B2751F7E9B3B66CA0B5C8905E0F3A51F68C96E9C02F10FE515FD6133D3ED298D15E +8B1649F3D13341BDADDA7FCB838720E8D0D9DD92D0A241A0CD8E25D7B313DDAA2F25B543BB0E7965 +402ED0F24AD146E49919ABD9604ECFEA5A7A49E58664DCEFDC893F5D722CF24A44D26369F5D86569 +9632141348086A80AE528EDAFFF9D9986D5665DBCD375DF221D5EF1757D79361E5A5BCBE333B6C9C +1CF46794A7C7C776477023BB298C970F6DFF9AE6C7ADDA8CEBE73B07117BFED6CED2A36548081C79 +BDA9A70C8FC5FA16D0EFD3E9F869DD5DBDF6E70CDA4217935FEFE577B64DA1DDBADB9A092B9D1E3C +E1FD3B6E0117DDED155313A5DB7D8742C3409FC18B9743F09ADEC49BFF2FDBBEF9D5FA7110266287 +7C65E3A1421BA56E258A49D76C436C97AE2116F772CA5EEA5726BF17AD5D5CE37DC5F45235730E90 +A1A1E3087132C820EB9E0E311500F2CA193C72E2ACCB4D77ADFA34E268B014FFAB5DD1BE7187251A +A69B7BB3A517796BACEFB9ACE0114FAAEFB0D9BCA028A52F5037291232A04C2353B9663BFCBEC2D9 +30CB007954B8C6396003214262AA9CA9FA876B4191313FD1831D664863C4A19946AEB8F4E21C468B +9C94B8CAA407A74AF418DFB60D46BEE5029C884950D3BE8A58023DA864E9AB34005337D335B0B35A +91DDEB53A54C3233150E27225B0800C841E489E9EA6C12F5D95112BB723C3E88AAEDA3A942D06242 +2CD80B04DDF024ED79224E166086C5770EB10C389D0BF0327B2F753358D9BD552C04EA52BA154EEE +5A84C51FBBFC79055AD0F1243E489F82FD3D1A3D2CCB24A02DB3F306767564A2451DA405BAFFED98 +20241A82C6F4F6E1D2A36FD954540EF3C091247120F5E1B362D1EBCBF80951A3158B1547E7A0CCFE +40C03F992BAD00496C32BBE9B0A06EE9554EA3FAA247A7399A37D05329CADB006F679B58F2E5969B +13A5516A9F6949505A64B7B3FE5E748B9B9662C05E6C5CB79D64FD72C54F557ADE72880B3F9D8718 +58D5EA3FEF2BD3FCFD670794AEA6144D13782BA89648C98E7CF5160089DB9798E49BE2DEBC2669C1 +0C42AE1AD7C1FF4921EF465300977E057C0AEE61579460F56E51B6EAFC1F7F41D669ADE9610EE777 +055F927444F971121CD76A55A273CEBB481B89D6B78D0066ECE31BE3EEF65CE9FD22D22EFD6E3ED4 +536E6439782AD53550CEF1A903125F228A3E2F1AA3342B0551C59924149358F8E941969125F6E54C +8B2C068F57FE0DF91912CD71BE9492768191F4F6B70BA45C72409D4AC6E9619E8A34398E6A66E984 +55F1793D8A2C0DF30EABE2B19679318AE09014262DFBB9AED5D637653FBA1C10821908ED6E088910 +AA033E2E0798A630966D29F84845A9C937C27A268CAB8BA1AE9B32E12B52EE4B64BE5388AD32035C +7F98904F052B31D7C4819E27BCBC597E8503A5614E0DC2BBE5C51922980E3F492B61BEEB169C3EB6 +AB9F1DA82EA6BCFBCC16199923C8399B0495EE9A9E1749DAD9FB289FF3FCCF34F55DF7D94F91EF31 +B3B4C8074C567C4FAB337A337BCC2F459075244F665B079C159AAB83781E465F5259D41313183EC5 +F5F53BFB4797942F93EAD6CBF9255F9B4DD0748A3BC6BF36ACBE0127AF68EA6566B65430D0595FD2 +A7A2FF74055BF2E70BFFF2B79131F1871CEDEFB495FF914B88770654F9E5556AF535E1B9AF812004 +99288AA39D1388ACCB5B11B13596B550F2746B2A83B076F4F606F7580156E54FBF6976AA4CFC9581 +0A7FAC41A1635FDCFD6F7CD6BF738190D7F9FEAAB8C0B7CE38DD1459E2465C3B75625C8BAFE3B60A +F66ED183965AB9681A8174C44311D8EE36E468A8FFE2035B7C5AB6A372FE37FF627F4697C0D19F7C +8A1E356F829BBF2B8F4A95A49F85CA16ADDCA5C0817D6A4F7C4EAEE908DA13E0C89C9AEFF6D1D7B5 +BCD1BBF672B46C4960720CD1C74E70C78784CBFA8930576AD4067D406A0FCE9248DCE6D610D7EF0E +97F3F9CEA1D08DDA90DFF0C484DD32265FD13A2571513F361DE3061F2EA76886112AC7589B290220 +E34610277CF81535CD628CA688CA812275D7B9909757DD519F1FC89FE4D0AFE5FDB999323A470C6A +A0D9BA9CC92BAC24514E7CB3A3EAD2A70271EA8B02B2DDFEC6CE803F1B761D9BB7099FFCBF918D8E +B602946FB0DC14F0FD1289037B15A4A96A4605BBE53BCEA9112BF9746F3BDDC06CCCD808C62F2B41 +D4F508EEFB03AD22E2E154888DC63C6BEC6A21D1851A5C82397FA49BD163BA8A72527827E4B6F50F +585325219FAA9B3A2CE14A0134C19DAE50373A0F9E6DDC8205467242D11A3989D17730C8169964B9 +CC9549BE20A84FD9137DD9C9F7DEBAC3A41345DFEB0AEDCD7ED408A909000FE8D9CAC85D93052256 +7A2C9312769DC85F902D4A5F5766EA3C561549F1F2B4C5269276946809ECA26B6F51DB4CBDA9E668 +BE1023D8962C0DBCACD5BBC9E5F61C459B825036E1322737C0F196C0DF93DA76011DF2CA06F7B383 +8C672A802EA24A474ACF7A51F2DE867ECAF5674B1053CD5419F5FAC20584A3F7565D7CE584CD395B +1968B622FE3C68DAD2F0E33274DFDF03B5A8EA047B077DA1316DF487C91ECEA84E9B417EA25EC9CF +1F1CEDD7A1C2CB0D51A58BD8F9772FAED8D553141ADAB148AFDA200479CD04C0FFD1478EBC618303 +5437A5BC1AEE3218E9B27D21656EB9F31BD4E7D3186C89DF251207E8B67265585083111BC1AFD4D6 +A2773629147A29CBB4BBC3935B83392487858E0D18FFC96E57C83C4C6744C8E0DBF001DFD64B660C +CC8064907AA4BECC12376A1EB55EDF655CBBB4744C1A6166590B9572073A2AB577EF446B80567241 +389F990AE6C90B286EB48454166BBF264422FF2A387FA0B413F2295B6B188A64927DF702C232CCAE +59A2CCD1D109840A464BAC74A45B865A6667C901D86F771C4A36421308551F532497990BC15DA648 +6E322566C210C517DAADDBC24DACB39CA41611B9F961E4696D1FEC46E71C608DFDEEF19ECDD88724 +24E1BEF7176B0AFA4888BDB4C56C8690AFD03428F32740DFBC4BE22B7583DC47BBA42DF4E83C14AA +29F4E79397BBDCD2EC43338BA1F0D2CA9DA6E64A065FCB0073ACD0D86E46EEBF1454C9C172C6BAA5 +0F37FA19756B05405763387237794E9FF74E6749BF6CE5EE9145413950D342DBE5B0EFDE57163127 +2A8D060E935547E1FB0FB1FD60400FF856D027671CCAECFF7E215BE61E8A77E9BF5C72C2C1E4501A +A11B2F8574BD823574EC9598D579A9C5C525867F4BAAAA78DDA0E5BD7AC832DEA41328A507874A85 +90B7F133E743471D4FA27DFAC39C6F99E233C913183B2A039CBEB5CB3BF8825A92D83D266246D5B3 +CC7B11469E611E260C6ED16D17C9693B13B78E3F0DE2F0ABBE73FD6AE1ED25F57B4894254FFDB332 +0E65ADA53669B95CA28BB4BD166507E9D8F12727E46D2B9593186C090764FE8A95F1594291FD551C +A96E4CBE1D6FD42852B2B65E7F10C4F17707F930DD934E2A2ACBDFA40E04EABC1E54632D67AB7D5C +00DC103A3D11DCF78B6771D98ABD5CCA0B253283C67B0863C80D1A78FE6D5422568207509FDB1946 +92706C7A211B29955F6354092C9732DB2ABA8CDAF407FF25B40BF9D73317D5985E19E6C12B6AB5F5 +DD59328905F822E1EBEB87C4E386EF20CEB7EAF842700F09BDC50D7AE6442D1C93804D56FE194785 +968373A154E1F426BF76692CE3E4474360ACB9FEBD3191B8EE909E7C224EF90EEF36CD660AC9A642 +4DB5BB20B8835D365D35A442ECB2444F30466C3323001A087639B73848E3EE275406CFFB495ECDB4 +52F2A357F45D2D32BB22CAF9F9C2C44741A5341B29BB6C4322287A2A3891E1E853B976E17DD9306F +D98BDE8A0C97ABDCC7C708BD1BF49B524CD0DCECB40DDC557E9C90A1EDDEE57BBABC4C338F08A625 +E03C1C1A20DEA709051EB1A3264D7002D6DD2BB7B622AF93C7DF54F49EFA5FBA58F115A049A91875 +A2476C19F9133D0BE1B07C8102746163CEA0A98F9B62EA59C9CED7AD808DD52DDF8687933AE52A7D +9D7081150A812801D3FA78F90C4933E5DF09C991325324DEC6ED364ADF73DFC7B78953B51FBA2F81 +F264C2A94139289CEBF7ECD6D8FEE9E579BE231FE1ABBCC07B4BF1884CABDCEBFC56610A5E2B7510 +37FE804511FB5443F7B21290E38855F6DE5EE38960E02481DC57F83CFCB87C8FF1CB196CDC19B2B4 +6C7C72B64D7D45C678851ED7E2860DE7DB772BE7B33C84ADF7E6F9B2063D1263CDF55E6220753ACD +67C4701EBDC0799AF47AC58A0CCE796384A50DE6A814DD994B372630E64E5FDFF57393522F689DF7 +81C44599B21AB1C214D4ACF94F07CFEE79C628C85378B96D9007ACD4225E14DAD8C70984DC596DF1 +96BC814E7346217C94F172FA40B3DFC73C30E6E530DDE4A91F6A5166AE19EAA3D2CC3D9D6917BFD3 +1EFAC19C236463AF8D7FB7D9B3A4D6FB6D962C59B296E988561957A3C3D11F33ACF69F5768A2DA30 +462BA9AFE67D3C41A1E8368099361F50D4F6368963A48C75F1590804E7918A02EB9DA5F60F49828C +95AC60E9D86DEB7699C35037669C02D408269D6BF481EC745E4EC630F68FF5168BE5E0BDF875EEC1 +5EB9CAA9CBED1CE2A0EB4CE97C14E10114087DCBE5DE1DB6A070CBC6B68EE141B7D62ED320F7B74C +07D9762E0F5C8AF0779D38B7677ED65B5DD2F83ED06F041B5701228EFEAA0FFC031E44D6D68B274A +45FF9EB62FDA007CDC98B86831E9A668098364B421E1FE2A45D85824612A521777C6177316F0C6DB +D9847A2F61326E74A3114EAC3ABAEE456156DF125E4EDAD6BDB66E7DA6370C90ADC4AF4EC1958170 +C32D5AAF3B45BF749EE1DFAD8A5D0C03973B7C1589EAE4E67F6C87213C721E4A33466F3F54253545 +5014417CFFC9AF461CF695F5E1D7A37E4C7680A5624AA11EEA65C59BB38A96734E4A4A238C636638 +9EB8083D5AD753FA7F74EDF10054315EDD53DE61D48846F70AA9272626E23BA3DAB7CCEE819B0A88 +9CE775AA7F5205C95A19AAF475729D2C0BCAD9AB5459380A5F034C108A64B80621820D779582A198 +14058DFDCC6C2D6461AC6F98C64AABA53A2EF77CEF612EA766FE15498F56F54106D70D0F8DD4134C +8C64B282E79A96F15D4BBE0297885ACF775BA5009006315637D60AD86D25A3CA6AC5BF5B4DB95B1A +672A8C589D89101809D838236560F7690E329784C1D335203BFED3A258CD79467D16B6DA06A282BC +4B9669986CB50C54DADE3D5B20FD5CD9FDDE60AF4B5D8D9C50E77CF50DF3B36F37F381FE7E28A719 +91C8199D18258C09BD0440F5E258A5D2E22C6CFBFDC5F91228992E86532F4C0A50067BCB2D4387B2 +9FEFB8043D7BC44A322FD3629D7D92B6EEBA21B035DAC884110BDC6B22CF59B08C195BBA1C682E5F +8CF32F479937D329FE8EC6E9A3A3AA63AE95EEB766E0EACEEAD4DCF46617888AE687009112732A7A +8FA8419A4652075EDDF83E291613B66BA793D3858CEA093C79B89B0C93E11CAAB86AD1980C401713 +0B6A02545DE484FDA6E4DFBCEBBC848751B2A8821CB3F7AC96D2EA9B83B575AB60CECA41EA567EE0 +8ADAC8F1060255057527485D6B12A26D2E7EF6892865FD65FEF28F8B46249C4F19341089D59DAF82 +582CAD1A3B53C763A0E57134A57C4A24AB4FD358592ADB01BE8F1D9FAE9CA97A3BC3A0E2939913E0 +62960CC1D29ECEF9E61BAC20E92F13C49078E7F1562C9D7C01C2B300ECD6ADCAC9AA0C1F1BA30401 +0573F79D158C66D91A8D987C5239C9D4FEED1DC5C9586C4129C33E7C737C624EE9BD1EDE38E9F72A +78DC19E144B5AABF3B0FB72216815E371A5D6452684965CB7208B87CE5A27E444820E03F80AD2DF4 +E280D25D9BDE41E719BB2C939E25B7965DB162CA8665CBFE65DFDC7992A508D54FE4EE8454C11482 +7186A280D3D9EDF397640A809E3646675AB6621B9CDF42808E2F19859AC975CF70F41D2B3D22F8E0 +C180126F4A12122B207A150BC8E1819962DAEBB821DE59B7EEF8FF9219474597AF859E353B4B713F +5F1350F4049DCE99627492E396038B10A2F8E82AB679F65BE14A166E4502921BA44CCFE04E5DD3FE +B04BA47C3F7A3E7DBF7D55328AD1E96092C73EDB16C3325537295DF768E2633D01304EA0B01379AA +DAFE6CCA2AB3E246768C5AC841A458A2F69141A9A9D716D129B26643F1BBC95D8CD0ED44148F7C6A +F370201E454F4AFF7C11A8080508FEDE242343E7ADDD5A8AAF079C50765A79BD25FBBDC59EDF980F +FE0B60435692C49D20AC22F8BD1FCA3F3D8200CA26D61A30212558B54671E63BC09776A3AE7A906F +CA63281F080A00500C42C02B8F82357A3EBF572CFC4BEBA3FE86537DDDF23A861C240A31778A131E +F638C1DBB7E87A45487C092D23FD9FB2C162C3253636ED263B141178FCA9F2D5BE2D81F99B2872C4 +B3C08749CE18C9C00E75DA6B3E4C6A6DD79EBA731618EF5B5E767BB40429D8FEEC6ABFE9975746FB +3A82A03A07B7D0DA8BAECFD1F72AFE17EB0C0CF100A2D48F4449A42C482BB1D64C19F8FEA73F5BA1 +8FC9BFEE3F640C39C081669A9B0EE3FF6B2BDC0A726301EF0E2259E65BE59275C76E7A3F1C76C3A3 +3AD0E2F79198DCA39C482B4E1196238DBA937692CF319436830BBF441C0A8A04161DC0814B5DF049 +83099439EF421B6D37D351D4A988A8DFE93D796B2E8B2564C602F79E6162F56F933196A0DCE91051 +600BAFFD9D439A91F17C20CD6A9104B553D823A0317E3C8344E9F0B2560AD583BF876AE307892500 +2177AF61BDBD745CEC2B2A7931D27423D2667584AE0D6CAA2B281EB9271ADE86C493791AC9A2265B +8324F764D20DE65F6267439F1574DB02C600E771D3C743D420FA6F8BF01A977B91AF035E0D5412AB +0B85D6ED3ED9D0345ED434F9B95FD911A9D3828CD162686E6ECCD2B6A5B1104F0E6838AAC7FB3FC7 +09F08F8E82B4BFCB55D8984771F9F4D339EE7FF391448C7807A436A78B6A487D3A5A86F314B302FA +C5BE1C1DB9844F975619D615D1C7A20EC2B144797E0648CF5C044C8DD1699EEA3AC9D7E3BAA54A85 +11A932623D5ED3F0A9C3028CA439AC395F58DEAF1C0354A169D9AC7F380900D9828C3ECBB975F6FE +6BD79EA4BEA9B71F3A9B1D2EAF8F1E475B4FB99758192EBCDA21B47EA33F57C58E904DA260C801AE +CF457C84592B81CFA96C10E448D705D24871F3D1AD1FE004406C8070A54FA3747788C5B55E9462E6 +F51378BF3F848360542CAD2D5FF9EDAC84C164DFEC115A2F3D873760EAE58CC8F361B37E6CE076EF +325A1C54DFF84DB95DB7DB5B56C48ED15C5426E6153D8A94B1A35B22EAB1D7B871097DF12C093BA5 +538957AB7D0AB2E39A2D1AECB91F0A693B8F6A00601929B6C55AAE8A227CAB6404FEBCC8BC4EEEE0 +ADD4CFD7DEC225170A0063457918B9875AD7F022A8F9932ADE316E4126EAB75C1D0B2B9F44E85F1C +54755D1301345665EA630C8E885A8D9AB069DEA2CC6D12E4A0BF6E80C3AE12BC7ABE507422A5D92B +65F70B4A472DD945EBB960880EC4C34E4C206C260FDF86A997D7D399A0219A51E6F8BC1189EC0ED0 +B8686F0243BFAFD829979747442AB1DA8282F10F4C37C4E6C88D231460DA3BF34C23A4755E2F8F21 +F6D138C86B6091CB3BCE5ED170242C4CC87C010FE63DA9A2ECBDD85311DA8572AD67E40F744D2EC9 +6607701D9790574F051C859EF29291AFA301C809E0A5513DF9A7FC2DB1776DBD4A2CB622BD17811B +C0E584A8D89D4BEB17C2D0AA8BF31CD7E2B36C0E1888409788E30EAC2E46AFA1CFE457D3A19C28C5 +80669CB82CF36BF40DFAFED78F99A5D5DF189CE293413FFBA9E8D2457986C68483242AD767D5F026 +1CE303153C35D5DEB1789448088B51C48A9B8E903BBC69ED7FB0129CCE33A0337198F11DBA94FDAE +AD74BBC5E519F559094E15B03AF3EFA89DC38B4278C68D2AEEEBE4B28B351D7B72F52D94480D8CBC +06544645F20800B8284A4E7C8FAD59D0AA8EC8EC0551912CBC62694A5DD15C469BBE614FB187C084 +838D676E5A1CC8DFA8676758EB7D53FD2BA8F1C434F4F70AED8F2CE27EED2F6A82E20E30EACF9BF0 +CE28B4BF7D556815A887D777B7D75F60D2C410AA2D378B7BFAA6C3A5BF0A5EF80D3E35A0CF393A5E +BC27A895D712C6F004F1A03F9FCF493973F33E6FB106407DFD992AB0E9E36EF395663A6EEFFD71BB +2DAD9DFE6AA4685F307B8FD69BB2F74E672666414A71FA479B37D3DEF6AEAF80FA14821C65F7ABBE +5CC6DD49002216E23B3D22C282F3DE0077EA81017E79D8C3BDB61EC031CFB13FA981120A6C518F4B +4C39C9DD5AC6EDA29ABA527C9948BC3162EF65E8251C9D4E3494C6A51A41D489F8BC89250128EBD7 +0EFD01B9F03C40B6E0384364F2571F7D969016A959E4243D7AB728F0F030B8E2FB456D8E47A85898 +313B0E91C68C7A8A7226603427AC6DCB5C69924FD026E90CB0EE89AC42D874BB15AFC981079E9DC7 +1271C3E461265AF327D4AAB4AEFAB6A9DC3F84DCE7C7868D4719006344B9055C8BA952A2250B8041 +E5EF519BB788201124BE90AECAC2293A310D9565E1EA4AEB99F7ED7106649169E8EBF3DCB210706A +F2FA65158D3059DFBD7AB5B00DBB06B016F73FEDB3AE1C1CC03BC2D94B143BFBD759338EB181B1F6 +841F7B539D8D126FC5B31B244A3A00ABBB121A98F73314D1F1683F513C3CCC9A0C2180824ACE5D45 +E454062CE1217F7AFBDC60A6022B0134EDA6C5D787DE8A5925BEB374F64D612329B9AC17B6EC03F8 +56CFEE502C1FC3AAC7B6F251E8DE4147DCB1580185C406F38E6B729307EADFF6D7A0E824C9830093 +F0C4FECEEFC7FF839306FEFEB780FFBD8879F22AFDADCA4C0767B8EEAE5DB869D7BD91355F0255E1 +CAC68A81A276967F233624DE0D2082B89E2C2D1DB0F8BE0B2BD7051C4DE9B1564AA6CBA03E50386F +D685943D1EDDF5BE0EB34418C48E5EA645CBA552A0444B254E82E9E84A5B7BD0064D5EA87B1716C1 +4EF7FD14E2187FCE16FF6ECBC0A6588B30B4329626F024BDBA65457028668BC6750A4DD668D017CC +EF35CD9CF45EC73885FC91B8AD78920919B4747A710D25BB850B558C5CCF7170C20012DD0F2874A6 +699DF53EDC6C75716A5B6B06C98F4904BAD22B18C77B3EEF32DE3F45A8AC12383DA16DF6F06CD923 +D344CFC2253ECF97A52F2DC6A22539231E29A324F851265BA82BE566E42167247CEBD914EBE42EAB +E189E74FE1FF17ADA8A5F47DC9866CF86694DEE28EED333B0A8AE380557820E3F1D32B05AE27FFE1 +68267FADD310C4EE550F71704EF71F4C669952F30E485AF37561884066C0E3425C748BBAC77CC7A9 +87EC336B8BEBA44358BEA4ECFEA32494A8006E9852C4BFCB1C0F00ACC247F1F8E3F3B34539C67638 +64527F9F885132DCACBFCB1DC80EE3B2A711145DDA1D3B5F83144E2CD698D43A02B0DEB706C78D75 +A24FA39B9374E1155969FAD7D5FB477F4C3C7440DBFE1BD1DC877360E8690A6D3A64EF0A1313A520 +C49320295302227CE5625E134EA442A537505D206EE46E7E218BDA38F7B6BA124ACA0E949725CEEB +961EF8B0727E3A78287E61E65A84AC14D1014A213259A48F04C634C1F04C73B4110D0DB491250CCD +7DAE323CC8BD29BEF605DD92AA5480009DEAD1256E5C9FFB18362D63B022C2A378D15ADE2C6AE603 +1DF385CF090226F0AF889464584A9EA63B884453DE231058C5661C4F7DCF7505D7B1E48C78AA5799 +8FF39084FFF1CD18CB2989D867F2E085B291FE52367B4E8667E5CDA4F41DDBC7129DA5BDC39549E0 +8B67D9C9E4C332D91FE261728521245CBF51870F24C42F5814F393719A9E126C5693C0E776E4D8F3 +0F0288537B8E9A7C5DA4F931FC83F96A1950B1DF26C0448B646D3BF42C0429E2BD0164703FC71833 +19DDEA75CE65D9DFFE8369787FE664306D843421C2C0C3A5B210E823F8366161A96CA88D95FD6486 +5F9E5B0D53DCEE4A98AC10A8FF1BC34A46DB6016E973C9FA298D3F58228E3D9FCCCF9632556CE2E7 +F9C81E05FDB0FC1071E69A4C158E742D0B8939CA3DE33A4BE8D01D679C81C6BA9CF396A6768CA626 +05D796AA5C323E3A6208F2DA5000FC4462ED81AD0044D9959E40E4764562ADA76E33EC63271966F1 +7A13AD376A54FFA40FA247896A22C1C4A4CE51094CA1892A0B9904EB762839AAE1EDB5A0A5A69343 +CCA45F88F61926B23E6C85688862B1D10F1ED827FCCE2A427E49D960DBEE0D39EDE08D2565C6F5AD +66CA83DAC1467C38F742132F715DA7E9757A5D067FDB4A8859D148473F302BF6ECB9B7EA5C0DF851 +8F58D4D076625B92BC8201028064950D70CD40492DA83B98FFD79BD3DCD9CEE95178B6DEA9AB10B3 +4C61A74B49C3697CF714F56A9B1E366B04500C9A67704AF6F721851EA4566B96E220E7B526873F94 +E6013EA8C489639D291167BCAEFB661C96C27DC7BCD5B317C1F101CD8DF2FB29F6162B5ECE903C0D +5570C8C6325F05218850E4A5B5F94C7CE334BADDFC5DB98EF5333B4A92129F41D046D0F5EB6F342C +F362BC8E45511FCF328E2CE2D4C579516307AEC400BA207071C887F5185AF536A6827307D7698095 +E8A6F8C19812A11C32AD207FBC353172C8DF626C5C05CD24C171B3C8DAB270A74AC48E35F0A8849C +E0934B93D56C93599F5235EC441B5232257C4C6767C7B5A35830010E60E56AFBE9199B42E725A216 +C49D45A3C554767DF192F04D1A192D5CE196831EB7A56201A4A96CD44DD084729774334F698E1902 +614405347A37A4C753C0211D7F24D5265CF033E3C4D30E61886CF781D2AC30385E6021A892AB116D +1F5505635BF5C915D0300ACA8AC32CAA0F3639550C4AEB390DA7B86E53B6BB0E4819626757BA9986 +BCA4B4B18F6BF3706577A3BE146F2585025D5A36EB14E587C5469EEEB622D31FD32EBFA812B9CCDC +CFF7D17D00D9F21BF8871661F20E89A25A4543068EC509D0DF7DEA2A24FF68957943BE0FC7BD319D +92A575E12E6FB8112D72A100FEF3EE85972304BBE07FC1026CCC068E8A414F641116E51F76009C5A +F9E664BCCF93C943CFF2115235BB3290F935AA3FAB4F2E73FCDB9FA5E1866EFE278FFE7EA9DD6F89 +38CD5E5F4F31F6C659AF7EFF5B291AFB86521678B42C99275024EDAD04E5929D201A36F4A6C4DCD7 +2E349D6B9A1F90B123DEDE3DA50EDF929F9285747CD504C8F4A73D522F312D623BD65A35419C0729 +30976129B2D99B32D1DEDD81D64F7879CEDDFF04BBBF1286E07BCE651E2DEC190DFB9D25F3C1EEC8 +D7277752BB7CCC313A0FEEBF0EFFA2A189861AD4EA13AD4FDBD5160DC273F12525815F0D693C1413 +AFE4FBE09BCBD71E16C33E6CF8D18732447FFDCC2E4AF9E270D725C1D262D96E72538CA3309BCAF2 +35A3FAFAB27D265C583E6F6F062B555C97DFD89AC46E6154670ADCA13FD99F7A4B9B8BB224D43764 +8CC9DB76E177F562862C912D8CBC248A4C628FF2D0C9688611CED3DFB89064B988F16655633B7CC7 +AFAE34B2937528EA0814ED245D6B0D2AEF87A5B63E2BD7E4F7D9CFE372C295A0891F97C3855C97EC +C03C7C2A231704BED419612255F8B2C9262D48CB4FB46D63CC9697A210FA9D7BFC3BAB7E46172D3F +52A4AF9C4114BC72A5C7CBA042FA633AFD5E404F29408D4485837E55E6F9702F04C7AB410C351039 +6A78C8672F8EAD53BB9CBCED63FB9E72E7238CC88FB4E7C48C3DE3E4B80E277B952727916A4127A2 +5CC1413C390F4DAFD5253B07BC96DAE8CF4DA08330DEE580CBC1E12B75A661819E96B018D47A8B71 +B6BFAFC5E3CBFE68E5193417B6E730E6A2820838D22049BE6BB64B74AA13779D46519965ED80D5FC +30B0A6F73D26DEBD8150B3D27B3F135F608D59BB1632AD9C2E11177FAF54CBD1C4E58D58C395BAE8 +AD7C7AEEB0E1C3F0C0B7A5E7142E9A1DCCC8B4EF56C319D4A4F750857D2FD180F871772B9CC69FB9 +B222F83C3ACCB66125AF6848B2EFDCE3D2284FA5844641FB32F701FBF32F1D2F2E2233B66E36CCD1 +49FCB3FCDB6EA04367D11624717D73D9128EA7D9AABB8658BE9E9986E532 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +cleartomark{restore}if + +%%EndFont +%%EndResource +11.52 /Mathematica1 Msf +0.75 10.5 m +(p) N +P +[1 0 0 1 -61.69 -151.566 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +70.6 105.842 m +73.497 105.842 L +s +P +p +np 52 98 m +52 113 L +69 113 L +69 98 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 53.44 99.467 ] concat +1 w +[ ] 0 setdash +p +np -2.44 -2.467 m +-2.44 14.533 L +16.56 14.533 L +16.56 -2.467 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(2) N +11.52 /Mathematica1 Msf +9 10.5 m +(p) N +P +[1 0 0 1 -53.44 -99.467 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +70.6 53.742 m +73.497 53.742 L +s +P +p +np 52 46 m +52 61 L +69 61 L +69 46 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 53.44 47.367 ] concat +1 w +[ ] 0 setdash +p +np -2.44 -2.367 m +-2.44 14.633 L +16.56 14.633 L +16.56 -2.367 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(3) N +11.52 /Mathematica1 Msf +9 10.5 m +(p) N +P +[1 0 0 1 -53.44 -47.367 ] concat +1 w +[ ] 0 setdash +P +P +1 g +[ ] 0 setdash +p +0 setlinecap +78.19 2.952 m +78.19 5.849 L +s +P +p +0 setlinecap +92.764 2.952 m +92.764 4.69 L +s +P +p +0 setlinecap +107.338 2.952 m +107.338 4.69 L +s +P +p +0 setlinecap +121.911 2.952 m +121.911 4.69 L +s +P +p +0 setlinecap +136.485 2.952 m +136.485 4.69 L +s +P +p +0 setlinecap +151.058 2.952 m +151.058 5.849 L +s +P +p +0 setlinecap +165.632 2.952 m +165.632 4.69 L +s +P +p +0 setlinecap +180.205 2.952 m +180.205 4.69 L +s +P +p +0 setlinecap +194.779 2.952 m +194.779 4.69 L +s +P +p +0 setlinecap +209.353 2.952 m +209.353 4.69 L +s +P +p +0 setlinecap +223.926 2.952 m +223.926 5.849 L +s +P +p +0 setlinecap +238.5 2.952 m +238.5 4.69 L +s +P +p +0 setlinecap +253.073 2.952 m +253.073 4.69 L +s +P +p +0 setlinecap +267.647 2.952 m +267.647 4.69 L +s +P +p +0 setlinecap +282.22 2.952 m +282.22 4.69 L +s +P +p +0 setlinecap +296.794 2.952 m +296.794 5.849 L +s +P +p +0 setlinecap +311.367 2.952 m +311.367 4.69 L +s +P +p +0 setlinecap +325.941 2.952 m +325.941 4.69 L +s +P +p +0 setlinecap +340.515 2.952 m +340.515 4.69 L +s +P +p +0 setlinecap +355.088 2.952 m +355.088 4.69 L +s +P +p +0 setlinecap +369.662 2.952 m +369.662 5.849 L +s +P +p +0 setlinecap +384.235 2.952 m +384.235 4.69 L +s +P +p +0 setlinecap +398.809 2.952 m +398.809 4.69 L +s +P +p +0 setlinecap +413.382 2.952 m +413.382 4.69 L +s +P +p +0 setlinecap +427.956 2.952 m +427.956 4.69 L +s +P +p +0 setlinecap +442.53 2.952 m +442.53 5.849 L +s +P +p +0 setlinecap +450.12 210.041 m +447.223 210.041 L +s +P +p +np 451 203 m +451 217 L +459 217 L +459 203 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 452.28 203.666 ] concat +1 w +[ ] 0 setdash +p +np -2.28 -1.666 m +-2.28 14.334 L +7.72 14.334 L +7.72 -1.666 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +1 g +0 10.5 m +(0) N +P +[1 0 0 1 -452.28 -203.666 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +450.12 157.941 m +447.223 157.941 L +s +P +p +np 451 151 m +451 165 L +460 165 L +460 151 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 452.28 151.566 ] concat +1 w +[ ] 0 setdash +p +np -2.28 -1.566 m +-2.28 14.434 L +8.72 14.434 L +8.72 -1.566 L +cp +clip np +11.52 /Mathematica1 Msf +1 g +0.75 10.5 m +(p) N +P +[1 0 0 1 -452.28 -151.566 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +450.12 105.842 m +447.223 105.842 L +s +P +p +np 451 98 m +451 113 L +468 113 L +468 98 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 452.28 99.467 ] concat +1 w +[ ] 0 setdash +p +np -2.28 -2.467 m +-2.28 14.533 L +16.72 14.533 L +16.72 -2.467 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +1 g +0 10.5 m +(2) N +11.52 /Mathematica1 Msf +9 10.5 m +(p) N +P +[1 0 0 1 -452.28 -99.467 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +450.12 53.742 m +447.223 53.742 L +s +P +p +np 451 46 m +451 61 L +468 61 L +468 46 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 452.28 47.367 ] concat +1 w +[ ] 0 setdash +p +np -2.28 -2.367 m +-2.28 14.633 L +16.72 14.633 L +16.72 -2.367 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +1 g +0 10.5 m +(3) N +11.52 /Mathematica1 Msf +9 10.5 m +(p) N +P +[1 0 0 1 -452.28 -47.367 ] concat +1 w +[ ] 0 setdash +P +P +p +np 210 257 m +210 272 L +310 272 L +310 257 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 211.235 257.578 ] concat +1 w +[ ] 0 setdash +p +np -2.235 -1.578 m +-2.235 15.422 L +99.765 15.422 L +99.765 -1.578 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +10.08 /Times-Roman-MISO Msf +p +0 9 m +(I) N +P +p +3.75 9 m +(m) N +P +p +12.75 9 m +(p) N +P +p +19.5 9 m +(a) N +P +p +24.75 9 m +(c) N +P +p +30 9 m +(t) N +P +p +36.75 9 m +(p) N +P +p +43.5 9 m +(a) N +P +p +48.75 9 m +(r) N +P +p +53.25 9 m +(a) N +P +p +58.5 9 m +(m) N +P +p +67.5 9 m +(e) N +P +p +72.75 9 m +(t) N +P +p +76.5 9 m +(e) N +P +p +81.75 9 m +(r) N +P +86.25 9 m +(,) N +92.25 9 m +(b) N +P +[1 0 0 1 -211.235 -257.578 ] concat +1 w +[ ] 0 setdash +P +P +p +np 34 75 m +34 165 L +49 165 L +49 75 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[0 -1 1 0 35.28 164.105 ] concat +1 w +[ ] 0 setdash +p +np -1.895 -2.28 m +-1.895 14.72 L +90.105 14.72 L +90.105 -2.28 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +10.08 /Times-Roman-MISO Msf +p +0 9 m +(S) N +P +p +6 9 m +(p) N +P +p +12.75 9 m +(a) N +P +p +18 9 m +(t) N +P +p +21.75 9 m +(i) N +P +p +24.75 9 m +(a) N +P +p +30 9 m +(l) N +P +p +36 9 m +(r) N +P +p +40.5 9 m +(o) N +P +p +46.5 9 m +(t) N +P +p +50.25 9 m +(a) N +P +p +55.5 9 m +(t) N +P +p +59.25 9 m +(i) N +P +p +62.25 9 m +(o) N +P +p +68.25 9 m +(n) N +P +75 9 m +(,) N +10.08 /Mathematica1 Msf +81.75 9 m +(c) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +10.08 /Times-Roman-MISO Msf +P +[0 1 -1 0 164.105 -35.28 ] concat +1 w +[ ] 0 setdash +P +P +p +np 70 3 m +70 238 L +450 238 L +450 3 L +cp +clip np +p +np 117 136 m +117 152 L +155 152 L +155 136 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 118.485 136.705 ] concat +1 w +[ ] 0 setdash +p +np -2.485 -1.705 m +-2.485 16.295 L +37.515 16.295 L +37.515 -1.705 L +cp +clip np +p +np -0.485 0.295 m +-0.485 13.295 L +35.515 13.295 L +35.515 0.295 L +cp +clip np +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +p +np -2.485 -1.705 m +-2.485 15.295 L +37.515 15.295 L +37.515 -1.705 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +%%BeginResource: font Mathematica2 +%%BeginFont: Mathematica2 +%!PS-AdobeFont-1.0: Mathematica2 001.000 +%%CreationDate: 8/28/01 at 12:01 AM +%%VMusage: 1024 29061 +% Mathematica typeface design by Andre Kuzniarek. Copyright \(c\) 1996-2001 Wolfram Research, Inc. [http://www.wolfram.com]. All rights reserved. [Font version 2.00] +% ADL: 800 200 0 +%%EndComments +FontDirectory/Mathematica2 known{/Mathematica2 findfont dup/UniqueID known{dup +/UniqueID get 5095653 eq exch/FontType get 1 eq and}{pop false}ifelse +{save true}{false}ifelse}{false}ifelse +20 dict begin +/FontInfo 16 dict dup begin + /version (001.000) readonly def + /FullName (Mathematica2) readonly def + /FamilyName (Mathematica2) readonly def + /Weight (Medium) readonly def + /ItalicAngle 0 def + /isFixedPitch false def + /UnderlinePosition -133 def + /UnderlineThickness 20 def + /Notice (Mathematica typeface design by Andre Kuzniarek. Copyright \(c\) 1996-2001 Wolfram Research, Inc. [http://www.wolfram.com]. All rights reserved. [Font version 2.00]) readonly def + /em 1000 def + /ascent 800 def + /descent 200 def +end readonly def +/FontName /Mathematica2 def +/Encoding 256 array +dup 0/NUL put +dup 1/Eth put +dup 2/eth put +dup 3/Lslash put +dup 4/lslash put +dup 5/Scaron put +dup 6/scaron put +dup 7/Yacute put +dup 8/yacute put +dup 9/HT put +dup 10/LF put +dup 11/Thorn put +dup 12/thorn put +dup 13/CR put +dup 14/Zcaron put +dup 15/zcaron put +dup 16/DLE put +dup 17/DC1 put +dup 18/DC2 put +dup 19/DC3 put +dup 20/DC4 put +dup 21/onehalf put +dup 22/onequarter put +dup 23/onesuperior put +dup 24/threequarters put +dup 25/threesuperior put +dup 26/twosuperior put +dup 27/brokenbar put +dup 28/minus put +dup 29/multiply put +dup 30/RS put +dup 31/US put +dup 32/Space put +dup 33/Radical1Extens put +dup 34/Radical2 put +dup 35/Radical2Extens put +dup 36/Radical3 put +dup 37/Radical3Extens put +dup 38/Radical4 put +dup 39/Radical4Extens put +dup 40/Radical5 put +dup 41/Radical5VertExtens put +dup 42/Radical5Top put +dup 43/Radical5Extens put +dup 44/FixedFreeRadical1 put +dup 45/FixedFreeRadical2 put +dup 46/FixedFreeRadical3 put +dup 47/FixedFreeRadical4 put +dup 48/TexRad1 put +dup 49/TexRad2 put +dup 50/TexRad3 put +dup 51/TexRad4 put +dup 52/TexRad5 put +dup 53/TexRad5VertExt put +dup 54/TexRad5Top put +dup 55/TexRadExtens put +dup 56/LBrace1 put +dup 57/LBrace2 put +dup 58/LBrace3 put +dup 59/LBrace4 put +dup 60/RBrace1 put +dup 61/RBrace2 put +dup 62/RBrace3 put +dup 63/RBrace4 put +dup 64/LBracket1 put +dup 65/LBracket2 put +dup 66/LBracket3 put +dup 67/LBracket4 put +dup 68/RBracket1 put +dup 69/RBracket2 put +dup 70/RBracket3 put +dup 71/RBracket4 put +dup 72/LParen1 put +dup 73/LParen2 put +dup 74/LParen3 put +dup 75/LParen4 put +dup 76/RParen1 put +dup 77/RParen2 put +dup 78/RParen3 put +dup 79/RParen4 put +dup 80/DblLBracket1 put +dup 81/DblLBracket2 put +dup 82/DblLBracket3 put +dup 83/DblLBracket4 put +dup 84/DblRBracket1 put +dup 85/DblRBracket2 put +dup 86/DblRBracket3 put +dup 87/DblRBracket4 put +dup 88/LAngleBracket1 put +dup 89/LAngleBracket2 put +dup 90/LAngleBracket3 put +dup 91/LAngleBracket4 put +dup 92/RAngleBracket1 put +dup 93/RAngleBracket2 put +dup 94/RAngleBracket3 put +dup 95/RAngleBracket4 put +dup 96/LCeiling1 put +dup 97/LCeiling2 put +dup 98/LCeiling3 put +dup 99/LCeiling4 put +dup 100/LFloor1 put +dup 101/LFloor2 put +dup 102/LFloor3 put +dup 103/LFloor4 put +dup 104/LFlrClngExtens put +dup 105/LParenTop put +dup 106/LParenExtens put +dup 107/LParenBottom put +dup 108/LBraceTop put +dup 109/LBraceMiddle put +dup 110/LBraceBottom put +dup 111/BraceExtens put +dup 112/RCeiling1 put +dup 113/RCeiling2 put +dup 114/RCeiling3 put +dup 115/RCeiling4 put +dup 116/RFloor1 put +dup 117/RFloor2 put +dup 118/RFloor3 put +dup 119/RFloor4 put +dup 120/RFlrClngExtens put +dup 121/RParenTop put +dup 122/RParenExtens put +dup 123/RParenBottom put +dup 124/RBraceTop put +dup 125/RBraceMiddle put +dup 126/RBraceBottom put +dup 127/DEL put +dup 128/LBracketTop put +dup 129/LBracketExtens put +dup 130/LBracketBottom put +dup 131/RBracketTop put +dup 132/RBracketExtens put +dup 133/RBracketBottom put +dup 134/DblLBracketBottom put +dup 135/DblLBracketExtens put +dup 136/DblLBracketTop put +dup 137/DblRBracketBottom put +dup 138/DblRBracketExtens put +dup 139/DblRBracketTop put +dup 140/LeftHook put +dup 141/HookExt put +dup 142/RightHook put +dup 143/Radical1 put +dup 144/Slash1 put +dup 145/Slash2 put +dup 146/Slash3 put +dup 147/Slash4 put +dup 148/BackSlash1 put +dup 149/BackSlash2 put +dup 150/BackSlash3 put +dup 151/BackSlash4 put +dup 152/ContourIntegral put +dup 153/DblContInteg put +dup 154/CntrClckwContInteg put +dup 155/ClckwContInteg put +dup 156/SquareContInteg put +dup 157/UnionPlus put +dup 158/SquareIntersection put +dup 159/SquareUnion put +dup 160/LBracketBar1 put +dup 161/LBracketBar2 put +dup 162/LBracketBar3 put +dup 163/LBracketBar4 put +dup 164/RBracketBar1 put +dup 165/RBracketBar2 put +dup 166/RBracketBar3 put +dup 167/RBracketBar4 put +dup 168/ContourIntegral2 put +dup 169/DblContInteg2 put +dup 170/CntrClckwContInteg2 put +dup 171/ClckwContInteg2 put +dup 172/SquareContInteg2 put +dup 173/UnionPlus2 put +dup 174/SquareIntersection2 put +dup 175/SquareUnion2 put +dup 176/DblLBracketBar1 put +dup 177/DblLBracketBar2 put +dup 178/DblLBracketBar3 put +dup 179/DblLBracketBar4 put +dup 180/DblRBracketBar1 put +dup 181/DblRBracketBar2 put +dup 182/DblRBracketBar3 put +dup 183/DblRBracketBar4 put +dup 184/ContourIntegral3 put +dup 185/DblContInteg3 put +dup 186/CntrClckwContInteg3 put +dup 187/ClckwContInteg3 put +dup 188/SquareContInteg3 put +dup 189/UnionPlus3 put +dup 190/SquareIntersection3 put +dup 191/SquareUnion3 put +dup 192/DblBar1 put +dup 193/DblBar2 put +dup 194/DblBar3 put +dup 195/DblBar4 put +dup 196/BarExt put +dup 197/DblBarExt put +dup 198/OverCircle put +dup 199/Hacek put +dup 200/VertBar1 put +dup 201/VertBar2 put +dup 202/Nbspace put +dup 203/VertBar3 put +dup 204/VertBar4 put +dup 205/FIntegral put +dup 206/FIntegral2 put +dup 207/FIntegral3 put +dup 208/OverDoubleDot put +dup 209/OverTripleDot put +dup 210/OverLVector put +dup 211/OverRVector put +dup 212/OverLRVector put +dup 213/OverLArrow put +dup 214/OverArrowVectExt put +dup 215/OverRArrow put +dup 216/OverLRArrow put +dup 217/Integral put +dup 218/Summation put +dup 219/Product put +dup 220/Intersection put +dup 221/Union put +dup 222/LogicalOr put +dup 223/LogicalAnd put +dup 224/Integral1 put +dup 225/Integral2 put +dup 226/Sum1 put +dup 227/Sum2 put +dup 228/Product1 put +dup 229/Product2 put +dup 230/Union1 put +dup 231/Union2 put +dup 232/Intersect1 put +dup 233/Intersect2 put +dup 234/Or1 put +dup 235/Or2 put +dup 236/And1 put +dup 237/And2 put +dup 238/SmallVee put +dup 239/SmallWedge put +dup 240/DoubleGrave put +dup 241/Breve put +dup 242/DownBreve put +dup 243/OverTilde put +dup 244/Tilde2 put +dup 245/Tilde3 put +dup 246/Tilde4 put +dup 247/BackQuote put +dup 248/DblBackQuote put +dup 249/Quote put +dup 250/DblQuote put +dup 251/VertBar put +dup 252/DblVertBar put +dup 253/VertBarExten put +dup 254/DblVertBarExten put +dup 255/Coproduct put + readonly def +/PaintType 0 def +/FontType 1 def +/StrokeWidth 0 def +/FontMatrix[0.001 0 0 0.001 0 0]readonly def +/UniqueID 5095653 def +/FontBBox{-13 -4075 2499 2436}readonly def +currentdict end +currentfile eexec +D8061D93A8246509E76A3EC656E953B7C22E43117F5A3BC2421790057C314DAE3EFBFF49F45DD7CD +91B890E4155C4895C5126A36B01A58FDB2004471266DA05A0931953736AD8B3DEB3BCB2A24BC816A +C1C90A1577C96B9096D6F51F9E21E625ADF6C3A49867A632A605C117E6325C820121799F412E226B +EFE61F2813676F172CBD7EC10FF1EFBB92DF3A88E9378921BBD00E6024CC08EF057CECD09B824E0A +CCDAA4644296DE34D19D779A21C30666026829D38FB35A2284CAED23C8B913E7B28BB3DA7C8CE390 +4C0BAE30B0287680CCCAB2E6D4CAB2E7D786ABF54068028FD7D94FAC236094761B62B7E76F68D2BE +58C23AF85001950EFC1A9C1BB71520B78DDF6AA0058D25D041E86D01878DF56A5C48D74DCB2BBD68 +D75C94A3CE878484D28049331CE3D4364B40FAA2C754E8F443D244C5BC44B1C7868E36EAF4F7EF1F +6CB81E6CF63FABD65C29A991EB7D724DA06535AE43F3D0E2D04F6113B493C15463B4CEBFB72AB879 +7E4645F9CC0BB17E02A9626BEA4B4259F798B53B18DF2ACCF2B86BF2209CF0265DE0A46869333F98 +CCF70BF2C9239A0ABD2E97923AA5695BAFEA31E27B8F532BAA45F2980A11D069265A5312A260A627 +A765C4A08897E5C500990AE6FDA4CD6D575905468417297380EB6400CB2CF001C4B8EB79811CD8D7 +C173A337922B99DAB1048D5D03C78F78F36FEE31673D8C5FF8AD689A63AEA055CA705DB47D3AF965 +73571985E62F63866018C96EC4CA7E735C9294D8C81C03806D23CB87C0C08F86F5FA68CFC9AE48F6 +958AE016DCE4D60EB64AEAAD59D8A2592BC398BCA479FBC2F0C20C3E7F730481494C88781A6A9E0E +4F47A94619A3841FAC76A4FB252EB6FB43628AE1A4944539B1DFF672940AA5E93FFACFAC04624EF6 +7ED9C691788F0004DB7FFD1C995F2C52C0042F02F5C789F85D9E51716F3B4EDB21D4B9E660E4A892 +B747201EEC6DD6A8881FA3039664061094D1892108A2AD068D7F0251BFA72D874ECB2F42D27CC343 +052156F6A3A66D2CA6DAEF046A433FD54FEB4410315690971D0F43363EC0119B21F4BE3DFDC8C28D +BF5D68F145D3AC932EE62C32DFDEB1C48C2455392193268C893093BF911645986607DD13D8285895 +1854A7FF81FC98ADD44742907818B3C8E3187371BD9FE6EF5B4315E82C359BF2EA91D2894EE7FD9A +734BF2745D7FE8D08D29DA2A03176C992E11F1AADCE219D5E33F325BCFF4521D6D04E61B913B6C41 +740AF8BD9EA83F3AE7C4E5402366A812B1724431EE78D2028317B975E91941B591CC6C97740C453C +C7D7AB3CE97AE2F4DFAB6B9C8810A52A276BEAABD687BDA0971EE623A40B7AD6A8E69ED8BE63959D +3DCF8799E2505AC7F7B0E2FFECB0B08027A6266B7F96311B0AD78B7B6C78392AA062A73FDF179FEC +7F748929D060289F899FE417D4028FF332204FE04146BB130EF05FB57AF4BF9337EF71094DC5922E +3EF2D6A9F8257AF242019C349B65C2A3972ACA842D14EAB6A8D287456C9D7D295198F4AB7632EE43 +7D006B8124A80BBF26C1B902379D7F90080B51F982630E27B6031CE20930C273EA5D7FF0FC7E996E +67B072796CD96A7739444D21FE10DE7B57D143E4155EEE1B89F32EBCBF4C64D6D3FA1C46E06C9E9F +F99BC9BBCC61A400C7DBF812C42AAED863FE9EE3B02E731D1809F3CAB941252DE486BFE641F60F60 +C788F68519A6B04A4D6F50F02F93C6B8774B960C1FE893373D2AC2D865C487CFFE7669E1F1E73630 +7D34C15835F0453C8B0C1AE352CE1F27065F1082E6C84F86330F76B39C246315D6996AB69F81A020 +30D92CCB4C2A5AA4F4B3CAC88B2C8C9C621294A5EAB6AC3778DB99BD2F411735DC1421861417B4FD +399B365AEA45185A3D9D687546E36BB73994FB7FA3EE890AE3734BD9381B0E7AE514E8C517B87268 +7364C38D0A8379039F33336799205F2F54BBF7C2E9B30B27BCFB9FF2CD64F5D700F2455EE66B6252 +6E79ED2B0E5FF9732281CA50D27A93F259B6D4B5C7F856BB7D4F2E0F7741FA2419BBAF86C372E34D +59BC7AABC4CEF4F47EE40E118AB95A4863E16C55824D34002D59844D1F51B5DC6FB6BB5D595C4404 +1E05A84FD453A129279F894D726F6CD53BA3E234518324C5F715DAE6E7B5695326FC0C9B6CA2B53D +B25EC76BE9662388058629E70DC7BD861E188F1FF32A754A455195163CB4754D116D24E6A6300B67 +1862625E260442DEA2505E36D5F7AA4AD1FEB3B42632E382959C7E84569B2A790A0D0A208C2D4816 +AD28046B42C7A4797D424277AD9425C04DB87DCF112AE431CFFF6E4FFA979E947502AE5E1755C112 +0AE2361888B956F3F513A5975680E6E8374D8BF26C32AADC826D729E9026B14A68BC3D065E11C697 +D4D474CF963AFE083DD7D9278A0C27447E25AD70DD40B2EBAB8040164E11CD75AE3125C29806DEF4 +AD1B989F7001E227685DEF6EBE3287DE43BBA5FE2123A0EC835AECF879C13F7CFDC409901F291E89 +06C26E42B189862AFAE029F03D86D35E44E318FE16537E2B73E4F39F1E6945A3A6432438DCB6D2B2 +09814B5228D08165568617C279499ECA1B88C90300F1C88C45D4BE3DC726A59E24B46C5B2FF228C6 +E6645819C6F1D5B05737BE7353F4787EE52A21DC47A44F3C44406E79BBFDDC164682B02F4C39300D +12EF37A58E317FC1B8CE58E04BE666ED5DA75DBF752BEDDA4C7491E4D6922BCCA9CF421CE6751002 +8638EF643119841F423626D6B19A5D2CFB193B093D7646913F64614C098E5F5FF9422EBA75FA9AA8 +4F8ED114AEAB011E6F0727FB96F26BECBBAFE3AA8D0ABC5A8E9906B6CBB9E03F8CC4FCA97C884B83 +7CC33C619CD3195C55633B72D3F2D45561CD226F42B859B8099812D591886FA851107A185169FA7C +944248DE28642FA3043FF3B60236BFD781257C6FE4D56174AD16ABBF9659C05F08673A70496A0787 +C187D4367CB0CF48BD9A4FE0E481273E4909A1092626A13917DCBDE920028B06E094F5B28887B990 +32521E1720B75EB284AA6FFE53FA5CD5B903F951FCF7B33CC981FE7BCC4BDF4907ACC3AA60B69969 +A9AF204C84EC8C0F5DCB8A85E39EA9F2D4B67095A44CA0C8B072D7A61F3015D502B1A0F660C22221 +3231986D5E8C04AECBAFE999D1735A80051C06CA279D0FF6215633FB7A706454DA7236DB17AD72EE +1F6044A26A0EB77AB3BCE823E3F5E0DD31ACB029A1D17665FF16E5D1ACDDFD83CAEE1D48666D7BC6 +DADC34D317C335855D118892CBD32412F5870C3D2E599A46AA997A5E2BBDD3001C2957D81345DBED +583B72C4FB356F0C872A31A35087775EF18601930C0874EEA1ACB3ED3690EF447926439CC383087C +C9D5C6EB21EDF5941CB4E99FDA434A91676D76DC1A1BD801EECA6B0A88370B7005D41A1253CF8217 +1285986DC302B51123DBA9733BDEF0361AE0580FE6FBA5F29CF1438801586559D7436436CFE33E6A +D6EFA850BB8C9382E1A068246F370388186DC278F31F770C2C96881AC6E40823B24D6D536514A2C7 +AF3D159621080442592CAC03D6967BCBDB38FCA1A45F76F1A10027F1DCC076533C6AFC097FBCF0DA +A0078BE0828247F938AF76A87EFC14D9F5444CBCDCE637E2325D86B9D99D1ED70F44194E19F6C7A9 +9E415DC8A6E484DAAE52AAC1307A5353E8F649A35214B3F43DB5F3DB3ED06580A570E60B3E52679F +F90A3B00D4EB4DFBCF0D2F9C5545A6DE10BCC849A0BA9E18F29C6F09ED0F0DD48AD37B7925654433 +A6D02965A3813BA2EAB2E6C2004ADD216DAE99471EE518BD0DA0F534F1512A8E26286B07FEDE71E6 +0A5A057A22AEF095A8B4752F54C04CB8BC170F3D6B725B83A6780176194B21BA906E37B7923E2548 +604F8DB18E0A3E1B5FF04D00898C29C6033DAC54637CF5B068291559D8526D5201F3503FBA4EE12D +D7A6CF6271618F41FE08384521CD771FA80364D747430A071EE3D3ABDB5400DD36A0285430D537FA +F6EF8ACAF85C250D6652F327B2BD3B29E1C64E6E66C788FF1D9C3AC6DD38691CDECD0F3FF4079BAD +A2BC0CBE14AA3FCC38E3F31B3298A6995C87B34A7245ABA2C968F908D8337860507569C370654635 +570615F987531551414B5CCAF7F4D0B38F701619C553E746BD90655294270560046A925A021C98F9 +3EA8FF5B5B8A0D05AD483E6DDC5257635308C6C0FE9182D0E4FB011A00981A7B95DB5BF5A82F8B1E +B68E8822F8B1B7CF01AF11302B44307F3A71D5EB3465F793CAEB1E72D2C63E3D264380A75FF1DDA5 +00B5F82B04179EA9DAC10731FDEDF5913EFDEDDF5799D2A86EF2D16C0B52D99FCEAD392E9226AA6D +3D11D29054130C8602F703CB1EBDAAA089A02C0EBD53343A7B297836CB63E4B2C33C8760ECEB15E5 +6B59D4B296B8B724244D37651C3CB862C7135D62B2692F5A27B9374C5C3C5399E5C4DCCD76572294 +F742B1F545B736BF4C82D8F4E2941CD52C0B944261DD4CCF8A968B646662F3D187557206FF165F3C +0D3D5CA1E428D61D7936E1D00C5377A047EE80E0A5612F7FDEBB8B224270ED23A031A049E8676516 +BF66EBAFCF3F9D4975B0F212FB7A914EE45640972B61AE8E60E602DC7C20758BC07A159B08862F16 +6302D8CBEF03C4B0C73BD8504EB5B14DBC64FBDDC867FE51F76099769A7BD4FA4CF4096EAAAFD55F +9A20F6D4B84D8FD139112A43204026F15F9FF5AB6729537CCDA60369C24D7EFF4B6B971EBF0BD277 +A9AD1BF1066508A0A7DD9A8D45447A39B92D6B0F7DA4BEC2689D25F35C8C490891F053F90DEE5E2D +8C9D7FD1E23D0F1C5F57921BDB13BC9B8C3CED4FC42C4DDBF0706985A0DDABCC683FF5EA8416C225 +ABD219024432E3972C3314C30A672FD21523C83D93B2AC8D1DF023EEB1BD74E825FCD19873E63A45 +F6935E6685CF5EF472191B976F9EED2A4765E1B21B46EE1C4CB90AE89DA48E52BC4EDBAC2C855A67 +CB0BE9160F3126151CD171081192D0D6CB27E4EB2D725F31AE95FB283149F53F22BD8E414354D4BB +56724057601FE4BF34A5B188C00B0E550639CD796CC66EF895AA5315BEAD49B5639EF0878CDF2CA4 +271027678693EA212D0C11A6EA52F748AD0F62A0336BEC8497EE933EEC461E461CCD2F5291B980E2 +8B7D66690B10EEBE22B092179396EEF5D00E42E6CB73BAD4485F2063AEA6B6207E136ABB925332C2 +60F12D8B60B211A9BB15F37F42F53AC2559F5A8397DDD491D314B6DB0B50E41F0AA0A42FFDD5B9F3 +FBD8EFB7E343C06F793DA6BBEE4FAAFB233C31EAA3AD701B1F1C4F2FB05A7647439D19CC856C7D98 +EB3375B3ED2255FA33D9ACB87C1937E0B7F34C9B299C8D2A7F85D41141C598F9505C72B5AC2DE9BD +E24CDAE1DEE416786B93D4EE713E437D2C4A3251A296B785C81A8232F98ADD04B3D2B41394F8BDEA +7B602A555EDBD51A088C2277D8E86B08A0D05CB6142E33E973BB3F2CE841D323ABE6FBBF83B84272 +220F569DE23264AB672C2590D4527A29510E7F289DC2193E66FF23D83703E27E9E81572E7534B1DA +510BB846576E8A39D9CF44483F297C92317ED8E46237B3D73844B3B19D37831B44EC116CBAC3F75B +B67928C4D4E741EC95E96FAD74D852220F4F1A8FDCD273E0F6E77F09EFD5723CCA1398A021FAE947 +9CAC5922DAC8E2F46704BC216C7BCC1575A455CCE2C2A080B9FDCD0758D9D921EEB6DF96C64A31D1 +C9BEA80F48461857ED7DB635A3BABB3BB6155E835E605B09E06A2AAF6BF3EA70B8C0E098CD1A818E +30B9A4AADC284EE2B87E945B717FA73AFF5FB788E51827F6FBE319ADDD059614B493ECCE718789A2 +EB0F117EC811EC38A3F4EDEACA660612BD247425A3FB2E6022CC14FDF69B6660B37FCD4359F1BA54 +D12B1F478D76CF898824762C25A026B01C94752F6F3724C31AE788CFE992D9CA152902EEBC4AD8B7 +A5F0E68A5A0408A2F0BA71CE0D94B4A905B35F2781D7E7A2712DC62E87518BFE72A5BC4A9F36A4B3 +B1494B0C4C14705203762E0CD0B28BE31234449C7655B5D6165D7CC4A16431F7A8ECA58D25711E98 +4FF2CE123C05AF9A65D478B73739715DE62A199D47BAC65785EE1DD25AF91868F91D037C0AD754BA +CE3DC4B67F1FDCA8FD9FA39796EFA9C975DBFAA99DB70624B3563408D0048E3AAC6B4F228DC0AC08 +B9C2B63657EEDB53B46D157426A3B4B4B8CC5B4F30BC24CF9BED442DB51F3C7A0656DFBEFA401E1E +0823065499C69D51C477360FD13ACA8896A8117789C4561F3D85F3A80D18E39F1D6BF76C7876922A +1038ADAFD53F2D092369B356D0CA3FE6A27D7B9BD3985C78424C21E60F6BB46408013DFD7A30D320 +EAD9AC6E5FD36655AC6706666A76F1E426640C4B0BE692E4879991EA9EDF4596C0DDF43D4E370360 +D91E8B2839D368DA2A910AA6092337E2E20DEECF43D583CF164881079ED5A492B5EFCC1CAF91512E +0FEA8140CA3E2553733D6F743728ACAC3E643394015967DAC8839D5A804503A45DBC539FB8656D75 +2F00EECF73E7EC8746CB13F438CAFD554C01150048F758573903B0B3260AEDD78BC2EE87D201E219 +486315A4C01D95DAAB54335A4F2CAFC3F43F12A9574CD2DECCBC1858406C701EE62C281A55B729DC +EBBE74FDFF275A0A7B8B22C7490187D1839F4FF271723C140095F28221C4145C9061F9A5B3EDF8D2 +9E0DA04D9A8AF6ECD42DB897DD5C9318D636FAB698554BD9EF9B0902BFD8C96CB958773A3C4A5FCE +8A334C673206C39277E45AB50DA2661F89D621AF057CF1A7ECDE344DC7658514B4C655937E7BE010 +B0694B069FF64D5582E3A4B7F6AF6C96D056ABB20CC883AB25A9BEABB18A84F0258CA3E4F33FFB77 +9841F5970DB447969FE9C6BFDB066ACBC040648D74F553EE434BADC353450A3792EEF9CFDB2FBCD6 +07153F2EF73C1BCCE3784609F26C80193BAEF766E7CC7C33A4CAB862E6E01FC1CDF11E2FBF25FE1D +308CFF9CD924893861BABF78F762F3CADD3E0BEB38F6157CD08F1B8767606427C4A631AFC9488E6D +4D1A8F4B51ED48582BCD3089BE037ECFF18DF6175EC317EA56D4FDE37288F089532C13F7B3C1EF7D +333E7FAF8B49D95F535F60889CD7245E5CB0BEBFDAE8F7A0AC1AB7DA18F2BC06267B27403F1BAD9F +DF5F13254E96C294C4568EC7154479570E514A55208D19A4538959F7C5B2A0C9CFE4B4A40300F248 +5943C6AAB753F3F0E551727B8DA8E75305A9CE623757B36FB7D34D13CB14EE561C404CDB2D88F375 +2BBFD9FDBCC92CF110D6C35629E3040D995CD25B90BED2CE79BBDC846EAA321B1BC46DFF7845380F +BF08782D6A31EC7D41F251786FDE403A39626D6052D5189CFBB3DCFF09E81C09D0CE7D571F979509 +79B26AA4F6D07F97A33522306AD692D4D960CEF1CEA3D251A841E2A23C7AE3EA44B3F31E79808F22 +B6ED20DEE4186927394624E22D37E873B660BB8DE6FFAE058DD5817A3BBD68963D055D406F253701 +D940B5E58DAB01FDFF1C626D0D35E8E7995D37057DD07F6D4D45F152A141269A7FB81433652209B2 +B23D69BB5D8D367D95D4A94B2C2924FB83F50F651458CABCB65009912F00F485A924A61A0008DB44 +D478CAFDB7D9524204947703B822755A1301FE163A8248C8AED7073362D918D097597B24A3B579DF +FE00F47776428D2B992E1F5FAD6190ADD1C789BB9A913BB1B938EDE357BB436F734477F7BF332E36 +7C380D049AED8687B4C2AB6EB813E719718C9CE62296C24F783B16E9635A7E4402840BD26D3A0DA5 +78050C49E6D8239EBE9E053719BE94CF6D8C7942AE1660F28B21B3C1E6E15459C1FEEA4FAAE64AA7 +165824F7B939E28E51009FB778E370C6001B3F546EBB76454912D730A3F8C7F6F9EC01B9F90A2E5E +58EFF9EA99BE57A0154147C2B7A7C7E493E37C9BD0ECDEAD4AA4DBFF95D7E451246C9D4C30E71F4D +76441A297A1223FD2F8E017B10C108B0F0F67385060E9C18133A6264AB2B445F6DBCE86CA803357D +749244A6FFD8FF8AD37EBAF3787F3702764C5EE2CA7547D7A9FED5AECDD2064F7C767078579DE13C +F135CB05561B15BD9803646611422353774984D386BAD640C5EED157569356A17BB6233EB298960B +8E34209562AE170A08D15F3A29967DE067F6AD183BA1EB49A99F4899031A01410D7311BB9B7A984E +BD6A303D44CF42B40F12769D44160583BCD68C68F823DDC0D73150083404B12AAA68E97206053C6D +23FF0620231D3570A089520E928E980F56A273092DF94EB3A99FBFD877B58860231B2A761DC91A41 +A427778B06F07D4F78924FF986F837C1437B44EAD5E7C56B9CE9CCFC0F6ABDBFDBDE4A32E3FFF336 +7F7194DA20D038CC44C4413B2CAC15C05B22758569D1008EA057DCDCF4A324C924021B35B10ED632 +BBE921BE2E34795951DDA394FABF3EDCEB99B3CA15D6579559F0BBECF8DF6E6DAE427DF75657AEDC +FE180A88DDA445A5A5E239B364B8884714B0ECE259F32F1742DBAC0BFA9A1052E2B14E632B56A474 +F2C9DCA9B5FD8D62A39227CA8C208DC69E5F543A745A913641950AE0DCCE02D20D374B652E2CC41B +F0417F99C2EFCE1C23204261FD1BCED5A1E8AD4736C5F23E14482D766390B1C62A320F751CA13454 +8DBA0B08E4BA0A0CA5F6DC765F9520D15D895792BE580133B92EF3691B95331DC76A551C4AE9AB10 +24D7EFC4A02B5654057224C3433A2AD6859E3E4C40F8F0B089C846002C75ABD667C68606D7300B7D +0569753AC074BE6943AD20018835A6EA28B99C983BE3BEA9B742A76F2D7A2A26B35664D24FFBF656 +EA28D525A78298C898C0BC2DDB56FA37151AF5A08B28226CE6BF09726C37F1B0BD39DB144CBB5494 +5DC48C374BA8716F6C9A4626C95B6993DB2CCD59A7430E3A3B2E6CCAB9A801760B8548C8447D129A +01EDF187435162EC13A65C943CE9EA547C3694F88F9706AF29F5357EE59500EC080E7FB844E8021D +907EE02C344DDCB78559AD7FDA31A3031D5CA80C004DBC04BE54B38752D49DFD19F1124C059ED68F +6E85E1A3A848F98707D7413ED3DEEEA823D958CCE720B85303CF208AEBB9B799653EBE4DD16186CB +F8C0301AAC6271EF9E2CF6732A6CB8548B7CAF2399287D6AEBD5ACC7C9D0DEB85BE38150072A0184 +51D3F1A8ECD784AF8149BF932E0422EDFC608B20B46D80D3EB68D746E1EF40423CD6FA218C9F569A +3442B0E70A2D464DC59CAEBC9607333D7B8FB90349676207AACEEE5ACE8E0E9F9C5896751ED4DA00 +95D68C421D8754D665D3D58C682AAB1DD72EF9050894EB680110C3E7F02C3551D73C63CDE8B45E5C +453BC9AC1FB3145CB6F0141B8E4928351FCE66F2A5AD534E5DD8BD901CEBFEB963DE2787095D7755 +81E588D3A165BD55B51F039992567B85FD3AE92C7526E33B44B8149E57BF7E57579E37611AA29DC5 +9EC94F583181201638BD4BBEEA23BB9EF067CFEC2E436D726F401EBA897480AEF7E38B9798C6CD42 +182C43B2BFCA7D8B6B696544F6B00C7B7D0D2C70D955304A4FC8D97E317C01113404129D480AF8E8 +EC0075A94859D5A79DF5F3FDC2EEF4F0BC1113D2C92DAB9859E9944DFAF557DF43AAF42B4FADE1BB +F5AD6728F884F0D4E7671787F1A3500B00E62929147C72FED37CC222EE991630EC9AF614160872D1 +BF4666DF3B682373AB1CE36FB87C42597FF1F96D3D7B07DC8755C2304AE69B955FD2547D316E16C0 +458BEEAD37B904BC27DE764240E67A68ED5FB57BA1F9C7C4C8F2BFF745F3E6FC732FD5E37CC1DED3 +6EDE20B06FD832673AC78DFB7655D7358CA03332A18241D96BB0A45D16BF2A2B80D0A68C74C8DAB3 +F18936EF73085EEACA9B24B18EB7DFFA75C707C947E17736EB7B608C4AB90ABB53A53F44D8661485 +5D60E36CA31704053CC453F2A50B878AFCE0361EC30444F5D6009ACB5D9673E5536B11A02B659252 +A64923E738F494D6264824392234FCED4D66E0342D03189778D41AEFD907272A919AAF40066A304C +6D7831F90B347CB8EACCAC6A726B40BE4671D6A0A591DC36A30ABBF91F9E780137D80EAD43BD49AF +690A3789F39D3EBFEA9CC64B82D601B88805B6FDAC3C84C61638DFF1E391DC74FE4D08A0683BC5D4 +E6634F82F4DA357742764FFB2B8D264275F82052921F7647BD8709857BB1C98C205D13EE51C14E9A +DAD1324562267D1931B5143A2ABD173C745B7272A6FECD532B5F189C8749DE0ECD3A6B1799C1834A +414554EA6972309C48DAB44A9DC41D8B28361E89CCE4DE8AD6058469D2F603E7AA62631E80C01535 +539580E124A24E9387E3E0E58A63AFB29944207BE5929455A150AA58E27EC885CCF019CABE1B8769 +0AA7FD1F4166DF820A324FA0FE3B59F8B767BFE029A7E3ECED513A6CC622AA8CE96563219EE328CE +BD649EE99E5F108FD57646926CBA30BE3AA8E00EB4CCA282AA35C0742410477E2E5771DAB74E4181 +D91DBCF25DF36BDBDFC5AB6C73A982A390416A23C8DA10655906878AF714C3478C8A0C7534F6022B +80925A069F63834539B18D9CBE67844520A195019C15F8F858E91CC50DE001EDB52C89F06035473A +022A718893BF7F6FC0B2E6CD4C1CB26E98C1A191EA5429BAE831F464971180F5EC2CC6E6F8F9EDB8 +2E2A7CA8C5656BFBDD618F7D31635A23C73F330EC76F4543C9795600F8EA45DF62BF4E071FFE3758 +2DADBF252F2A3EB399F61BEAE10BE0FEA537C272CE850026872F4BDFE5921245147B71DAFDC8EE88 +C509B6D8AC249557725FC5D14198A2DC889A4A9ED045870F767906A226826AC93FF1E09D75B4DF79 +8FD690C5146175EF2CBED8F03C9DEEBD95AABA69E98E25A98CC96820CF1C684F02E7739F525B12C2 +72613012143FC26919B800342903624AB29219E6266716E451C9D60A4FA010B8D26B56A4C91AE1C2 +ED778E35E66B49C4DE64021894C2B39E7C883518B06E04D198B7D056A24C3E65BC9E30BF2F73F2DE +21E676A37E2AFD625220831F598E93BCBE098AD73FB6EA5CBD9D17EFBE6EE35FE4EE93BD3A75A2F7 +118EACBCCB82216DF70F56C2E82F77F072093824C6ADB800C66F0F26BF7AE513A490AC3DCF648DF8 +2E54567ECB9D6FE66E908748B7D5090910EC99EB9B222E92B058F3EF34A11918D6FCDDBE2B9C27D7 +DB19AD215E7B327689E0597963E6EC907A23A0EBFCDF86ACDC349CD8983EE83144B5B8489545AE2D +ACCDC8E553FF4A1601F42CF82A90D571E36193BDF4A7893B2637DDC0C73EC0C21BDC4BE1D02BD257 +F4C240DD6AC53A321E13FD2EF4343B11B9C8152EC02EA6C4DBF7F66C9356647A948CA9E26672BD7F +9B828FE109600B1638806DBB95DA6AD0F78050FB13AA580139C7198B1673D4AF9BB610A83A709D3B +7D7B01AFFC0A740F03A5E2E3EB7AF145E3338D46D03A83FB82DD6487A52F9494A89717FB500A26AB +C949C51FE25DEE8F78E199AA53EC1DDF9D767D8FDA77FA32F19200BDC539F00A23DEF166D97F0DF6 +64489D932D115523CED7460212BB35D887FC3507C2592ECF622FEA49AE2F04299A547ACEF75EB0C8 +8ABDFA7A042D5EE4C88D716B00E79C40173A3F7044546D093B988463623DC1649FC6CD645D01E102 +1AAD11911190D0B07C0A66AE7F9F9CDCD0D841A976A8298C26A0850FF4FD42EDECC37B97A19F7240 +3413098A81025E4451850EAF180299667A392B7D2E96C4068CE110CC3CE15C6E58CBB11CE21A3E9D +FDC88ECF14A8F2D422E1CFCDDEA320DF5CAF93E6F9AFACBADCAEFBF542775D07EBF09A96F0162438 +62662AB782A464DC7A96BAC2B0F0F043E83690C3B24432F61293A1C5B3699605EEE8339AB079BA1B +A7C65ED392B6E94FF817CC25AD32E89C95A0667F124F26B11AF5B45A9AEDE4F443429ED30130D7C4 +68C940A7C538ACBDEEF77BC084F8A24FD0060BB9CC12A710DB9DF03CD381FB6E76F79D3DE40DEA4D +FEC56ECAADEAD68DF4492DBAE69EF1663E2CF90614871094BF6F0E1C9FA0EBB2D34923A19A557BE9 +54D914F35BA044FC800D822D88B5E70CAC27D6D56C66AD6CC3C7647DC679C8D3E1D39AA8282BCD27 +982428F5FAAB76EB16BCD26A1685C044E3C7B87B3A1685279DED690D76C0F1C52B76FD13C419165E +754BDD7FEA75E26DFE2B916DD0CD40301CCC945683C8E1F49A03A0DCE1974A76B754BF04D36C2693 +969FE4C6C39D60D995738F1DE0ED6A7E0B80B40BC14B440B6B8F1085E83995E224BFF4EEC6F67EAB +103B4BB6D21F9741932DFFBE85C0BA3D2AF925D670318D1157FACAE9C09B3AAB5B1FCFC889348207 +8D5A3F7787C699C420C9BF0883D3B8B3D7753E9A146175245CA9E2EE04FBE258B6E42334EF141A41 +D68ABA286864E72F0E4ADF41C1C96E60E69320E79211984A734392C870D72B8C236AD631672AB9F0 +FE48EF2611740799DF5B3339BD49697C4DFC0557C1022AAF15C24FDC54FBDEE2129EC70473A17EEF +D202EE43A1B5C7B78A299B6EC8BC7595FDA6BD0BD22E025E8FFD89115448D99FD27BAEB680A22C59 +7295E33201199E9E1E38AF099926344D1B7CA626166CFFBA2F3D1C15AD63F0C6035A5B9BC5AD644B +3D5636C2FF3B9403AFFC3AF0460B328C390D3B416C7880A0DFF10BF512BBB933081FAF4B2E06C093 +E80950F08BDEF07D56BD042433CB0A0C72E1F17692C9F5C7AA97C31AFEFA39233C62D5430F11DD31 +478E86A36A8AD3817B0AB397C8D6935960D905994ECD2AA8299D248AA274AE0FD1F1377F0443B37E +67DE63151184DB5EDDB4DEB9CCAC76896BEBE32E30E0F3C2557429FBD4B85ADE7829428C5CC95CBE +018C17BF38FE4B26B0AB736FEF35F6E3DACF0BEBB3B795D7982075B75D87324AC28D7E5B446F04F1 +0A001FF191A5FDD10B4729E57578FC42C26D473F22C70D5629AE05FC33E50A4EBA2C5D4D63B1D147 +9ED7B8FD7A0D824413D06437118C788543A21520653572608F9172CB1D1AC529280AADAEBB5A4E30 +AF99A58EDF2952BEEA29F366FB6FE7A804DFB1D116B73B45033E9E7E9767A9F41F2FAA76F97411D6 +420FB211B4BECF6C785FFEEBD90AB932E82EB0AEC7ABFA4A7AEE53E2482617576EB28BB2A3C7044E +15F0B6521F3B073021C3CE55890951E041EFA38937F2C0683BAD1AF255CF3747AF2F0B8A92BBE44D +88F7768D35B8F4EAEF0AADA3A42E26E3E3EC25E286C40808665B80C6265716DEEFAE3A86C9B54D34 +74285F3BA2946302A065B800EC7A9694B0B715BC86D3EEB649FAB8A47D170550D9213E8B8E9367CD +FC8527955263AB2AA55FB7ADB7DA9A4E727E3E69D9C7946369CC078DD7751DCEA1C0601C57F4B5E4 +48BAD7F5F8A919632178C77B7B5F95E402DD808AD59EDC020D82399DBD3A9D9F3FD089B0909C171A +940673E361F5728A89DB3E2CD0AE2009A6D64FD85ACEF62F8B42F009BBE11EA0AC14525C2ED72E03 +0DDF4F670D18B94C07C027509F281F7B59D854A5E46F4DC544BB320C427642D84B914A2E5043B1B6 +FC94802BE925FF260775F6D17A5C55C5A5D5931F88E3D7E8485D8026545CDED1DC96D1ED7E0088CA +ECBFEB99F110A5CCDF7EF3B4190F3DA6ADCD5A46DB27C0F076B558667A1B9ED967363799307E77B0 +9859909B4E86A27639DF3A8C0B193502FD22C0298AE0398E7D33C3039D7878922AA9997C93F29655 +5F5D7BF3A34D46BA5592FE2DAC5A32DD9945852962457B81DE4B6B9050660EEE1A0D95785D824A5B +DEABACAC8A8A90E7303093F0DFE57ACDF0EF2843DD7497B1B80AE7E883735D9BD689C84A61DE8127 +3E2DCA2F64B00B62F0FA3D3B5359241525434847763059927565F4D98CB8AD1842E31488E4C1DC58 +4BEEAFFE1D3E76AA2E6C81CE2DA9F00DD32841412695C8EE17EA60499E99B97D30C32DDB0B9E473C +E65C259949129A4682DDE5DEAC0611464650236934D7C57D1EF7E8B5D9E5D7458F0FCA9795853710 +F37B5C24E39D7EE92B2D4066D533A662AE2B063B741559B24AACF24DAB6FB6786F639ABD8B34C7E7 +AF20E5FC999BA74AD93CD821B545C2531C506719605A64FC06DA8907550087A4599EFA621DDFEC17 +B904B6115BF94AAFDC56F3570065D75DADA1AB177F4C333A04A0119A89BD209DB0CDBC5DA0C8B99F +EFF54B2F4FB4BF95AC573EBE6D5CC8110E6387365CCECA5630F5105C887DD5803DC1376986456634 +C3B3BBC235A72AF168CD5B350E0A8BBC303A2CFC37FF627F4697C0D19BEAE28FC3996E967CEAC4FC +8D9D309E2FA65172E899285BAD3F8B45B38C9C2BCE94C31911079850A040C08789EE377B9E652A10 +01EE4F44420757358E183D48EED67C3008E6F05C3971C732B98ABC24271527B267D8B10735CB1FBE +773E33FA51B5B472E402676E3590C7BE28BFFDE77AC34544718A20833C9891A176AA3A62D686E072 +7AB2150A1E77FAD5012D0299593B0222CA38CED2B9953B1E5893F176132F1197609D04F2F1D647B6 +F44B2EB0AD94211733F226B570E9D963AF9A6DF04FDFA26C0BDF31EDC41DA521F9D0090A9FA5DD13 +B9D977329F6412815A8C52C3290DD42EDBD312592DACBE0BFDEA209F389DE8E4B5D8ED51B46F1557 +C2B50098C2262D3DB298E12C0AC3E03B82CD2807CE04E109ADD00EB181D701E4BC3622DE8A86E977 +3D6C4AEB830F3283BCCEA663AFAB740B546C3577E9182EFE660AB380F0504590CEEC89313A608A29 +9F9DFFE22DA6296EA3E39857D7229885C78F097E7E7845E6C908A0570D4ED0AE320DFADB7AF87E5D +F85AFCD1B271B672813C0A0E2EFBAC5275807ACD3A2F09EAB95DE6F571E3F3C827FB3EA9DE89DEB5 +4B8B14770305B23EDE569571D0BB8BAF4811E12A0DD5BA4809818D2FE088DC1CD4BE72EECB36C819 +AC25B41BADFA27D5839D548CEED7DD18F5F2BF69EFCAC0ECD4FD942995E930C147E1A7AD93628180 +E62F20F3779824324C5A1C35ECEF68DE30BF5DFDB5DDEBF66CBC459B2C7FBCF0ADC0274D137BE67E +B22FA117C85CF7D52BBBB4CA5F9A6F73AFC23BF2E23B4B1EEACD33DAA3187F1D104843876EB44322 +67EDDDED02B6A507D13E3B9F012FCB8C9F0D14D951415BCFB20B3211B5B9860B0C6826BE597F2F9C +94DA2788E65107C5CC77CE1265E33DDE9378AF9019E7E68522997470A52088253FDCA09AF9F071C2 +988CEBDB46B7F7C8D08B5467A1B3FA0EFC552C5E20D4B9D565AFEF9B520FA2F544E802EB8E8E0C76 +21FF63F4A5C0C59F94E9E1731D8F3C80C772805DE264C7501E705BB21EC37A1379BEF8B8A9E50EB5 +6FE9CF9C10C9D25CBDC68124D2133B2DB6348175537EF872CCB8B1787330A5ACFEA87E2BE6615DFE +442EC74BBED30021A0437ED3E9DBA6EC49A824F0374B446271DE6E1B16AC74816F6216BAB7329725 +8CFBA83C178F5EC009C57404391898BCC3314411F129F12D68218AB6D0BCCE2E5AA9AA1D5FEE1E2F +0AFAA4BCC3D226C5512B456CA8F28DE54858F18DE2B30AB4FA02840859988BDE7ECECB4EB0002523 +C6EC40BAC2E7ABC411329F803DE2DCE1EEB354E4E6771E4328ADEE4E713AD248BF4F91108C52B169 +140F33D5C56F1EA2240E7E182C060187E29020139C373B4A6CC4D2156F7C15590D3C07C98535853F +4DF901EA9F2C66510C190D9456EE037DCDAA428D433CC2231B2B627CE2B5304B6A4630576BC48984 +66D7A8BB75D53ECA10C74D434A4E50B1A706ED6C7BA274A9CAD5D929B9BB8A631825A9C32A8F468D +578507DF2291DEAB6338ECC92CE8664D4B1C211A4CCE186679B6C71ACD5655B97ED8E552B09C1C85 +387749406C549057DEFC059CC85639203160B8FF05A48F7D5C4F452B111891846A521674C0E2734D +50B8C7E7B5D9F438C58DB139A6509DE3495388E0D7AD24F64FE73707C7BFB8CB06FA0E0C41346B98 +220E007E28515428C1874AC996819F16CB152C16F89CCDB3F9C83070AD90337F1823AC0A48B72749 +C6C29A8FDE1EC2E76B0D29FF711891EC81D0ED0B3349E9FDC413047731D70C33E57D2C4B637C8FCA +B027CADCB3E11F94F61CF3A56E4D90E8550F456BB90638DB6118229C9B74C9533508F343C0EB422F +87627EFB562C7730E1A804E3E4DC80FC0F199CE210045ECB1E3313C3364F78A500A8ACBFCAE0F7D6 +56FDC8B1BB95262A32ED7562A62EE5CF989235D1E641D726F34D215242D44A946662EE94E765A3C8 +75557732FB4DE1CC2699202802D4A5D99C621478C1C6D583FEE8CBDAF54C73C8C17BC73F1B414EEA +BD901409B83E98D62749F9E742FCA7C632C176D323E72FAB241D143950B87AFCA5B7B75936FC6638 +1FD0E537C30D744E970A08636D27AE7A277F3838BEB7D1BF821F331D483FCEE4EF9FF98F350B5B3E +CF2D6A5BBE95862FD5DEA6D3A5322DE6723B5201FF81EB1D1BC5BD0F77CC1737837655BE23E0662E +AFDAB16BC0635F40DA516BEBA8128C414D6EB5F5AF5F98B8C0129606FCF536181E7A8ECA563BBFDF +0AC65F1932F1DF20DDD6739F7B1EFEFFE282FB6DF35222E8148FB5968BC7E40A582D7B146E374270 +D3D014849E898E91997288BE44220B6B6E431B7AE3F2F7C5BF3E3444F5088F9F42B7F676EA14671B +C70A84B77BC275E82516040F7B9DDC582C0FE335852A83C31BE3B3F716F17253AE57372D14951A2B +58F69C2DF7B93052823311E4208A72C10D0625869BC5F3808D231E86CD259824D7E6C7669013CC55 +B61E4C20C0041C35BBD7F1C291EE7A3CAE016A8C90C56F22C515375252FC3E188B80940816EA5117 +88A2FC7AEEEEDAB9E0A33F2C82D886F9BE029BFA1348DAD16874751460DC149CAB5189D061E7C804 +1939D80A03BB529E3580A235F8C37EE25C503BECB9C31CB91904BFF4460837A1165A7474C83B5773 +5945BE04F3FAC3381310E4BEF8D8D4A7343B75D23BEFC58060C48BCEB52566A021C197ADCE5FA025 +1AD31CF2F90CF6A72122C38FEEACE6BE36176B1A990DBC42A33F0BC0090799C2F6C8AE6990725562 +B07725B3DD15C9011205C28700DF82AE4F00F9842DDEA3BB5C15D3A4CDCD18E692427505D7B24CEB +40CD7AE0D81A4C83A0F9ED579F924FCB19D9D017E60C6500CC64572E0161EBA57EBC11A5932F24FE +9F1AF444B3C657AD162BD39682D23D6355EF5D84440A124138CEAC85C926BDF484AD7B976D9A44AC +6015C25354DCD72A889474F31B8BD3CB7371B95A9729FF0E46EA3644477AA4C11FF5913D917B7742 +065F0593482B6E6EEC9EE63633A6A963819F3E6A2920136B17C982353F1F071B3D585DD24B22DE9E +EFB2B34F776DA85F85E9E107D16960AD67046476ADEC509FCFC64E8AAA6427935FC231C817A21C71 +6DCCE088EA4186DFF0A3A7A1C367615B8E7462DA176C9F9EA893DD04E29DFBF88679991AAB79A172 +48C06E2BCF3B91C108E09331FB57D94BE85EDCC66DA8225FF4B38E12F8563398E62622EBD2EAB098 +691EDED4D5D7AFC360AD6F263C043DAF483DA05CF70DD8BA8F96D2A5A87043DFACFBBBB3F8A378E2 +A896897DD48D8B13888A023AE6AD778DE5FA991907E731E5C5B0A3DABDC360D77CC71C59C34506C6 +39C21BA9FF7A9CF322C21B457D62F20448BA19CB765C4C0AE7CAD9A972F60ED60A21C92AB665537F +EAC8738AF8A27D3946F759162731F62C0A657CED40C66B8A9941EC2559074279CE0F6E10BE3F44C1 +D517E10D85EDA6BB6D097F4DF55A3DB7D50679675A781FA1FFFD6F1A8349B2870C8114A05F5F7645 +3B38446D57ED63FF8731661F0FEA79033E4C8B5CFA29CEC43355780C5E2EE86CCD449577EBDC0140 +47AA5CEC980CEA8200867212DDDAC234BEFC9FDFFDB43DD32F44883EC6F2963D4D28171E19E144FE +1BE2B8FCE99016691A00E4EA594F94E973E899D14DC44486BA6B4278DCB292FA5C7E6D73068A3BA5 +1557C3F072547E7F2F869D4E9AC03514276EBDB0920FA04E67E2934A250B1A502A8D06A25037CE59 +920D0E136C02D5DDEE2EDBE31A38BA32C4122AF89F295ADDF579FBFC72391283DC1914E9322A63E9 +44A280ABE7CEFA54ED2EF42B79FA97EF21EE83EF20CE34850FB66C378EC7C08B2BAB924F58FB8123 +FEFB43A385BC1EB922AD8360750FC1B0D8A303AD19286308B7A39A5086A50A8FDF7D60188342A9F6 +42286540945790524174800F8C44ED71306ACC3437FB49D8FC43FBF8E88103A76B4A92D95DB9B45A +FA067E31EAEFE6BC818D11D7CB8566BAB418B596A7494FC7326AB3CC029D010917B305CE585B194B +C5415088BC7AC7852A6D52EABD223E2634DCB29080217F6755B023C591F08C7E5D72267664136639 +9766EDF511FA744675D473AB37BED0ABD92E04049AA9008014C5EEC1B0443C6C86302CF6A3C38BDC +B8D9E0538E349BD930707DFB002700B5EB427192AD6105E01B8C2FE488CF617E9EACE73EFE3BAFD0 +F87CB57E02AD31627E6439188006655D6B992B393F4858254C2106FE9A3F0EF7347347C66C94A999 +D49527A2A177EE20A08BF594FF1E08CB091D22A8C1569320EB5145FE4A2674151750620B5EEC822F +A48C5F8B565DE0E18AD05F99827FF24D3BE03B007774F25284844D0F1F8F95643646960F303A831D +4C2C4ED9E0664C1D705137C157671D62179B47FA4A6441DAE99C2F7C8765C4C931EF345CD8F92D11 +E290B2FDEB1B0FAB4BA661A4511F75768808AC1DF2FE79BC285B976D364ED25C64EDEE62E8E035A4 +B79344D55B1E7E2B43AF1CF94ABFC8D0CA5E89240EEA231464449B831F1B3F9EF01AD07B38F0B402 +712A0346892A1DBBBCDBCC827220CE7F492CD7471FAD35B43E71CA14F1F1A9CEA740C4E1A98337B1 +5EB97B10AD57DAA9E9ED134CBB4614321D548C6A71D8ED95900E7947A7E4331D7DF3EF367F6DE8F3 +113A7DDBEA0F741F9C189B8B586B83671475A492AAF9994D884FEF3A646ED4F272668DDDB05EA230 +399223AE63088D636AE6AC7CE2DA06CA6AADE9272FECE86D0EEA8290B927B17450DA6F34A3D566E2 +096300CD8D5A34139489228ECFEB104714FF907A6E1D3DE35BC0FCCF45A2781AFC5562CCDB627E06 +F23DDBEBD4212F36C332C4A5A9498032213DA7C3FD03FC4832D1F2AC9EFEE3B840BF8356A16E14CE +989C37E6234CAC7A215DAE7C4DE2E2B6D9A876F709422113B503556A4BBD0ADC107B6B639F1BAC9C +6FAC7F4092D23C04EB8684C9D0A5F184160CB660CF6E8672ABE1AFA596EA86890DD22D0A406B2118 +9BB626943F378227132475A25710B75E5B3BB2ED7DC0412A0A079E2AC311ED55AD8E7B2A1A55FC84 +E62D61398511B70877F3DDDFEE5D033A9ACB66899021951378EAFB9E9A799AC9686FBC2E9E9B3A51 +6DA9DECFDE87FA4BC042A5D26C2117E29AEE8840B18361B7B38B21493401E931B431EAB1A3371628 +EF13BE5A0C64A3B9F8B6A29D209884706D2A9AC85B86E3839706269366EC7E53F31BA4219F741E55 +21E42F0A10B7B29E839B924AD90861FDFE3A3D446D1E32C06E66A5D7187A63E590000D1356718648 +7C99BC31088356C9ED29BD80C61E442B81E15657AB191E90EE77A6804C762C45D3C1937EA17454CB +D76D781A0C96A0914B9DE3D3984BAF6075D3A25AC69BEB34CC413D26C39824B83F853DF864C269F6 +8443970933FEF93208BAE4DB3BBE90DD3CABF6EAAD2F6CE664CFA05999FB1CC406A3502DAA6C8145 +3C69CD218B18BF0B9654FC3637EBAB8007A2EEA6ABDD11FB338E89FEC84B344857804D028F849E44 +5982E294384A442390AD36D7C4182EDD7A05BE6744FCBD3BBE6FAF4796063CF42499F2DEC302AEA7 +B64FEF6DD74F278FB5C2897CFBB87FE03538B2739E25EDEB806142E0030F7413018EC1833840BF37 +269E21DDFF67B8059BDC83DE9C6461F3EC2BC2224D2585AFA2AABEFE7D3B2A899C3E08F00AF2A707 +55C145C6BFFAC8F96B54EE682C3C8CE01BB44EB574E1C721974F236E8A2AA28DF0A4AD285EC4DE58 +DBD3A2FAC8A20173AE84CBF877559ECCEC64D9448F8CA0FE5272BEA6543738D5EDD54B73AED3C4CD +172F91C4F70616B36D37B1252D355820DA88536C829B1542C1EF4F76375360E7123525744F55001E +71F5AB1E5B39A5248CCC6789647F1FAE5E989A8A8ECAA2A9116ED69DC9B9AC25A743F70AC4B62D72 +5F120D94431528EF8C74611F7529BE325AF84663989C219B70274584D1EC4485E11AEFFE4A29F534 +6912577C010FC753DB47204BFD9E504027B335EEBD952FD299E5562407234C5DBF0B839D1010AE10 +5D56766F87910E44AC7968842833A302882AB481FE0AD444911F7EB8555DBB9F3D062E9E9FF3B529 +4E9A3B3AED4AECE34C816BE7FBCDAEBA33FDC491C725F13801A6BE162F7F8E99491E8BF77F2D2010 +D9FDD78BA2606A28810CB5D88CE00218D49609C62E96BBD36110DD611C3573EA341FD2AF6FE0C932 +58565D4A6C88CDA3951E287168A478F5EE074BAA9676EF5911D8EDF8D66A7E8F3BED1BB786F480F4 +423C3D3CDED2E80AB76CF5CE5A53B092A44FE52806D8EA72033BD3E7BCF1DA9C870108F32D8987FC +25E7D873985C3C4882A5CDFE7A8B51F3754842FD6F1FF6235DBB88F04989BB2F2816A476B513C10F +AB6F81CF115EF5044D3366A4E192EF68D385B6B6F4BDA7D14D9627F40843035CA80DE49F9DB52CD8 +714F769DF97F73E3152E8DE34D8C37163D485750F8F4E37AE8A3C3BF54D97BC3B2978985B557F9E2 +9F0537CB743EBFA0B7864BBEA9C126F13C02CECFDA50A8019F90900F409B6D700CBB9EEE18FD1952 +D496DBFDBAC800FB83B37C705367F182D91B21C7F6C242D8735A75343C84DBFFE583337DE2A95890 +660584B513C5BEB7A0926BCA7B7DC3ED4D080CC3F1264A4215ACD35DCC62D896B4354F2F7254444A +7235E0E3D53D02583710DBFF2CD55AE5E61D25ED1B3C3B6708E5BB308A3D658F043C26B881C949C1 +0940AF6BDFD2DB0D544DE119BC7F7B03451E61FE18845000D350AD6D04A09D8E3E999E6DAC6AB73F +818A11EAD345EAED03BA083A6EEE7E9CA8CFB760FCBC88B8DBE0887F79AB430913604F15272F9C73 +DAD19D591B40CB7863414A8FAB21C41F80A4BB0A3AFD9D4B1322487429149470DE62F305906F1244 +2AE20521BE034F159A7E7EA211A2FC6193AF59CBDD4B43207BCDE8697DD515459F80F8EDB982C97C +05ED3996E03891DD7EEAD505F6A71A924CC1CEF29053ABC8F0B5F56D0DA1249F317406822C225863 +ECEED46BE0072EDDFDF5F63DC8E94FC119087A66E394A653D5AC774407B006B35C406E7EE4385565 +78290F8CB8B131B88BD78CB87A11CDA44C5A199BC71388A81F2F30E2C003094E793969673E8D0906 +1F4A3FAB9B14C52EC89BACA1C52703F000A967EEC445E5423D3BCB9253D91AA64BB26727C8461FA7 +FF61022B4C6A9E793901D3407487B4962A16B564CAC93D7AA28A22C28318F69770E12DF9D6CFFF17 +09EE0604890191217696AA52630231FE11153761310A72D60E6925AD6B9D63A66047F32B9425C91E +57505CFFE42A90185451297C2CAD408B0CA4F8E923EC26A3D5D66448550AFA3CE3BE9296C8149878 +F853F2A7C3B2E98899C8F9B47B1405F96D2B22E9F1CD62A2945AB62F67AB0297982809A829826BC0 +F24A7777508EE0A71BA7588663D8118E3BAC936A61FF4A628EE96C0B9AE05072A5A4307E68EF2C3E +97A46EB31A2A7D4A33CE9C44E1111D73D9D3C6A0F22F50D8C22153C2CBB5BE187C0F2F37708237D6 +FEBC445843CF88F4C3A249B39DC971CA003A78028F8A2CB3BFBD2C26CFE457A9561350AF93E60295 +D21E1C2024312B1F2F76F2EAEB822CA72412F860F5A87DF705C2F82E681AB9AF45A19023E02538B6 +9E0F273BB4811F07D153029BF988D3EB66358D987895B96B5EC4C24F3409C1EE1B5978B1EC8F3E2E +75FDF2BEDB0AF0CC66481FA98ADBA8DF4D8C8EB800F88C7776BACEE6F61095FC2CA891574F309E1C +0C27E37D8B03472DB3FB8D6668E286501118F7DADBF1106687AEC6DC4C162D22E3BD0BD42DBED782 +BA4707ED5181DAA9ACED58FB358DA0AA9B63AB4394D42360C3F5082A6F168CF41D381073A1D99CA3 +3BD97F62446D62059BF4A8616A8809147B739400362748EDCF39FFD2BF2C16D4632C06A5D43B48D5 +0D0B98E4449B4F1D39659DCCE8AC72A2E8F32487FF8686E550299F37A6353A8E558D4A8B074C2F5B +864DBC8FA3391E3B5135FC738E4C13868D67234489B6AE382792FD5214D5F9E5249AD65C433C22A4 +D63FA7A36A48A339F443AB39C23D2025741186C8B18AAE9952D41E25E930D6905B29F85A4589E9D3 +95FF040E3F72FBF29650D4DA2B6FA27F60DAC4169DC9764021F4E7E094FA25F6B5AF25602C593266 +ADED528EAE6C967E66F0BA05F258E34CF6AA5488D3F7406C2AA9D9FD9C533E827F26862D348B5223 +A690E40953F337352C8245F1861A19E5326490F2A9742917B5546884E8036DE0874363F812CF6FF9 +574DFD5BA77A1DD7C5778B2B9A2E22F2482744E2CE9323E590C60FF02412CC7AB0EC30D0EC857D27 +A4E3D8B35FD69FE28287B2160AC0BCF645A0654403D26B05041654B17D82928044BBAC40871BC3FD +D8EFC3207928BDE5D66926ED199017B223AAEBA563F2723AFFAB737F6482DD269F44ECCA3B32FA03 +FFCE3ED882B449BEE196F59E6616EF1A2F08B42B1A184727D5BD96DF83972BE1B5F8CF098F61B84A +B5BBCBF231E099CFC07D4748D43F129D123FD8051628564931E43A70BC09BF20AE2C0ABB009014AF +89753F91ACB574C5A218A47A02DA3AE44D3F688F9D96076AED9EDE7388B2935C01FD400BA7EC9574 +96E317C6931E3ED7078A53CB4CEE4E56311F4D8A368AE981606AD7E9DB0EA2A10E079476D9881596 +8C9675D9CF15E29B9328600FECCC02B484D8749B4154D69CF90997AD650D881735AA71289AE93FB5 +8C56686C1F9FB0E696CC0285A1A870373E5DCAC285A1B4FE903CC30B8ACAD2E4613873ED77D813D2 +9FA4984A7530F5B046A71011D97CDE4BAE9648AD54537A1D87A5AF1B92560DD7064F3EDD3CCFA7A4 +FBD6166D067945F103C13B3019540C3FE706804A7A00E1D28A0C26A40AC5A8845E39631A00099CE9 +88C3F12C8954EA84BF268D99E2E13726BF9C63474A5FC874CBF723BD48A5461177789E11B4F1CC63 +0FF4D60DE4F01422C1029E664782232A8BEFC38CD058135C79E015A55BA933AD8D446A81051D956E +429779863DBA60F4258DFC62D1DC3CC3862C5D310579417FEF4D7642ACBA8BA5284DC5833F581150 +ACF91709D6B3A41395960197AB43E63B8C6C2F745DEB4647E298341F3E3023D7DA22509340E28140 +97A0C193493711E60C5DA99C464D8DFC83A61291BD0E13AB6A42C81253C0F1B37E71E7706BF2D662 +76C60A3A7DECCC58E56524A200F8C6C0512FAD2FEB51F5C24109E284FADFFD8484A7A053E8EA544F +AFC1C7BA21E4824866090E542B8BB0BE603060A36D8A8CD64EC92D6037B438EA6A7F3A23E9608F29 +02E8DB10F2854D89E2CE6F093E6804299305BFF3D1601C2830F793B92CC2542A6CB0E9DA602E00FF +DEF234702D0DEFCD270F2984642F13B818F65BD407B61227A94AB11E7ACE8D40849808AB3A7E6EFE +B2D4F3B9FCD233B8497B35299DF28F651B0B2960B4545BF2C05952229EB1CF676DA761995413051E +885A529957538A8B2C6BBDCEDB2A3F4104004B880624C2F55B544EEE8DD29231386492598C2BE995 +6E7BA78FA75FAEBED43807FAF072839BAA02333D38BF4A59B1F3ACEA5C7BC188C0BE8A8BCF2069BB +E36BDB2203EAC19D249C4ACFBF8F9717111B7F4637D631D058EAE0CD4E4E8A9F0ACE1BE19B3A241C +9C6DCE2513690473D8F4A59E1A7FCC926885CC324086981FE0C6AADB8F48116822C59B1E93F53829 +77CC88B82FEEDD2B4CC6095691FFA1419D5A6850D3576C4E705C676850D0BCAB84D52791355DCC7D +70A7F5CF603B4D0D7F0CF4F25AEF44F21A2D627D45D306DE0BE1C794AB90DD1EECAB2D61A115D3D4 +AFAD1914E808B16BC868FD48208E1F915B0E8ECFD59AA5895CD7ACF95E0DCE87DD8B12F683C8EFD9 +625CD388337C262013626A06ED60556FA70A8D425D36B48E430758AB4CAD34A656F457A60E4DF933 +07C4D5B2698501E6D1F270CFF70E47DE38A5FCA2B04D7BEC3A2945EB93C49F1D366835581B42F43C +C99D71F13F45A9FF8D12E26C43E1B994BE5AB44E5309B0F936D78C93169D666DDE6D18E33A5E016F +32278897FD7E76BAFE39498C6A849F4A6D882D109C40B42488059554CC95530FBBAB2591DCFADED7 +3EE5F2F2BBF17F4A131B8126F9E0AEB4B379CEE4EAD92A1BB29E3789EC19671E77558B4DE961629F +28B49DB4D8FAAF541D23205844EAA801FEA468D26F32BF9CAC30BCA244246A55F600BFBB61C5E8C4 +10CA07319DD094770DFFC1CB700DF67097F61C46036353C8AB3A5E5198445A194BF189E20490E970 +7D2C03C1A003BC782A66841AE5DDEED2297BB6DD019C98A66A8F279748DA39C85CE2082D09210EAF +CA995591A1A3DCA52EFC9A752DAE0CFB125127DA2932AAAAD7E9850AD78D48304260B4C270EFF12B +160DEF2C2B8E30E8C137975AD20046F37A72F1355F31D878334D96313D330C24EE7350D4042AAE2F +A345EDCEB133121D4B39645B1D6114D0597C3301B4CA56DA2B4A457D7A51BD13B7AC61FE6E1CD451 +6253F606FD4B57E9F4895CAC93691EEBC2AC992CC5D122DD3FC6A9EC8FD337CD402F03F901CE7986 +2F4F4E4500617DA0F913E357BED3ED04F49FD61FD1C66606CB231C3B7A5042C7C07EAB2E02BE8CF6 +AEE5E16B4AB725B5FB5D01EAD4887E365BC2ACA579BD80E0AA686E4A08DFFC70F99132353E3D5898 +5205807271753BA3DB7264C4567DF5FE999514F28E1DD6D3E966D8810978B140F8DD9BB259078A47 +013BCF247C37F543C0899B532F34843CA56F18F688B42A12DDE2A90CA457860540B6FA138F753DAB +E7331188ED6F535480FBFE934F68EFB1C9C16D4F11EDB35F944EAE63751101928EDD0E7AFE64D7C8 +5E9CAFCBA88450DEF9122A245FC1ECCF9EF8DD94EE6A70CF16ECAB39B52597AB1E8C47B6DA4FF0DE +C7D0FABC84DDCC8C652DD7C941DB3FDCC5F0542A8F433AF9FDF4D393E123884E1DD5D359D46DAC61 +0694020BAE84B3BD4C068E3BC871BE21DA13571BB61387E207926769236776B5B31A4A462902966C +DC3D92BE171F10EF8395D2402F0C492A3FE55979CB903CFF2CD2319CE4B46481489E798A131635CB +2E70147193FE3C8F4570FD01BE10E004B17341C4DB8B029BCBBC45D31227A684E5F38F5D6F0821C3 +ED13D31DBCFF51BD759C84A98145FC86D82F871D2D83F43C3DD7FE9A064120338A7BC63A2C60667E +25B50EA1C267174B334F4437856295A6B826F54C3EA9ED39CA6909A0F6D9669F1E75A7A05CFBE7B4 +2C330668E311872177F0BE3A9B3EAE611EB48721AAC2F10C3CCD897CEA8A136E5E10C2337BC5EEB2 +FBC1A3646A6B6792CA3946BA5D4D135D929547042A2F0D0A202A4D86E3F7098C823E7AE4331CEC6B +607F6AA434180B4153F6B10DCC914A92D6D0934551BC9DDB3C78065B2177B264216AF5524D798AE6 +2A90D00A70CB36C5B9950499163C2C1B04339FA76D28E03A4D0C80FCF7BF29B8A188C67EAB6A4BE7 +8C07713C7EE09043301B5BFD60222DD0D0943180AFB286D2953A8A12661986A4812E2C0DE5B3E703 +DAB34AF0E9306A5711D286AD09A3C6AB80841491BD0E5A1D1ABE1D600CB494BC17EC4F74B45870F6 +AC41EFE16BB6C87F382DFA4B2B8DDC9C2E912FD139A2FFD5C92D836F3D749EEB985C62A376849751 +A6AC56B1F679A85FDAD9448DC7A4CFE323AFB540408547C53297DB6FE9A0B08901BC285997934A2A +1772090FBA175CF4660764B87A21A738519D5B619840D15F45DF3589E8B80A6DDF1F395B65345869 +58C7060DC131700DFF6E25962494583085E6F8BAEA557A5666E6634E4704D0C07BB0A2EB228A7BEF +EB890B4EEC638303B8005BCCE922CF3A7AE627206F2946A142B0095FF960BB8B8F9C975B6FF07479 +2D5C3DFA125B7BE7A8356D8B44E264AF6AA582DB84BA09D2FFDC2213903FE8FE16DE5EF61E518DE0 +6A29D98E217038A4CA4D219E4F114858CA493AEF0CD6495A7C5EF1ADA06AB543051B1A5213952D46 +648BE06D15B1728768BB853CE32943AB0988D172227780CE82D1A1D297D0D6ECC51B290E156645B6 +9BD54699940AB17EBE10EDF258BBA6BFEA39F4F0B066FF6B3FA16C7C72F2565CC028F249BAA4B488 +B48A2513DCC5D1E205FB874BCFE45612DF4EDAF815CDD53CBB80842B429B1AFA32D35EA58E17F4E7 +252C2C9A737AAECFC25FD8CA5520D3EA38AF71C61B88F31FB53A7F5369305D63495ADABA455C3C4C +35D9FE423A00CFBB278CD482D3FD33BEEAC1F359AD9B6AA17D60CC46FAB670ABFB3B2B4161B9EB9D +949A06CC3B734F63DE821FE0B8EA065B6F79C0601E46E8CEB6E2DE0C052771E0EC7012063F1AC46C +3FC454767469225AA266784DD77256C6FDE25D7D857FCEF2562AEBE38A9A47063AEF91449723E680 +B36D824BB95BE95C9802F7AD4DF1AF37B4777A1E116A7EEF770F0A499BC9F9675A775503091D4EBD +F55118782E3E54AEBC67A0545E8D75BA5A482C6CEC50595D45AF041664689AA4338C1428870CDC15 +BFEEF788C1D1E87B3389103225E2619120B129FDF048DF3F5DDAFF1CE87428E6BFA591B91D82720C +E4A72FBB952403435248657ECD5456CA814FA0ECBE70BC3D391AEA0A195AE8FBE92D054AD0F3E549 +841ACDCE59DCAA9DAC20348A1D05DF7B4127C75F4C6607EF8501F741CBC96AD8D429A4CA8034CA52 +9AB673EE706F1FF51DD93F45802031EEBF8F3AD29D2A74888B1CB344F88ACEB9B4DEA13ECAE0A3B7 +1FF5CAA38D0DC484B96E90251D56732B85DB8A8DBF10E0A5921121CFDFDB6EA9CA45B98584401C3F +346BD026DA2A17E75CEEB74E48667F9BF5C538623E88021F0C24EC7D082C3E1FFDE5A6F68F0F3F6D +2B61547C4C14614118F7942851B090D6C8D80BA06D9139CB5AEB0EF77DEF2376C0BF602FB1083178 +27E86DDCC00622438FD7A268C0252CA2E20DA887A9D5ED251FD5CCE811E514D702206E413DEE7528 +3A6AEF793F5E3E3B0B10E59226102DF2A7347A95B96E39C6B6ABD1FB4B7CDC0813D7390E2819EE50 +7E5458E06D43CC3BF16E40CEC4E64909615CBFCD233B5D316BC8B8B46243CDB7F2FBECA2F208D0A7 +985048E88A5E685A9EE8C3A351FFCFB522C1EE41A8E4EF0A080C0D110D2A0D8A980AF1F604D1CD52 +3CF41D08A45EB809AF11F530C138F256FFD49BC20F77005D004AC85FD563E8E8BA5F79CC60FDE714 +1416F4C14FFF281ECE2BE1D167B7B1ECE17790454F795B056CE4DF1D08F25F0E5E2A16CFA066AF97 +F3A80015068FA2A1D65F329A5CF7856114A77EE7295C7929CA27796D8C51B31347CBC7C4EEF1E75D +12237C558BE5B65DCAA4CDDCB419E543B1785DF8B225C2E9778839123E6F85E53D925C6D2FC0327A +EF9CA13D1F2F9B3E510A1A4190D3057227621788AFB6567132238BE86559BCA006B85AFBB8AFE5D7 +A8CE58AF731F2550108C6B8CD63632184B1E218F01881CB94CEBFFC310F263565A39C83C874AE474 +AA213F7DD9CB3B8446A3E47E81BACF7B16E4A80899BF2D30C28F9DE0678DF364589DA6A454398E30 +21A92AD1D8B0DC494D96A06C7568E34547F0E68829E5D15F6500ECD7403B40B5CC2A479D0F8CE3E8 +709861608AED046339BE055BAED1A6BD2311CF918E74378D893710D42A769671326E947F108A38C2 +3F92EF5C6A50EFE36E858671F17151C719B9C7D60E8E429F088616D616080DC676760BE83A3F5229 +6C4B13CA3818E3FEB56D51DB5979F28063F7D537C15493D57CAA1E55438C01C794336D9F21520484 +FE5B5BAEAD7121E3CCC5086E2F2191DBEE9527CA61CE85CC0A4D99EA29169B17A10B3D4372EA493D +48CF572E570CF48B3B7512268A9AC4CC161F97695D19412C36F1D67522E272B1C18B6122A355B43C +37636E71462503C43C1AEF5ACE34A6442075B59892BA54E9B5A99B9733BAF64161450F8ECF4FB2B2 +DA6F9AAEBC827CD9AEF94BB463935C26E1DD5DDCA5BA87E0EFB62D9D3226DC28427EC097B68C9EE1 +E47D125F884F8656C8AD55D625A8F7F1A306383A5036A12E63B6FAC4A215AC88725611EBA74B6770 +B20F45356879DB807E9C9105D14C83104B2DCB4F274A2F37152425935B3F2D1633E4444540F7411B +24C17055850C9528D912F9DD40984CA1B3BE277F82198BC99B80233B8AAB674A44B42B9123E886B7 +B3161D48B4AA24970BB75A983569690BFD4B92E6ECB410DDCA14A3BF59057397FF1AF159BE230216 +FF2F7009C3A3DDD81F2BD1F5C10C2C2F30A8095D7371427AC9D527AC6794CF8593303F786F21CCBE +36DFBE01B0ADD78DB3C9483D9DB09A9E2577A3A4296E7BD09C41F5C91B29C9D7E69EF06B5BDB66E0 +853C3DB133769BC8858121A350CADC1D61C77C1A313833D077938AB2E10EF970590256479B1549B0 +401A9C5E97F12A269A305B372F0EFC035B06AE3E2187D6E3A62DE3B4E8075F0ECF5BE32A5B97A4AC +23A7335C4AB29917129C08250DBD1D5BC123C92E821BBB1A7598E32DDDF1876C6FBB7B858FD317D7 +6022955595338AAD2BFB3B17DA11380E7473A5F566C6329E0F38D9A67E68FD4B8CFCC82FC0BD7BA3 +9A25D5BD0952CC71CF0BD7B5EE184A8E62FBB366D95AB051BC3A80E9F6C27EC100E00C4A002291B0 +4D758A9683657FCB07A2AA8B07DF585CD4205B4DC55330E8A2A367CBE966E84618ACFCAAF81C8A9D +3A3165FE5A623720A0C11CBDD5CA200100DD2C970A09B124D98987D6A2CA7CBB40DA5B585B86FCB3 +8E96F54C64A921D7A5D2F805C67339DC4FC026FB2EE533BB9700F66F4AD58949B3DB75D770C4D7C5 +C23DC720D78124A7ABF30CDFF10C708219903431C7B1A19317341CEA22AA11A588F8AF9BFA4AAC40 +31EF22F13BA597BD1E5958CEA741274197113CB0B7975424C17F73733ED30BDC0170CD0084BB7DC1 +8F031BEF2A6C44D0EA0EBB84E31D5B4C50B63DF0AF96AAF33E3C986D4B82D161E2F9049DE9710EF8 +A11BA0E08266A3547F20567E582EDB47633772D37FCCD119AF5B11F338098F185D5B055975F56092 +CC6979FF49487D69E08A4E8754FD51D29410F56E0F3EFB9C3B86253B3B0A39E9D217604F664CC696 +634D216524F976D066034DDE8EDF5E8F9644337C50C7D8E2230B38E0EE88C53DEEDB3363477E32CB +88B3ACB6931B9717FC93F8D1A9DDDA3A7D8A56783F2B651A1DE216493C34DCD17A7635D21CEC0770 +751424B599B00CE950E60D8A8B3444F1629220DF073137E2ADE1B6243C1C69BD5E10450B948BFCBA +46A841B98BCE7C86E120789C73C87E581868021E68F78CB903AA6D494331174CBF944221DEA9E567 +A2542BD399C5E471D972FAB7E8950AEFF3D91A1296E685B0BFF91367C2703DCAB10AB236DE603E3E +993C4C9020EEBFE89726AB9FB92AF9D371D7188448CAB9FD2704506C39F0842E36EB8B9AAFB1F26F +CD366E66725AD28B19FA57537757EC71B04B451F056C3AEBFA04D1C0CABC9492B6B8D239EEC36776 +D6B515ED43A66AB4342BE4C8B2027F2D008EA231A24B472DA8352A05DDEE31CBC3577E5814DD2D4E +C17A216A7C8FFB27012346C9ED12F7819B96A5B135B6196E888C9AC73D7D4B7DC370E2FBFA17FD74 +0BF516CFF69294493900BFD63721A537BEFB31C5262567103F9CEA4E8DB02964B983E1AF190ECCEF +41F552C45E9B94E29AE3F129687EC35719F591C1987A08DCED3B822344DF81A70AE78E14A81EF1A1 +A77FD5D19F7D35B7C12A473EE11C655E15DC5D3C94F226CBDA85377338BFCEA18359176CA7EED622 +84F9015F2D592E27F1037C95570CFDCA3B9B90D35DB8C341434BBF04C0E692D4C2C3F59EF386CD1C +5A8C783198EBE42C89E3B64C662F7875325982E9299C18DBD1FD2257DD964F9F9DDEEBFE56E4AE9E +8163E8C58581BFD5818BF396CCDE3A66F58E17BDD262D63E5D9AE0D0E43C304CCAC4776B87D40F6B +5AD7E625B2065FAABA81AAF2E2D32E0ACAB12DB2A9FE9C6160FBA7DE5AF019810AD9C64B2E6567B0 +F0108E8EDEC635F4BA88BDEBD3561CDBBB9063B4D19C493E0CA4F551255A7DF6BCE96C17A5DD4877 +7F654F4F114CF29ECDB9779F63EECDC04E1FA06E48701547A8F96483B4195C0DE90B0FD1B95B5F12 +5C43BB973D0B6414A994595359289B5FFEE4A08D684C2E7E3F417585581318D54DF2EEAE7CF36E2C +A1A70EA76154B88C1E4FFDF5EBC28ABE264B40583F2DE1F761DC25DF242396AE502CC63EFD6EE284 +DB37B0DF36A8833ED3EA5A1DB372C8E1F3203E6E945A53D83B87250CD72C851F68A602E7BCF92DDC +E600D518069F8900F3BCA434D21694229572D55F307E0FAFA3DAEA864BAA19F227097963CE83882D +22FB7F1AA3FE558A3669BB7FA37C0A591B4BF3E2A2FF85BFEDE5E424E0AE337EABDFD67BBF38E160 +09A687D2E7C1882DB79304D722EACA233EFA489B10E8D4D404D12A5A4683FDE377A9A952CCAB257E +ABB408D75FF130206DC3447F26E74D1D00354313E340878917177DBD2AA5A2A70E2087D551DA5181 +6FAD07F6E826DB0BF21560BB26906D3100E12AA3801D4A8BDCBF1A0A8D58F1054EE2A0DB04ADDF3F +EAF1FD6E322BBEE274A4995EFFEAAD0F24D9FB45730F6BA0F42D88BE4AEBE9777F8AF508DED61024 +EF955B26ACEC51AF5C21BAD7AD93CC0C9DBE03C0FE9637A3099E5EF329051C87FFF70042D788F21F +4DE645FEBF58374F7E38A9AFE3DE4D2888DD807A09169DA8516DFE37591C122A4798343C1A8121C7 +430F244EE5B19A21897A4423E21CE0492E75C9320E37BD65F1EACA7FC6FE032842E4D985E666E633 +AE5CC62B3449404672B284EA5C6A01E927787104ECCE1354D1C0E5AA2452B7B12937B946B74EFA98 +8C5C79EEE5ACDCFC994CAA853AFA08EB9E180C5E898FDDF8903EB0863E98A4FA537207C154CE5B98 +EF4E97FB6A1CBB07C45D34221EED26998EC864475D231FF76E830A7C0900C4B5FCB980E3F67F4EAE +3DD8086CFA1874E65C424646C7A8F84272DAB04A5984FCE3F4B89380B3A5C6F04EA3B683CC224913 +9209136336AC9349AA89690B5DE0D93E92F996BA7E346B043EBD45A35FA297DB70E9A6250B138674 +FDDFA609BE11EBA8106EEE3923B4E2657CF7A82443C25022A5DED862E8E3A5BFA7B8AC1C1B804B23 +C08A7D8FE594140E8432C1A5E183FE361B6B8C763054458798B756A6CD2535FF4667C634DF78AFBC +C87D40DCD56FFFA12CFC6EDC6A8AF41AF657E0137320624F1895D9AA84AABAC5F3653E977B054C52 +92B06FEAAAC0E5C7FD45B6654D0D9CCDF292A8489E1E6CD929F0AF25B9604CE33BD25662133CEE99 +CDBBD00F865431193767480700CA019D694B38D695CC29F732B5B39B6CE654F1AA18AC3223B60E55 +3FC3C3E114E6A2E59617BFD58AD8E429200B59E035DB7D67C8DC6C72001D005924670ECF7A06FF5F +7B3DD5556DDDE1BE5163F7CD6FC969033EBBAD85856522ECA91F2A200A75BDDA69070240D86C2D7D +959F1DBE45D488A96F308269E8262327A410D057446F596418DF4A12267A7A3FF5ADEA0846896D3A +EFE3D5C72387F7EEEE809BCCD23D1126B86C45A0F66404FAF617D03379A2E44865051E92B4C835F7 +21492147DE3DCCE47EAD192D3F10A5DD459634D6C253F4D09DD98E7DDC836EED8DE08A78A6F6FB93 +84565CF6DA8D4C948144272D5F8787643B9B550F96E42A8CFC0F1C1F2B14D83201199E9E1F5BC0E6 +51E39E16A744930F4409C61CDF9F456C7EAC5C62BD22CE0DDCCDF3755DC670FDC8FEBF09F241BF95 +1A1694BD70B9695A15653F719DF4374AFC6C27F58DD144253BDF86FC1BB3D4FEAEEB9196BCEC7168 +AF1488AB7072751FA24C6642B87DCADF4B2427631E7D7FA39D126410189CD8E24CF0A490FDC7045A +A9A83D02CAB57B9C27211FE1805080FBCEF23B86CDB19885A574C589BDB15C4CB3651FA3D2D6B619 +4449B16D927047E376A84F9423562CD548D192AD0A57E0D5B41010D1C7D87929DB6F456B0A073DF9 +A8220BEB3D4DA01518B5C85C82ABCC821BEEC1965283DABF6AF3AC55ABDDAEF1B53E6119DBB4A114 +44FB1769F9891B5DC931ADFEC8ECEFFA179427FCB08C1B149EDA70717C5E9322F3CE9405438481F3 +90968A38061EDAA2223147D6143877E4C4E645EC0A9803A933116D610491FDC639DF69B418156772 +E14A428864982D3230EFFAA1D2D3388B0F70D8FE202CDD6CB0E97B3290ECCF4A1A69D93C11D3D735 +FF0B5A9A161A86CA4F16B7C231B8BF71D5083697B2F24C0BDAA6E83E5DD2222DA248D4ED3B5509CC +7CA40D8D5A2507A1D40FC1AE9486343CA2B2C65B6CED9B1F4C0535ADF7BECCE45E1DB74C7417B06D +897C6ECDD05F068857D931B3D960103628675BE2FB82DA774ED3786670D8C2BA4ECC36C203C7F7E0 +9C97A09E3B2E2C3EC30B53A8825519B664D09F149AB73F183A4A22C8F20E2052F76649AB881BA28C +6C3C3950EA9B3961F87F6AEFF24DD7B4E10DE28C15DCB27F593F98CBC9CDE19360C5531B2A0C6DC1 +8FAE2832AF49BF9522C068F3036516F711AADEE496EE4E3EFA2F55EA4891DEC6D8A868E0B0017076 +3040CA4D42A73A71057DFE315FF8625BBD74F41E5CA77B7DF096C2F4F0D51B1184CA9F2C8517FCB3 +956C44873D630E94EAA1D2D1451B332770172B2D7A21ECAC864C612235C0B39ADF55EF074F2073FA +D3B54A66B07AEA1FB495A9F7269BD1C07692B0B762DA6881EE6B265ACED0BF0794C0397F8D8B13FF +275BE77561067E2C1FA536131184682BFD32384C6488012D29E8E55838E5A5AA7C40C20AB6B03BFD +BF4EFD2001A612100D585BAC1C77C1CB1ADAF2B000E09686091AB7D1AB6ADE395A03580BD78E961D +14D052E7D4A61227534B55FACAFC4E8F9326DE35BA53A463F7D94B705698300771185DB19E78FABC +D0AE4FCE005B79C795B692F2D9C00B6A61D4B343C35E417BBA169EE82A2E4AE204A126B08A94191C +6E5B5E8328A147BD6ED5A5AD5C9143A50C47789DCFA699720336AFFBD6B1646D8C35139B0DC34340 +C76C7E4FD72DABF80BF64BA04742D07B380E20A678EBCB19057F2346FEC653F1302992279DCCD2E8 +902B2B8F78435AF3400DAE319E94E3148BA88C056701D524DD67FF368C85EC6366F31689A62FFC03 +4BE436588ECBB8B41E8C43112F3B65F50D20A5EC51A26FE899E74731B01C7771F75B76070DC0B223 +1845BE9C09670E65C4DF54C0FB36511B735251AEEBD13FDC0FCFC3134D8DA826E4100521CFEAB202 +B83267EB3F69AEEA25FDE1E9C86407E38AB4CA2D1B91607EFC96DDC5BB10FCD46DEAA5FFED196959 +12302ED55DFACFDC6F22C1528AE3A9902B75F46A489352C87B0F5056FD149DDE67219A3C3D1DD875 +BAD81AD443E0F90DC79D979E10B4262DD20B255B04BBBE5C988B23667135CC61D988928F5F07753A +C76807203942F6B62A178A1E5CBBFBBAEBE735D09B747BF00C360F2E2B6C9F2261F7DB018EDF40B1 +3DB4B7962D4D2F858E1FA6376F21ED9CEA4BEE07FFE27F136BF60352AFB71CDBFF7E65F6B4624E16 +C6E5AB7FF60B4711A178FEE691DD8D362B14175A69070FBC5B9EB15248E4DFDA3C9453335D0E1EB2 +7714E4624C7215A73B53402B02CF7AA05A14CE1E5AE161B75F218BF0EE1215D3CD925D5ACB7DBD2E +BD8E4405433F432C491999794C814D5B384B7A1C0AB28A11CBD0A6EA12678508CF86232309B0BBF7 +EEA929FB7951F5A6B367C5AC57DD7D3B3C1E2188DD4664D14FBC32B1A1EB53D7EE7F55F4D5C2D014 +528EBB7F0E595F7618721451EE6B83EB240A6C4D33377893D4EF542F47EB2845A09759D5554C74DC +5E9109FFC8C929CF1AC446D8149957720EE4FB670D3CA61378549DB992126B23618CF49361D6D4B1 +C73C3D37E4A4465ABB349CFA34E9351D1192E366267EDF02DE432ABC80792B0CFD41FFD0AAA42E63 +3F5B2177A351D33477C636CA573CB02C07F7F7A41C9F1BC4C112BD6459DD130757D2BD6F47495C3F +92E99522871DAC2865 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +cleartomark{restore}if + +%%EndFont +%%EndResource +9.72 /Mathematica2 Msf +0 9 m +(\310) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +2.25 9 m +(q) N +9.72 /Mathematica2 Msf +7.5 9 m +(\310) N +9.72 /Mathematica1 Msf +12 9 m +(=) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +p +20.25 9 m +(0) N +P +p +24.75 9 m +(.) N +P +p +27 9 m +(0) N +P +p +31.5 9 m +(2) N +P +P +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +P +P +[1 0 0 1 -118.485 -136.705 ] concat +1 w +[ ] 0 setdash +P +P +p +np 181 96 m +181 112 L +219 112 L +219 96 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 181.505 96.904 ] concat +1 w +[ ] 0 setdash +p +np -1.505 -1.904 m +-1.505 16.096 L +38.495 16.096 L +38.495 -1.904 L +cp +clip np +p +np 0.495 0.0956 m +0.495 13.096 L +37.495 13.096 L +37.495 0.0956 L +cp +clip np +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +p +np -1.505 -0.904 m +-1.505 15.096 L +38.495 15.096 L +38.495 -0.904 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +9.72 /Mathematica2 Msf +0 9 m +(\310) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +2.25 9 m +(q) N +9.72 /Mathematica2 Msf +7.5 9 m +(\310) N +9.72 /Mathematica1 Msf +12 9 m +(=) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +p +20.25 9 m +(0) N +P +p +24.75 9 m +(.) N +P +p +27 9 m +(0) N +P +p +31.5 9 m +(5) N +P +P +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +P +P +[1 0 0 1 -181.505 -96.904 ] concat +1 w +[ ] 0 setdash +P +P +p +np 267 3 m +267 19 L +305 19 L +305 3 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 267.864 4.035 ] concat +1 w +[ ] 0 setdash +p +np -1.864 -2.035 m +-1.864 15.965 L +38.136 15.965 L +38.136 -2.035 L +cp +clip np +p +np 0.136 -0.0353 m +0.136 12.965 L +36.136 12.965 L +36.136 -0.0353 L +cp +clip np +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +p +np -1.864 -1.035 m +-1.864 14.965 L +38.136 14.965 L +38.136 -1.035 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +9.72 /Mathematica2 Msf +0 9 m +(\310) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +2.25 9 m +(q) N +9.72 /Mathematica2 Msf +7.5 9 m +(\310) N +9.72 /Mathematica1 Msf +12 9 m +(=) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +p +20.25 9 m +(0) N +P +p +24.75 9 m +(.) N +P +p +27 9 m +(0) N +P +p +31.5 9 m +(7) N +P +P +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +P +P +[1 0 0 1 -267.864 -4.035 ] concat +1 w +[ ] 0 setdash +P +P +p +np 295 86 m +295 102 L +328 102 L +328 86 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 295.617 86.954 ] concat +1 w +[ ] 0 setdash +p +np -1.617 -1.954 m +-1.617 16.046 L +33.383 16.046 L +33.383 -1.954 L +cp +clip np +p +np 0.383 0.0458 m +0.383 13.046 L +32.383 13.046 L +32.383 0.0458 L +cp +clip np +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +p +np -1.617 -0.954 m +-1.617 15.046 L +33.383 15.046 L +33.383 -0.954 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +9.72 /Mathematica2 Msf +0 9 m +(\310) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +2.25 9 m +(q) N +9.72 /Mathematica2 Msf +7.5 9 m +(\310) N +9.72 /Mathematica1 Msf +12 9 m +(=) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +p +20.25 9 m +(0) N +P +p +24.75 9 m +(.) N +P +p +27 9 m +(1) N +P +P +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +P +P +[1 0 0 1 -295.617 -86.954 ] concat +1 w +[ ] 0 setdash +P +P +P +P +P +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +%Trailer +%EOF diff --git a/services/clsi/test/acceptance/fixtures/examples/latex_compiler/main.tex b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/main.tex new file mode 100644 index 0000000000..97b6888628 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/main.tex @@ -0,0 +1,9 @@ +\documentclass{article} + +\usepackage{graphicx} + +\begin{document} + +\includegraphics[width=\textwidth]{image.eps} + +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/latex_compiler/options.json b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/options.json new file mode 100644 index 0000000000..a280541cfe --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/options.json @@ -0,0 +1,3 @@ +{ + "compiler": "latex" +} diff --git a/services/clsi/test/acceptance/fixtures/examples/latex_compiler/output.dvi b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/output.dvi new file mode 100644 index 0000000000000000000000000000000000000000..84888d7dd621975b78a51a6141c58cd6f26b6138 GIT binary patch literal 272 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 Xw81FT8>v9UaTXhe92~@6Q=$I=GIJf; literal 0 HcmV?d00001 diff --git a/services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/main.tex b/services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/main.tex new file mode 100644 index 0000000000..28da61efd3 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/main.tex @@ -0,0 +1,8 @@ +\documentclass{article} +\usepackage{luacode} + +\begin{document} +\begin{luacode} +tex.print("Hello world") +\end{luacode} +\end{document} \ No newline at end of file diff --git a/services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/options.json b/services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/options.json new file mode 100644 index 0000000000..96a05433b3 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/options.json @@ -0,0 +1,3 @@ +{ + "compiler": "lualatex" +} diff --git a/services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/output.pdf b/services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/output.pdf new file mode 100644 index 0000000000000000000000000000000000000000..38ce5418e0b8df9a51b75fd412138a058e8d7be3 GIT binary patch literal 11405 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
Js=$eE+)>@?2OcNc9i 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%0A~fxUlF**<4$0I`4V_$Xs1z=} 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

H1M%;!e)R)rUec-VjM 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

yMq~1l29T_~N#6 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 I<;9@>2e6av-2eap literal 0 HcmV?d00001 diff --git a/services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/bibliography.bib b/services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/bibliography.bib new file mode 100644 index 0000000000..5654dbd22f --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/bibliography.bib @@ -0,0 +1,10 @@ +@book{DouglasAdams, + title={The Hitchhiker's Guide to the Galaxy}, + author={Adams, Douglas}, + isbn={9781417642595}, + url={http://books.google.com/books?id=W-xMPgAACAAJ}, + year={1995}, + publisher={San Val} +} + + diff --git a/services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/chapter1.tex b/services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/chapter1.tex new file mode 100644 index 0000000000..3056c071e2 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/chapter1.tex @@ -0,0 +1 @@ +This is chapter1.tex, included from main.tex diff --git a/services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/image.png b/services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/image.png new file mode 100644 index 0000000000000000000000000000000000000000..8660218b7b15437ae0070f86952cf8432aa34a08 GIT binary patch literal 10601 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

*g;pkkZuBpseReQszk`rU`em5zs2J&;R*HCT%q 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%0A;uh@wvhoQ%H9%#Q7Zut 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%0D&Bkr=phw&p!L0 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 Du6Z#s literal 0 HcmV?d00001 diff --git a/services/clsi/test/smoke/coffee/SmokeTests.coffee b/services/clsi/test/smoke/coffee/SmokeTests.coffee new file mode 100644 index 0000000000..4cc18eadb1 --- /dev/null +++ b/services/clsi/test/smoke/coffee/SmokeTests.coffee @@ -0,0 +1,35 @@ +chai = require("chai") +chai.should() +expect = chai.expect +request = require "request" +Settings = require "../../../app/js/Settings" + +buildUrl = (path) -> "http://localhost:#{Settings.listen.port}/#{path}" + +describe "Running a compile", -> + before (done) -> + request.post { + url: buildUrl("project/smoketest/compile") + json: + compile: + resources: [ + path: "main.tex" + content: """ + \\documentclass{article} + \\begin{document} + Hello world + \\end{document} + """ + ] + }, (@error, @response, @body) => + done() + + it "should return the pdf", -> + for file in @body.compile.outputFiles + return if file.type == "pdf" + throw new Error("no pdf returned") + + it "should return the log", -> + for file in @body.compile.outputFiles + return if file.type == "log" + throw new Error("no log returned") diff --git a/services/clsi/test/smoke/js/SmokeTests.js b/services/clsi/test/smoke/js/SmokeTests.js new file mode 100644 index 0000000000..c364c88406 --- /dev/null +++ b/services/clsi/test/smoke/js/SmokeTests.js @@ -0,0 +1,64 @@ +(function() { + var Settings, buildUrl, chai, expect, request; + + chai = require("chai"); + + chai.should(); + + expect = chai.expect; + + request = require("request"); + + Settings = require("../../../app/js/Settings"); + + buildUrl = function(path) { + return "http://localhost:" + Settings.listen.port + "/" + path; + }; + + describe("Running a compile", function() { + before(function(done) { + var _this = this; + return request.post({ + url: buildUrl("project/smoketest/compile"), + json: { + compile: { + resources: [ + { + path: "main.tex", + content: "\\documentclass{article}\n\\begin{document}\nHello world\n\\end{document}" + } + ] + } + } + }, function(error, response, body) { + _this.error = error; + _this.response = response; + _this.body = body; + return done(); + }); + }); + it("should return the pdf", function() { + var file, _i, _len, _ref; + _ref = this.body.compile.outputFiles; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.type === "pdf") { + return; + } + } + throw new Error("no pdf returned"); + }); + return it("should return the log", function() { + var file, _i, _len, _ref; + _ref = this.body.compile.outputFiles; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.type === "log") { + return; + } + } + throw new Error("no log returned"); + }); + }); + +}).call(this); diff --git a/services/clsi/test/unit/coffee/CompileControllerTests.coffee b/services/clsi/test/unit/coffee/CompileControllerTests.coffee new file mode 100644 index 0000000000..6e14b42e0a --- /dev/null +++ b/services/clsi/test/unit/coffee/CompileControllerTests.coffee @@ -0,0 +1,92 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +require('chai').should() +modulePath = require('path').join __dirname, '../../../app/js/CompileController' +tk = require("timekeeper") + +describe "CompileController", -> + beforeEach -> + @CompileController = SandboxedModule.require modulePath, requires: + "./CompileManager": @CompileManager = {} + "./RequestParser": @RequestParser = {} + "settings-sharelatex": @Settings = + apis: + clsi: + url: "http://clsi.example.com" + "./ProjectPersistenceManager": @ProjectPersistenceManager = {} + "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } + @Settings.externalUrl = "http://www.example.com" + @req = {} + @res = {} + + describe "compile", -> + beforeEach -> + @req.body = { + compile: "mock-body" + } + @req.params = + project_id: @project_id = "project-id-123" + @request = { + compile: "mock-parsed-request" + } + @request_with_project_id = + compile: @request.compile + project_id: @project_id + @output_files = [{ + path: "output.pdf" + type: "pdf" + }, { + path: "output.log" + type: "log" + }] + @RequestParser.parse = sinon.stub().callsArgWith(1, null, @request) + @ProjectPersistenceManager.markProjectAsJustAccessed = sinon.stub().callsArg(1) + @res.send = sinon.stub() + + describe "successfully", -> + beforeEach -> + @CompileManager.doCompile = sinon.stub().callsArgWith(1, null, @output_files) + @CompileController.compile @req, @res + + it "should parse the request", -> + @RequestParser.parse + .calledWith(@req.body) + .should.equal true + + it "should run the compile for the specified project", -> + @CompileManager.doCompile + .calledWith(@request_with_project_id) + .should.equal true + + it "should mark the project as accessed", -> + @ProjectPersistenceManager.markProjectAsJustAccessed + .calledWith(@project_id) + .should.equal true + + it "should return the JSON response", -> + @res.send + .calledWith(JSON.stringify + compile: + status: "success" + error: null + outputFiles: @output_files.map (file) => + url: "#{@Settings.apis.clsi.url}/project/#{@project_id}/output/#{file.path}" + type: file.type + ) + .should.equal true + + describe "with an error", -> + beforeEach -> + @CompileManager.doCompile = sinon.stub().callsArgWith(1, new Error(@message = "error message"), null) + @CompileController.compile @req, @res + + it "should return the JSON response with the error", -> + @res.send + .calledWith(JSON.stringify + compile: + status: "failure" + error: @message + outputFiles: [] + ) + .should.equal true + diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee new file mode 100644 index 0000000000..098e29606d --- /dev/null +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -0,0 +1,73 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +require('chai').should() +modulePath = require('path').join __dirname, '../../../app/js/CompileManager' +tk = require("timekeeper") + +describe "CompileManager", -> + beforeEach -> + @CompileManager = SandboxedModule.require modulePath, requires: + "./LatexRunner": @LatexRunner = {} + "./ResourceWriter": @ResourceWriter = {} + "./OutputFileFinder": @OutputFileFinder = {} + "settings-sharelatex": @Settings = { path: compilesDir: "/compiles/dir" } + "logger-sharelatex": @logger = { log: sinon.stub() } + "rimraf": @rimraf = sinon.stub().callsArg(1) + @callback = sinon.stub() + + describe "doCompile", -> + beforeEach -> + @output_files = [{ + path: "output.log" + type: "log" + }, { + path: "output.pdf" + type: "pdf" + }] + @request = + resources: @resources = "mock-resources" + rootResourcePath: @rootResourcePath = "main.tex" + project_id: @project_id = "project-id-123" + compiler: @compiler = "pdflatex" + timeout: @timeout = 42000 + @Settings.compileDir = "compiles" + @compileDir = "#{@Settings.path.compilesDir}/#{@project_id}" + @ResourceWriter.syncResourcesToDisk = sinon.stub().callsArg(3) + @LatexRunner.runLatex = sinon.stub().callsArg(2) + @OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, @output_files) + @CompileManager.doCompile @request, @callback + + it "should write the resources to disk", -> + @ResourceWriter.syncResourcesToDisk + .calledWith(@project_id, @resources, @compileDir) + .should.equal true + + it "should run LaTeX", -> + @LatexRunner.runLatex + .calledWith(@project_id, { + directory: @compileDir + mainFile: @rootResourcePath + compiler: @compiler + timeout: @timeout + }) + .should.equal true + + it "should find the output files", -> + @OutputFileFinder.findOutputFiles + .calledWith(@resources, @compileDir) + .should.equal true + + it "should return the output files", -> + @callback.calledWith(null, @output_files).should.equal true + + describe "clearProject", -> + beforeEach -> + @Settings.compileDir = "compiles" + @CompileManager.clearProject @project_id, @callback + + it "should remove the project directory", -> + @rimraf.calledWith("#{@Settings.compileDir}/#{@project_id}") + .should.equal true + + it "should call the callback", -> + @callback.called.should.equal true diff --git a/services/clsi/test/unit/coffee/LatexRunnerTests.coffee b/services/clsi/test/unit/coffee/LatexRunnerTests.coffee new file mode 100644 index 0000000000..d3782a6f3c --- /dev/null +++ b/services/clsi/test/unit/coffee/LatexRunnerTests.coffee @@ -0,0 +1,56 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +require('chai').should() +modulePath = require('path').join __dirname, '../../../app/js/LatexRunner' +Path = require "path" + +describe "LatexRunner", -> + beforeEach -> + @LatexRunner = SandboxedModule.require modulePath, requires: + "settings-sharelatex": @Settings = + docker: + socketPath: "/var/run/docker.sock" + "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } + "./Metrics": + Timer: class Timer + done: () -> + "./CommandRunner": @CommandRunner = {} + + @directory = "/local/compile/directory" + @mainFile = "main-file.tex" + @compiler = "pdflatex" + @callback = sinon.stub() + @project_id = "project-id-123" + + describe "runLatex", -> + beforeEach -> + @CommandRunner.run = sinon.stub().callsArg(4) + + describe "normally", -> + beforeEach -> + @LatexRunner.runLatex @project_id, + directory: @directory + mainFile: @mainFile + compiler: @compiler + timeout: @timeout = 42000 + @callback + + it "should run the latex command", -> + @CommandRunner.run + .calledWith(@project_id, sinon.match.any, @directory, @timeout) + .should.equal true + + describe "with an .Rtex main file", -> + beforeEach -> + @LatexRunner.runLatex @project_id, + directory: @directory + mainFile: "main-file.Rtex" + compiler: @compiler + timeout: @timeout = 42000 + @callback + + it "should run the latex command on the equivalent .tex file", -> + command = @CommandRunner.run.args[0][1] + mainFile = command.slice(-1)[0] + mainFile.should.equal "$COMPILE_DIR/main-file.tex" + diff --git a/services/clsi/test/unit/coffee/OutputFileFinderTests.coffee b/services/clsi/test/unit/coffee/OutputFileFinderTests.coffee new file mode 100644 index 0000000000..b429bf1a5e --- /dev/null +++ b/services/clsi/test/unit/coffee/OutputFileFinderTests.coffee @@ -0,0 +1,41 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +require('chai').should() +modulePath = require('path').join __dirname, '../../../app/js/OutputFileFinder' +path = require "path" +expect = require("chai").expect + +describe "OutputFileFinder", -> + beforeEach -> + @OutputFileFinder = SandboxedModule.require modulePath, requires: + "fs": @fs = {} + "wrench": @wrench = {} + @directory = "/test/dir" + @callback = sinon.stub() + + describe "findOutputFiles", -> + beforeEach -> + @resource_path = "resource/path.tex" + @output_paths = ["output.pdf", "extra", "extra/file.tex"] + @resources = [ + path: @resource_path = "resource/path.tex" + ] + @OutputFileFinder._isDirectory = (dirPath, callback = (error, directory) ->) => + callback null, dirPath == path.join(@directory, "extra") + + @wrench.readdirRecursive = (dir, callback) => + callback(null, [@resource_path].concat(@output_paths)) + callback(null, null) + sinon.spy @wrench, "readdirRecursive" + @OutputFileFinder.findOutputFiles @resources, @directory, (error, @outputFiles) => + + it "should only return the output files, not directories or resource paths", -> + expect(@outputFiles).to.deep.equal [{ + path: "output.pdf" + type: "pdf" + }, { + path: "extra/file.tex", + type: "tex" + }] + + diff --git a/services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.coffee b/services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.coffee new file mode 100644 index 0000000000..f8c78ef783 --- /dev/null +++ b/services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.coffee @@ -0,0 +1,60 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +require('chai').should() +modulePath = require('path').join __dirname, '../../../app/js/ProjectPersistenceManager' +tk = require("timekeeper") + +describe "ProjectPersistenceManager", -> + beforeEach -> + @ProjectPersistenceManager = SandboxedModule.require modulePath, requires: + "./UrlCache": @UrlCache = {} + "./CompileManager": @CompileManager = {} + "logger-sharelatex": @logger = { log: sinon.stub() } + "./db": @db = {} + @callback = sinon.stub() + @project_id = "project-id-123" + + describe "clearExpiredProjects", -> + beforeEach -> + @project_ids = [ + "project-id-1" + "project-id-2" + ] + @ProjectPersistenceManager._findExpiredProjectIds = sinon.stub().callsArgWith(0, null, @project_ids) + @ProjectPersistenceManager.clearProject = sinon.stub().callsArg(1) + @ProjectPersistenceManager.clearExpiredProjects @callback + + it "should clear each expired project", -> + for project_id in @project_ids + @ProjectPersistenceManager.clearProject + .calledWith(project_id) + .should.equal true + + it "should call the callback", -> + @callback.called.should.equal true + + describe "clearProject", -> + beforeEach -> + @ProjectPersistenceManager._clearProjectFromDatabase = sinon.stub().callsArg(1) + @UrlCache.clearProject = sinon.stub().callsArg(1) + @CompileManager.clearProject = sinon.stub().callsArg(1) + @ProjectPersistenceManager.clearProject @project_id, @callback + + it "should clear the project from the database", -> + @ProjectPersistenceManager._clearProjectFromDatabase + .calledWith(@project_id) + .should.equal true + + it "should clear all the cached Urls for the project", -> + @UrlCache.clearProject + .calledWith(@project_id) + .should.equal true + + it "should clear the project compile folder", -> + @CompileManager.clearProject + .calledWith(@project_id) + .should.equal true + + it "should call the callback", -> + @callback.called.should.equal true + diff --git a/services/clsi/test/unit/coffee/RequestParserTests.coffee b/services/clsi/test/unit/coffee/RequestParserTests.coffee new file mode 100644 index 0000000000..35ad6f4e14 --- /dev/null +++ b/services/clsi/test/unit/coffee/RequestParserTests.coffee @@ -0,0 +1,209 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +require('chai').should() +modulePath = require('path').join __dirname, '../../../app/js/RequestParser' +tk = require("timekeeper") + +describe "RequestParser", -> + beforeEach -> + tk.freeze() + @callback = sinon.stub() + @validResource = + path: "main.tex" + date: "12:00 01/02/03" + content: "Hello world" + @validRequest = + compile: + token: "token-123" + options: + compiler: "pdflatex" + timeout: 42 + resources: [] + @RequestParser = SandboxedModule.require modulePath + + afterEach -> + tk.reset() + + describe "without a top level object", -> + beforeEach -> + @RequestParser.parse [], @callback + + it "should return an error", -> + @callback.calledWith("top level object should have a compile attribute") + .should.equal true + + describe "without a compile attribute", -> + beforeEach -> + @RequestParser.parse {}, @callback + + it "should return an error", -> + @callback.calledWith("top level object should have a compile attribute") + .should.equal true + + describe "without a valid compiler", -> + beforeEach -> + @validRequest.compile.options.compiler = "not-a-compiler" + @RequestParser.parse @validRequest, @callback + + it "should return an error", -> + @callback.calledWith("compiler attribute should be one of: pdflatex, latex, xelatex, lualatex") + .should.equal true + + describe "without a compiler specified", -> + beforeEach -> + delete @validRequest.compile.options.compiler + @RequestParser.parse @validRequest, (error, @data) => + + it "should set the compiler to pdflatex by default", -> + @data.compiler.should.equal "pdflatex" + + describe "without a timeout specified", -> + beforeEach -> + delete @validRequest.compile.options.timeout + @RequestParser.parse @validRequest, (error, @data) => + + it "should set the timeout to MAX_TIMEOUT", -> + @data.timeout.should.equal @RequestParser.MAX_TIMEOUT * 1000 + + describe "with a timeout larger than the maximum", -> + beforeEach -> + @validRequest.compile.options.timeout = @RequestParser.MAX_TIMEOUT + 1 + @RequestParser.parse @validRequest, (error, @data) => + + it "should set the timeout to MAX_TIMEOUT", -> + @data.timeout.should.equal @RequestParser.MAX_TIMEOUT * 1000 + + describe "with a timeout", -> + beforeEach -> + @RequestParser.parse @validRequest, (error, @data) => + + it "should set the timeout (in milliseconds)", -> + @data.timeout.should.equal @validRequest.compile.options.timeout * 1000 + + describe "with a resource without a path", -> + beforeEach -> + delete @validResource.path + @validRequest.compile.resources.push @validResource + @RequestParser.parse @validRequest, @callback + + it "should return an error", -> + @callback.calledWith("all resources should have a path attribute") + .should.equal true + + describe "with a resource with a path", -> + beforeEach -> + @validResource.path = @path = "test.tex" + @validRequest.compile.resources.push @validResource + @RequestParser.parse @validRequest, @callback + @data = @callback.args[0][1] + + it "should return the path in the parsed response", -> + @data.resources[0].path.should.equal @path + + describe "with a resource with a malformed modified date", -> + beforeEach -> + @validResource.modified = "not-a-date" + @validRequest.compile.resources.push @validResource + @RequestParser.parse @validRequest, @callback + + it "should return an error", -> + @callback + .calledWith( + "resource modified date could not be understood: "+ + @validResource.modified + ) + .should.equal true + + describe "with a resource with a valid date", -> + beforeEach -> + @date = "12:00 01/02/03" + @validResource.modified = @date + @validRequest.compile.resources.push @validResource + @RequestParser.parse @validRequest, @callback + @data = @callback.args[0][1] + + it "should return the date as a Javascript Date object", -> + (@data.resources[0].modified instanceof Date).should.equal true + @data.resources[0].modified.getTime().should.equal Date.parse(@date) + + describe "with a resource without either a content or URL attribute", -> + beforeEach -> + delete @validResource.url + delete @validResource.content + @validRequest.compile.resources.push @validResource + @RequestParser.parse @validRequest, @callback + + it "should return an error", -> + @callback.calledWith("all resources should have either a url or content attribute") + .should.equal true + + describe "with a resource where the content is not a string", -> + beforeEach -> + @validResource.content = [] + @validRequest.compile.resources.push @validResource + @RequestParser.parse (@validRequest), @callback + + it "should return an error", -> + @callback.calledWith("content attribute should be a string") + .should.equal true + + describe "with a resource where the url is not a string", -> + beforeEach -> + @validResource.url = [] + @validRequest.compile.resources.push @validResource + @RequestParser.parse (@validRequest), @callback + + it "should return an error", -> + @callback.calledWith("url attribute should be a string") + .should.equal true + + describe "with a resource with a url", -> + beforeEach -> + @validResource.url = @url = "www.example.com" + @validRequest.compile.resources.push @validResource + @RequestParser.parse (@validRequest), @callback + @data = @callback.args[0][1] + + it "should return the url in the parsed response", -> + @data.resources[0].url.should.equal @url + + describe "with a resource with a content attribute", -> + beforeEach -> + @validResource.content = @content = "Hello world" + @validRequest.compile.resources.push @validResource + @RequestParser.parse (@validRequest), @callback + @data = @callback.args[0][1] + + it "should return the content in the parsed response", -> + @data.resources[0].content.should.equal @content + + describe "without a root resource path", -> + beforeEach -> + delete @validRequest.compile.rootResourcePath + @RequestParser.parse (@validRequest), @callback + @data = @callback.args[0][1] + + it "should set the root resource path to 'main.tex' by default", -> + @data.rootResourcePath.should.equal "main.tex" + + describe "with a root resource path", -> + beforeEach -> + @validRequest.compile.rootResourcePath = @path = "test.tex" + @RequestParser.parse (@validRequest), @callback + @data = @callback.args[0][1] + + it "should return the root resource path in the parsed response", -> + @data.rootResourcePath.should.equal @path + + describe "with a root resource path that is not a string", -> + beforeEach -> + @validRequest.compile.rootResourcePath = [] + @RequestParser.parse (@validRequest), @callback + + it "should return an error", -> + @callback.calledWith("rootResourcePath attribute should be a string") + .should.equal true + + + + diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee new file mode 100644 index 0000000000..1e7b3e776b --- /dev/null +++ b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee @@ -0,0 +1,152 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +require('chai').should() +modulePath = require('path').join __dirname, '../../../app/js/ResourceWriter' +path = require "path" + +describe "ResourceWriter", -> + beforeEach -> + @ResourceWriter = SandboxedModule.require modulePath, requires: + "fs": @fs = {} + "wrench": @wrench = {} + "./UrlCache" : @UrlCache = {} + "mkdirp" : @mkdirp = sinon.stub().callsArg(1) + "./OutputFileFinder": @OutputFileFinder = {} + "./Metrics": @Metrics = + Timer: class Timer + done: sinon.stub() + @project_id = "project-id-123" + @basePath = "/path/to/write/files/to" + @callback = sinon.stub() + + describe "syncResourcesToDisk", -> + beforeEach -> + @resources = [ + "resource-1-mock" + "resource-2-mock" + "resource-3-mock" + ] + @ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3) + @ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2) + @ResourceWriter.syncResourcesToDisk(@project_id, @resources, @basePath, @callback) + + it "should remove old files", -> + @ResourceWriter._removeExtraneousFiles + .calledWith(@resources, @basePath) + .should.equal true + + it "should write each resource to disk", -> + for resource in @resources + @ResourceWriter._writeResourceToDisk + .calledWith(@project_id, resource, @basePath) + .should.equal true + + it "should call the callback", -> + @callback.called.should.equal true + + describe "_removeExtraneousFiles", -> + beforeEach -> + @output_files = [{ + path: "output.pdf" + type: "pdf" + }, { + path: "extra/file.tex" + type: "tex" + }, { + path: "extra.aux" + type: "aux" + }] + @resources = "mock-resources" + @OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, @output_files) + @ResourceWriter._deleteFileIfNotDirectory = sinon.stub().callsArg(1) + @ResourceWriter._removeExtraneousFiles(@resources, @basePath, @callback) + + it "should find the existing output files", -> + @OutputFileFinder.findOutputFiles + .calledWith(@resources, @basePath) + .should.equal true + + it "should delete the output files", -> + @ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(@basePath, "output.pdf")) + .should.equal true + + it "should delete the extra files", -> + @ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(@basePath, "extra/file.tex")) + .should.equal true + + it "should not delete the extra aux files", -> + @ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(@basePath, "extra.aux")) + .should.equal false + + it "should call the callback", -> + @callback.called.should.equal true + + it "should time the request", -> + @Metrics.Timer::done.called.should.equal true + + describe "_writeResourceToDisk", -> + describe "with a url based resource", -> + beforeEach -> + @resource = + path: "main.tex" + url: "http://www.example.com/main.tex" + modified: Date.now() + @UrlCache.downloadUrlToFile = sinon.stub().callsArg(4) + @ResourceWriter._writeResourceToDisk(@project_id, @resource, @basePath, @callback) + + it "should ensure the directory exists", -> + @mkdirp + .calledWith(path.dirname(path.join(@basePath, @resource.path))) + .should.equal true + + it "should write the URL from the cache", -> + @UrlCache.downloadUrlToFile + .calledWith(@project_id, @resource.url, path.join(@basePath, @resource.path), @resource.modified) + .should.equal true + + it "should call the callback", -> + @callback.called.should.equal true + + describe "with a content based resource", -> + beforeEach -> + @resource = + path: "main.tex" + content: "Hello world" + @fs.writeFile = sinon.stub().callsArg(2) + @ResourceWriter._writeResourceToDisk(@project_id, @resource, @basePath, @callback) + + it "should ensure the directory exists", -> + @mkdirp + .calledWith(path.dirname(path.join(@basePath, @resource.path))) + .should.equal true + + it "should write the contents to disk", -> + @fs.writeFile + .calledWith(path.join(@basePath, @resource.path), @resource.content) + .should.equal true + + it "should call the callback", -> + @callback.called.should.equal true + + describe "with a file path that breaks out of the root folder", -> + beforeEach -> + @resource = + path: "../../main.tex" + content: "Hello world" + @fs.writeFile = sinon.stub().callsArg(2) + @ResourceWriter._writeResourceToDisk(@project_id, @resource, @basePath, @callback) + + it "should not write to disk", -> + @fs.writeFile.called.should.equal false + + it "should return an error", -> + @callback + .calledWith(new Error("resource path is outside root directory")) + .should.equal true + + + + diff --git a/services/clsi/test/unit/coffee/UrlCacheTests.coffee b/services/clsi/test/unit/coffee/UrlCacheTests.coffee new file mode 100644 index 0000000000..36a11cbbd2 --- /dev/null +++ b/services/clsi/test/unit/coffee/UrlCacheTests.coffee @@ -0,0 +1,200 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +require('chai').should() +modulePath = require('path').join __dirname, '../../../app/js/UrlCache' +EventEmitter = require("events").EventEmitter + +describe "UrlCache", -> + beforeEach -> + @callback = sinon.stub() + @url = "www.example.com/file" + @project_id = "project-id-123" + @UrlCache = SandboxedModule.require modulePath, requires: + "./db" : {} + "./UrlFetcher" : @UrlFetcher = {} + "logger-sharelatex": @logger = {log: sinon.stub()} + "settings-sharelatex": @Settings = { path: clsiCacheDir: "/cache/dir" } + "fs": @fs = {} + + describe "_doesUrlNeedDownloading", -> + beforeEach -> + @lastModified = new Date() + @lastModifiedRoundedToSeconds = new Date(Math.floor(@lastModified.getTime() / 1000) * 1000) + + describe "when URL does not exist in cache", -> + beforeEach -> + @UrlCache._findUrlDetails = sinon.stub().callsArgWith(2, null, null) + @UrlCache._doesUrlNeedDownloading(@project_id, @url, @lastModified, @callback) + + it "should return the callback with true", -> + @callback.calledWith(null, true).should.equal true + + describe "when URL does exist in cache", -> + beforeEach -> + @urlDetails = {} + @UrlCache._findUrlDetails = sinon.stub().callsArgWith(2, null, @urlDetails) + + describe "when the modified date is more recent than the cached modified date", -> + beforeEach -> + @urlDetails.lastModified = new Date(@lastModified.getTime() - 1000) + @UrlCache._doesUrlNeedDownloading(@project_id, @url, @lastModified, @callback) + + it "should get the url details", -> + @UrlCache._findUrlDetails + .calledWith(@project_id, @url) + .should.equal true + + it "should return the callback with true", -> + @callback.calledWith(null, true).should.equal true + + describe "when the cached modified date is more recent than the modified date", -> + beforeEach -> + @urlDetails.lastModified = new Date(@lastModified.getTime() + 1000) + @UrlCache._doesUrlNeedDownloading(@project_id, @url, @lastModified, @callback) + + it "should return the callback with false", -> + @callback.calledWith(null, false).should.equal true + + describe "when the cached modified date is equal to the modified date", -> + beforeEach -> + @urlDetails.lastModified = @lastModified + @UrlCache._doesUrlNeedDownloading(@project_id, @url, @lastModified, @callback) + + it "should return the callback with false", -> + @callback.calledWith(null, false).should.equal true + + describe "when the provided modified date does not exist", -> + beforeEach -> + @lastModified = null + @UrlCache._doesUrlNeedDownloading(@project_id, @url, @lastModified, @callback) + + it "should return the callback with true", -> + @callback.calledWith(null, true).should.equal true + + describe "when the URL does not have a modified date", -> + beforeEach -> + @urlDetails.lastModified = null + @UrlCache._doesUrlNeedDownloading(@project_id, @url, @lastModified, @callback) + + it "should return the callback with true", -> + @callback.calledWith(null, true).should.equal true + + describe "_ensureUrlIsInCache", -> + beforeEach -> + @UrlFetcher.pipeUrlToFile = sinon.stub().callsArg(2) + @UrlCache._updateOrCreateUrlDetails = sinon.stub().callsArg(3) + + describe "when the URL needs updating", -> + beforeEach -> + @UrlCache._doesUrlNeedDownloading = sinon.stub().callsArgWith(3, null, true) + @UrlCache._ensureUrlIsInCache(@project_id, @url, @lastModified, @callback) + + it "should check that the url needs downloading", -> + @UrlCache._doesUrlNeedDownloading + .calledWith(@project_id, @url, @lastModifiedRoundedToSeconds) + .should.equal true + + it "should download the URL to the cache file", -> + @UrlFetcher.pipeUrlToFile + .calledWith(@url, @UrlCache._cacheFilePathForUrl(@project_id, @url)) + .should.equal true + + + it "should update the database entry", -> + @UrlCache._updateOrCreateUrlDetails + .calledWith(@project_id, @url, @lastModifiedRoundedToSeconds) + .should.equal true + + it "should return the callback with the cache file path", -> + @callback + .calledWith(null, @UrlCache._cacheFilePathForUrl(@project_id, @url)) + .should.equal true + + describe "when the URL does not need updating", -> + beforeEach -> + @UrlCache._doesUrlNeedDownloading = sinon.stub().callsArgWith(3, null, false) + @UrlCache._ensureUrlIsInCache(@project_id, @url, @lastModified, @callback) + + it "should not download the URL to the cache file", -> + @UrlFetcher.pipeUrlToFile + .called.should.equal false + + it "should return the callback with the cache file path", -> + @callback + .calledWith(null, @UrlCache._cacheFilePathForUrl(@project_id, @url)) + .should.equal true + + describe "downloadUrlToFile", -> + beforeEach -> + @cachePath = "path/to/cached/url" + @destPath = "path/to/destination" + @UrlCache._copyFile = sinon.stub().callsArg(2) + @UrlCache._ensureUrlIsInCache = sinon.stub().callsArgWith(3, null, @cachePath) + @UrlCache.downloadUrlToFile(@project_id, @url, @destPath, @lastModified, @callback) + + it "should ensure the URL is downloaded and updated in the cache", -> + @UrlCache._ensureUrlIsInCache + .calledWith(@project_id, @url, @lastModified) + .should.equal true + + it "should copy the file to the new location", -> + @UrlCache._copyFile + .calledWith(@cachePath, @destPath) + .should.equal true + + it "should call the callback", -> + @callback.called.should.equal true + + describe "_deleteUrlCacheFromDisk", -> + beforeEach -> + @fs.unlink = sinon.stub().callsArg(1) + @UrlCache._deleteUrlCacheFromDisk(@project_id, @url, @callback) + + it "should delete the cache file", -> + @fs.unlink + .calledWith(@UrlCache._cacheFilePathForUrl(@project_id, @url)) + .should.equal true + + it "should call the callback", -> + @callback.called.should.equal true + + describe "_clearUrlFromCache", -> + beforeEach -> + @UrlCache._deleteUrlCacheFromDisk = sinon.stub().callsArg(2) + @UrlCache._clearUrlDetails = sinon.stub().callsArg(2) + @UrlCache._clearUrlFromCache @project_id, @url, @callback + + it "should delete the file on disk", -> + @UrlCache._deleteUrlCacheFromDisk + .calledWith(@project_id, @url) + .should.equal true + + it "should clear the entry in the database", -> + @UrlCache._clearUrlDetails + .calledWith(@project_id, @url) + .should.equal true + + it "should call the callback", -> + @callback.called.should.equal true + + describe "clearProject", -> + beforeEach -> + @urls = [ + "www.example.com/file1" + "www.example.com/file2" + ] + @UrlCache._findAllUrlsInProject = sinon.stub().callsArgWith(1, null, @urls) + @UrlCache._clearUrlFromCache = sinon.stub().callsArg(2) + @UrlCache.clearProject @project_id, @callback + + it "should clear the cache for each url in the project", -> + for url in @urls + @UrlCache._clearUrlFromCache + .calledWith(@project_id, url) + .should.equal true + + it "should call the callback", -> + @callback.called.should.equal true + + + diff --git a/services/clsi/test/unit/coffee/UrlFetcherTests.coffee b/services/clsi/test/unit/coffee/UrlFetcherTests.coffee new file mode 100644 index 0000000000..3e6dc926c7 --- /dev/null +++ b/services/clsi/test/unit/coffee/UrlFetcherTests.coffee @@ -0,0 +1,74 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +require('chai').should() +modulePath = require('path').join __dirname, '../../../app/js/UrlFetcher' +EventEmitter = require("events").EventEmitter + +describe "UrlFetcher", -> + beforeEach -> + @callback = sinon.stub() + @url = "www.example.com/file" + @UrlFetcher = SandboxedModule.require modulePath, requires: + request: defaults: @defaults = sinon.stub().returns(@request = {}) + fs: @fs = {} + + it "should turn off the cookie jar in request", -> + @defaults.calledWith(jar: false) + .should.equal true + + describe "_pipeUrlToFile", -> + beforeEach -> + @path = "/path/to/file/on/disk" + @request.get = sinon.stub().returns(@urlStream = new EventEmitter) + @urlStream.pipe = sinon.stub() + @fs.createWriteStream = sinon.stub().returns(@fileStream = "write-stream-stub") + @UrlFetcher.pipeUrlToFile(@url, @path, @callback) + + it "should request the URL", -> + @request.get + .calledWith(@url) + .should.equal true + + it "should open the file for writing", -> + @fs.createWriteStream + .calledWith(@path) + .should.equal true + + describe "successfully", -> + beforeEach -> + @res = statusCode: 200 + @urlStream.emit "response", @res + @urlStream.emit "end" + + it "should pipe the URL to the file", -> + @urlStream.pipe + .calledWith(@fileStream) + .should.equal true + + it "should call the callback", -> + @callback.called.should.equal true + + describe "with non success status code", -> + beforeEach -> + @res = statusCode: 404 + @urlStream.emit "response", @res + @urlStream.emit "end" + + it "should call the callback with an error", -> + @callback + .calledWith(new Error("URL returned non-success status code: 404")) + .should.equal true + + describe "with error", -> + beforeEach -> + @urlStream.emit "error", @error = new Error("something went wrong") + + it "should call the callback with the error", -> + @callback + .calledWith(@error) + .should.equal true + + it "should only call the callback once, even if end is called", -> + @urlStream.emit "end" + @callback.calledOnce.should.equal true + From 5587ac0acbda1b13fca7c63e5a84d28023fd8015 Mon Sep 17 00:00:00 2001 From: James Allen Date: Thu, 13 Feb 2014 09:33:39 +0000 Subject: [PATCH 002/754] Fix up smoke tests --- services/clsi/Gruntfile.coffee | 2 +- services/clsi/config/settings.testing.coffee | 20 +++++++++---------- .../clsi/test/smoke/coffee/SmokeTests.coffee | 4 ++-- services/clsi/test/smoke/js/SmokeTests.js | 7 +++++-- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/services/clsi/Gruntfile.coffee b/services/clsi/Gruntfile.coffee index 37fa78f188..e95b7be7a4 100644 --- a/services/clsi/Gruntfile.coffee +++ b/services/clsi/Gruntfile.coffee @@ -80,7 +80,7 @@ module.exports = (grunt) -> grunt.loadNpmTasks 'grunt-mocha-test' grunt.loadNpmTasks 'grunt-shell' - grunt.registerTask 'compile:app', ['clean:app', 'coffee:app', 'coffee:app_src'] + grunt.registerTask 'compile:app', ['clean:app', 'coffee:app', 'coffee:app_src', 'coffee:smoke_tests'] grunt.registerTask 'run', ['compile:app', 'concurrent'] grunt.registerTask 'compile:unit_tests', ['clean:unit_tests', 'coffee:unit_tests'] diff --git a/services/clsi/config/settings.testing.coffee b/services/clsi/config/settings.testing.coffee index 9407a50906..19874cd5f2 100644 --- a/services/clsi/config/settings.testing.coffee +++ b/services/clsi/config/settings.testing.coffee @@ -14,20 +14,20 @@ module.exports = compilesDir: Path.resolve(__dirname + "/../compiles") clsiCacheDir: Path.resolve(__dirname + "/../cache") - clsi: - # commandRunner: "docker-runner-sharelatex" - # docker: - # image: "quay.io/sharelatex/texlive-full" - # env: - # PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2013/bin/x86_64-linux/" - # HOME: "/tmp" - # socketPath: "/var/run/docker.sock" - # user: "tex" + # clsi: + # commandRunner: "docker-runner-sharelatex" + # docker: + # image: "quay.io/sharelatex/texlive-full" + # env: + # PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2013/bin/x86_64-linux/" + # HOME: "/tmp" + # socketPath: "/var/run/docker.sock" + # user: "tex" internal: clsi: port: 3013 - host: "" + host: "localhost" apis: clsi: diff --git a/services/clsi/test/smoke/coffee/SmokeTests.coffee b/services/clsi/test/smoke/coffee/SmokeTests.coffee index 4cc18eadb1..f560b0cf2d 100644 --- a/services/clsi/test/smoke/coffee/SmokeTests.coffee +++ b/services/clsi/test/smoke/coffee/SmokeTests.coffee @@ -2,9 +2,9 @@ chai = require("chai") chai.should() expect = chai.expect request = require "request" -Settings = require "../../../app/js/Settings" +Settings = require "settings-sharelatex" -buildUrl = (path) -> "http://localhost:#{Settings.listen.port}/#{path}" +buildUrl = (path) -> "http://#{Settings.internal.clsi.host}:#{Settings.internal.clsi.port}/#{path}" describe "Running a compile", -> before (done) -> diff --git a/services/clsi/test/smoke/js/SmokeTests.js b/services/clsi/test/smoke/js/SmokeTests.js index c364c88406..13b2fa4ec6 100644 --- a/services/clsi/test/smoke/js/SmokeTests.js +++ b/services/clsi/test/smoke/js/SmokeTests.js @@ -9,10 +9,10 @@ request = require("request"); - Settings = require("../../../app/js/Settings"); + Settings = require("settings-sharelatex"); buildUrl = function(path) { - return "http://localhost:" + Settings.listen.port + "/" + path; + return "http://" + Settings.internal.clsi.host + ":" + Settings.internal.clsi.port + "/" + path; }; describe("Running a compile", function() { @@ -34,6 +34,9 @@ _this.error = error; _this.response = response; _this.body = body; + if (_this.error != null) { + throw _this.error; + } return done(); }); }); From 73c652fb2c71a60bb5a8c96024175fc3e03f61a6 Mon Sep 17 00:00:00 2001 From: James Allen Date: Thu, 13 Feb 2014 11:08:40 +0000 Subject: [PATCH 003/754] Update a missed setting --- services/clsi/app/coffee/CompileManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 9d2bd2acc2..78710472d0 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -35,5 +35,5 @@ module.exports = CompileManager = callback null, outputFiles clearProject: (project_id, callback = (error) ->) -> - compileDir = Path.join(Settings.compileDir, project_id) + compileDir = Path.join(Settings.path.compilesDir, project_id) rimraf compileDir, callback From 8771817cdf20bf0f6fd5729f4af9601509128dde Mon Sep 17 00:00:00 2001 From: James Allen Date: Thu, 13 Feb 2014 11:11:53 +0000 Subject: [PATCH 004/754] Fix unit tests --- services/clsi/.gitignore | 1 + services/clsi/test/smoke/js/SmokeTests.js | 67 ------------------- .../unit/coffee/CompileManagerTests.coffee | 2 +- 3 files changed, 2 insertions(+), 68 deletions(-) delete mode 100644 services/clsi/test/smoke/js/SmokeTests.js diff --git a/services/clsi/.gitignore b/services/clsi/.gitignore index a039a12bd1..0a58336149 100644 --- a/services/clsi/.gitignore +++ b/services/clsi/.gitignore @@ -2,6 +2,7 @@ node_modules app/js test/unit/js +test/smoke/js test/acceptance/js test/acceptance/fixtures/tmp compiles diff --git a/services/clsi/test/smoke/js/SmokeTests.js b/services/clsi/test/smoke/js/SmokeTests.js deleted file mode 100644 index 13b2fa4ec6..0000000000 --- a/services/clsi/test/smoke/js/SmokeTests.js +++ /dev/null @@ -1,67 +0,0 @@ -(function() { - var Settings, buildUrl, chai, expect, request; - - chai = require("chai"); - - chai.should(); - - expect = chai.expect; - - request = require("request"); - - Settings = require("settings-sharelatex"); - - buildUrl = function(path) { - return "http://" + Settings.internal.clsi.host + ":" + Settings.internal.clsi.port + "/" + path; - }; - - describe("Running a compile", function() { - before(function(done) { - var _this = this; - return request.post({ - url: buildUrl("project/smoketest/compile"), - json: { - compile: { - resources: [ - { - path: "main.tex", - content: "\\documentclass{article}\n\\begin{document}\nHello world\n\\end{document}" - } - ] - } - } - }, function(error, response, body) { - _this.error = error; - _this.response = response; - _this.body = body; - if (_this.error != null) { - throw _this.error; - } - return done(); - }); - }); - it("should return the pdf", function() { - var file, _i, _len, _ref; - _ref = this.body.compile.outputFiles; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - file = _ref[_i]; - if (file.type === "pdf") { - return; - } - } - throw new Error("no pdf returned"); - }); - return it("should return the log", function() { - var file, _i, _len, _ref; - _ref = this.body.compile.outputFiles; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - file = _ref[_i]; - if (file.type === "log") { - return; - } - } - throw new Error("no log returned"); - }); - }); - -}).call(this); diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 098e29606d..204da112ef 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -66,7 +66,7 @@ describe "CompileManager", -> @CompileManager.clearProject @project_id, @callback it "should remove the project directory", -> - @rimraf.calledWith("#{@Settings.compileDir}/#{@project_id}") + @rimraf.calledWith("#{@Settings.path.compilesDir}/#{@project_id}") .should.equal true it "should call the callback", -> From 27251a1df1d44a21c3d3fa0d3c99a54b6ab389dc Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 17 Feb 2014 15:06:53 +0000 Subject: [PATCH 005/754] Install custom npm modules from github --- services/clsi/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index 0cfaeadc11..f6898a3347 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -11,11 +11,11 @@ "mysql": "2.0.0-alpha7", "request": "~2.21.0", "rimraf": "2.1.4", - "logger-sharelatex": "git+ssh://git@bitbucket.org:sharelatex/logger-sharelatex.git#bunyan", - "settings-sharelatex": "git+ssh://git@bitbucket.org:sharelatex/settings-sharelatex.git#master", + "logger-sharelatex": "git+ssh://git@github.com:sharelatex/logger-sharelatex.git#bunyan", + "settings-sharelatex": "git+ssh://git@github.com:sharelatex/settings-sharelatex.git#master", "sequelize": "~2.0.0-beta.2", "wrench": "~1.5.4", - "smoke-test-sharelatex": "git+ssh://git@bitbucket.org:sharelatex/smoke-test-sharelatex.git#master" + "smoke-test-sharelatex": "git+ssh://git@github.com:sharelatex/smoke-test-sharelatex.git#master" }, "devDependencies": { "mocha": "1.10.0", From ff7f2eae04e6cc18b44b6b03f3d5bfe8e8dd006a Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 17 Feb 2014 15:09:12 +0000 Subject: [PATCH 006/754] Use master branch of logger-settings module --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index f6898a3347..2630995cf5 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -11,7 +11,7 @@ "mysql": "2.0.0-alpha7", "request": "~2.21.0", "rimraf": "2.1.4", - "logger-sharelatex": "git+ssh://git@github.com:sharelatex/logger-sharelatex.git#bunyan", + "logger-sharelatex": "git+ssh://git@github.com:sharelatex/logger-sharelatex.git#master", "settings-sharelatex": "git+ssh://git@github.com:sharelatex/settings-sharelatex.git#master", "sequelize": "~2.0.0-beta.2", "wrench": "~1.5.4", From 85275a93732265710c824642d74d4a0141e97a6f Mon Sep 17 00:00:00 2001 From: James Allen Date: Tue, 18 Feb 2014 16:58:21 +0000 Subject: [PATCH 007/754] Create README.md --- services/clsi/README.md | 52 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 services/clsi/README.md diff --git a/services/clsi/README.md b/services/clsi/README.md new file mode 100644 index 0000000000..0e3342fd7d --- /dev/null +++ b/services/clsi/README.md @@ -0,0 +1,52 @@ +clsi-sharelatex +=============== + +A web api for compiling LaTeX documents in the cloud + +Installation +------------ + +The CLSI can be installed and set up as part of the entire [ShareLaTeX stack](https://github.com/sharelatex/sharelatex) (complete with front end editor and document storage), or it can be run as a standalone service. To run is as a standalone service, first checkout this repository: + + $ git clone git@github.com:sharelatex/clsi-sharelatex.git + +Then install the require npm modules: + + $ npm install + +Then compile the coffee script source files: + + $ grunt compile + +Finally, (after configuring your local database - see the Config section), run the CLSI service: + + $ grunt run + +The CLSI should then be running at http://localhost:3013. + +Config +------ + +You will need to set up a database in mysql to use with the CLSI, and then fill in the database name, username and password in the config file at `config/settings.development.coffee`. + +API +--- + +The CLSI is based on a JSON API. + +#### Example Request + +```javascript + { + "options": { + "compiler": "lualatex" # Can be latex, pdflatex, xelatex or lualatex + "timeout": 40 # How many seconds to wait before killing the process. Default is 60. + }, + "rootResourcePath": "main.tex", # The main file to run LaTeX on + # An array of files to include in the compilation + "resources": [{ + "path": "main.tex", + "content": "\\documentclass{article}\n\\begin{document}\nHello World\n\\end{document}" + }] + } +``` From df4516ce957c4ee49391f98941239df47c9c4d49 Mon Sep 17 00:00:00 2001 From: James Allen Date: Tue, 18 Feb 2014 17:08:26 +0000 Subject: [PATCH 008/754] Update README.md --- services/clsi/README.md | 56 +++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/services/clsi/README.md b/services/clsi/README.md index 0e3342fd7d..c29ec947b7 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -36,17 +36,51 @@ The CLSI is based on a JSON API. #### Example Request +(Note that valid JSON should not contain any comments like the example below). + + POST /project/ + ```javascript - { - "options": { - "compiler": "lualatex" # Can be latex, pdflatex, xelatex or lualatex - "timeout": 40 # How many seconds to wait before killing the process. Default is 60. - }, - "rootResourcePath": "main.tex", # The main file to run LaTeX on - # An array of files to include in the compilation - "resources": [{ - "path": "main.tex", - "content": "\\documentclass{article}\n\\begin{document}\nHello World\n\\end{document}" - }] +{ + compile: { + "options": { + // Which compiler to use. Can be latex, pdflatex, xelatex or lualatex + "compiler": "lualatex" + // How many seconds to wait before killing the process. Default is 60. + "timeout": 40 + }, + // The main file to run LaTeX on + "rootResourcePath": "main.tex", + // An array of files to include in the compilation. May have either the content + // passed directly, or a URL where it can be downlaoded. + "resources": [{ + "path": "main.tex", + "content": "\\documentclass{article}\n\\begin{document}\nHello World\n\\end{document}" + }, { + "path": "image.png", + "url": "www.example.com/image.png", + "modified": 123456789 // Unix time since epoch + }] } +} +``` + +You can specify any project-id in the URL, and the files and LaTeX environment will be persisted between requests. +URLs will be downloaded and cached until provided with a more recent modified date. + +#### Example Response + +```javascript +{ + "compile": { + "status": "success", + "outputFiles": [{ + "type": "pdf", + "url": "http://localhost:3013/project//output/output.pdf" + }, { + "type": "log", + "url": "http://localhost:3013/project//output/output.log" + }] + } +} ``` From 70c6b0b8da5171b711ebc42c6b6d12fc96b0e047 Mon Sep 17 00:00:00 2001 From: James Allen Date: Tue, 18 Feb 2014 17:09:54 +0000 Subject: [PATCH 009/754] Create LICENSE --- services/clsi/LICENSE | 661 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 661 insertions(+) create mode 100644 services/clsi/LICENSE diff --git a/services/clsi/LICENSE b/services/clsi/LICENSE new file mode 100644 index 0000000000..dba13ed2dd --- /dev/null +++ b/services/clsi/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. From e4eca94cdb1e59d60876a35a4de9670c810ed46e Mon Sep 17 00:00:00 2001 From: James Allen Date: Tue, 18 Feb 2014 17:11:52 +0000 Subject: [PATCH 010/754] Update README.md --- services/clsi/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/services/clsi/README.md b/services/clsi/README.md index c29ec947b7..f9a07783d6 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -84,3 +84,10 @@ URLs will be downloaded and cached until provided with a more recent modified da } } ``` + +License +------- + +The code in this repository is released under the GNU AFFERO GENERAL PUBLIC LICENSE, version 3. A copy can be found in the `LICENSE` file. + +Copyright (c) ShareLaTeX, 2014. From 9d66fcf715bf7fe17faf1285ab1a28e18a04e2ea Mon Sep 17 00:00:00 2001 From: James Allen Date: Tue, 18 Feb 2014 17:40:55 +0000 Subject: [PATCH 011/754] Use sqlite in development --- services/clsi/.gitignore | 1 + services/clsi/config/settings.testing.coffee | 2 ++ services/clsi/package.json | 3 ++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/services/clsi/.gitignore b/services/clsi/.gitignore index 0a58336149..30aa9829f9 100644 --- a/services/clsi/.gitignore +++ b/services/clsi/.gitignore @@ -11,3 +11,4 @@ app.js *~ cache .vagrant +db.sqlite diff --git a/services/clsi/config/settings.testing.coffee b/services/clsi/config/settings.testing.coffee index 19874cd5f2..ff7b0087e6 100644 --- a/services/clsi/config/settings.testing.coffee +++ b/services/clsi/config/settings.testing.coffee @@ -8,6 +8,8 @@ module.exports = database: "clsi" username: "clsi" password: null + dialect: "sqlite" + storage: Path.resolve(__dirname + "/../db.sqlite") path: diff --git a/services/clsi/package.json b/services/clsi/package.json index 2630995cf5..1892a7aaf9 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -15,7 +15,8 @@ "settings-sharelatex": "git+ssh://git@github.com:sharelatex/settings-sharelatex.git#master", "sequelize": "~2.0.0-beta.2", "wrench": "~1.5.4", - "smoke-test-sharelatex": "git+ssh://git@github.com:sharelatex/smoke-test-sharelatex.git#master" + "smoke-test-sharelatex": "git+ssh://git@github.com:sharelatex/smoke-test-sharelatex.git#master", + "sqlite3": "~2.2.0" }, "devDependencies": { "mocha": "1.10.0", From 0a9444199884afbd33f9ad074d562f5f95586a19 Mon Sep 17 00:00:00 2001 From: Anton Ilin Date: Fri, 21 Feb 2014 14:29:06 +0200 Subject: [PATCH 012/754] Typo fix Should be "downloaded" not "downlaoded" :) --- services/clsi/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/README.md b/services/clsi/README.md index f9a07783d6..bb1d828847 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -52,7 +52,7 @@ The CLSI is based on a JSON API. // The main file to run LaTeX on "rootResourcePath": "main.tex", // An array of files to include in the compilation. May have either the content - // passed directly, or a URL where it can be downlaoded. + // passed directly, or a URL where it can be downloaded. "resources": [{ "path": "main.tex", "content": "\\documentclass{article}\n\\begin{document}\nHello World\n\\end{document}" From 07d858e5c3f87037606919c0c8f7054566b20900 Mon Sep 17 00:00:00 2001 From: James Allen Date: Fri, 21 Feb 2014 15:04:41 +0000 Subject: [PATCH 013/754] Use https git URLs instead of SSH --- services/clsi/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index 1892a7aaf9..db76807bd5 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -11,11 +11,11 @@ "mysql": "2.0.0-alpha7", "request": "~2.21.0", "rimraf": "2.1.4", - "logger-sharelatex": "git+ssh://git@github.com:sharelatex/logger-sharelatex.git#master", - "settings-sharelatex": "git+ssh://git@github.com:sharelatex/settings-sharelatex.git#master", + "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#master", + "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#master", "sequelize": "~2.0.0-beta.2", "wrench": "~1.5.4", - "smoke-test-sharelatex": "git+ssh://git@github.com:sharelatex/smoke-test-sharelatex.git#master", + "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#master", "sqlite3": "~2.2.0" }, "devDependencies": { From 4d6d4eb6a2977be97a044c3a8c379daa918fae2b Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 24 Feb 2014 14:27:06 +0000 Subject: [PATCH 014/754] Create .travis.yml --- services/clsi/.travis.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 services/clsi/.travis.yml diff --git a/services/clsi/.travis.yml b/services/clsi/.travis.yml new file mode 100644 index 0000000000..d97f744899 --- /dev/null +++ b/services/clsi/.travis.yml @@ -0,0 +1,19 @@ +language: node_js + +node_js: + - "0.10" + +before_install: + - npm install -g grunt-cli + +install: + - npm install + - grunt install + +script: + - grunt test:unit + +services: + - redis-server + - mongodb + From 6b450a45a7b0326a5905bc5d7ca88d8e22953aac Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 24 Feb 2014 14:42:36 +0000 Subject: [PATCH 015/754] Update .travis.yml --- services/clsi/.travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/services/clsi/.travis.yml b/services/clsi/.travis.yml index d97f744899..29f5884d60 100644 --- a/services/clsi/.travis.yml +++ b/services/clsi/.travis.yml @@ -16,4 +16,3 @@ script: services: - redis-server - mongodb - From 0dea22a8e98e28d99a79a86e256c8501503452b9 Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 24 Feb 2014 14:45:00 +0000 Subject: [PATCH 016/754] Add in Travis CI badge --- services/clsi/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/clsi/README.md b/services/clsi/README.md index bb1d828847..b94f8034df 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -3,6 +3,8 @@ clsi-sharelatex A web api for compiling LaTeX documents in the cloud +[![Build Status](https://travis-ci.org/sharelatex/clsi-sharelatex.png?branch=master)](https://travis-ci.org/sharelatex/clsi-sharelatex) + Installation ------------ From d829f83226a458b9d36c913c8f3180c8cc9b635e Mon Sep 17 00:00:00 2001 From: Christopher Adams Date: Wed, 26 Feb 2014 10:09:18 -0500 Subject: [PATCH 017/754] Correct installation task from compile to install --- services/clsi/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/README.md b/services/clsi/README.md index b94f8034df..97d1821ce2 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -18,7 +18,7 @@ Then install the require npm modules: Then compile the coffee script source files: - $ grunt compile + $ grunt install Finally, (after configuring your local database - see the Config section), run the CLSI service: From 2b5e369b9842570f90027c393112f33ad6a7c4f1 Mon Sep 17 00:00:00 2001 From: Christopher Adams Date: Wed, 26 Feb 2014 10:13:09 -0500 Subject: [PATCH 018/754] Fix example request URL and JSON --- services/clsi/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/clsi/README.md b/services/clsi/README.md index 97d1821ce2..270c06ac5d 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -40,14 +40,14 @@ The CLSI is based on a JSON API. (Note that valid JSON should not contain any comments like the example below). - POST /project/ + POST /project//compile ```javascript { - compile: { + "compile": { "options": { // Which compiler to use. Can be latex, pdflatex, xelatex or lualatex - "compiler": "lualatex" + "compiler": "lualatex", // How many seconds to wait before killing the process. Default is 60. "timeout": 40 }, From b484f08d6eb8cbc96838e58b32802e982b0c14dd Mon Sep 17 00:00:00 2001 From: James Allen Date: Wed, 2 Apr 2014 12:53:02 +0100 Subject: [PATCH 019/754] Use system rm -r to allow removal of files with broken char encodings --- .../clsi/app/coffee/CompileManager.coffee | 21 ++++++-- services/clsi/package.json | 1 - .../unit/coffee/CompileManagerTests.coffee | 49 +++++++++++++++---- 3 files changed, 58 insertions(+), 13 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 78710472d0..dce6a86365 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -5,7 +5,7 @@ Settings = require("settings-sharelatex") Path = require "path" logger = require "logger-sharelatex" Metrics = require "./Metrics" -rimraf = require "rimraf" +child_process = require "child_process" module.exports = CompileManager = doCompile: (request, callback = (error, outputFiles) ->) -> @@ -34,6 +34,21 @@ module.exports = CompileManager = return callback(error) if error? callback null, outputFiles - clearProject: (project_id, callback = (error) ->) -> + clearProject: (project_id, _callback = (error) ->) -> + callback = (error) -> + _callback(error) + _callback = () -> + compileDir = Path.join(Settings.path.compilesDir, project_id) - rimraf compileDir, callback + proc = child_process.spawn "rm", ["-r", compileDir] + + proc.on "error", callback + + stderr = "" + proc.stderr.on "data", (chunk) -> stderr += chunk.toString() + + proc.on "close", (code) -> + if code == 0 + return callback(null) + else + return callback(new Error("rm -r #{compileDir} failed: #{stderr}")) diff --git a/services/clsi/package.json b/services/clsi/package.json index db76807bd5..0fdc15ab57 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -10,7 +10,6 @@ "mkdirp": "0.3.5", "mysql": "2.0.0-alpha7", "request": "~2.21.0", - "rimraf": "2.1.4", "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#master", "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#master", "sequelize": "~2.0.0-beta.2", diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 204da112ef..6c75835b59 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -3,6 +3,7 @@ sinon = require('sinon') require('chai').should() modulePath = require('path').join __dirname, '../../../app/js/CompileManager' tk = require("timekeeper") +EventEmitter = require("events").EventEmitter describe "CompileManager", -> beforeEach -> @@ -12,7 +13,7 @@ describe "CompileManager", -> "./OutputFileFinder": @OutputFileFinder = {} "settings-sharelatex": @Settings = { path: compilesDir: "/compiles/dir" } "logger-sharelatex": @logger = { log: sinon.stub() } - "rimraf": @rimraf = sinon.stub().callsArg(1) + "child_process": @child_process = {} @callback = sinon.stub() describe "doCompile", -> @@ -61,13 +62,43 @@ describe "CompileManager", -> @callback.calledWith(null, @output_files).should.equal true describe "clearProject", -> - beforeEach -> - @Settings.compileDir = "compiles" - @CompileManager.clearProject @project_id, @callback + describe "succesfully", -> + beforeEach -> + @Settings.compileDir = "compiles" + @proc = new EventEmitter() + @proc.stdout = new EventEmitter() + @proc.stderr = new EventEmitter() + @child_process.spawn = sinon.stub().returns(@proc) + @CompileManager.clearProject @project_id, @callback + @proc.emit "close", 0 - it "should remove the project directory", -> - @rimraf.calledWith("#{@Settings.path.compilesDir}/#{@project_id}") - .should.equal true + it "should remove the project directory", -> + @child_process.spawn + .calledWith("rm", ["-r", "#{@Settings.path.compilesDir}/#{@project_id}"]) + .should.equal true - it "should call the callback", -> - @callback.called.should.equal true + it "should call the callback", -> + @callback.called.should.equal true + + describe "with a non-success status code", -> + beforeEach -> + @Settings.compileDir = "compiles" + @proc = new EventEmitter() + @proc.stdout = new EventEmitter() + @proc.stderr = new EventEmitter() + @child_process.spawn = sinon.stub().returns(@proc) + @CompileManager.clearProject @project_id, @callback + @proc.stderr.emit "data", @error = "oops" + @proc.emit "close", 1 + + it "should remove the project directory", -> + @child_process.spawn + .calledWith("rm", ["-r", "#{@Settings.path.compilesDir}/#{@project_id}"]) + .should.equal true + + it "should call the callback with an error from the stderr", -> + @callback + .calledWith(new Error()) + .should.equal true + + @callback.args[0][0].message.should.equal "rm -r #{@Settings.path.compilesDir}/#{@project_id} failed: #{@error}" From ee4a6e869ebd478df58f90657db5ed1d9edab6c3 Mon Sep 17 00:00:00 2001 From: James Allen Date: Wed, 2 Apr 2014 16:55:14 -0400 Subject: [PATCH 020/754] Fix up settings schema and acceptance tests --- services/clsi/.gitignore | 1 + services/clsi/Gruntfile.coffee | 24 +++--------- services/clsi/config/settings.testing.coffee | 37 ------------------- services/clsi/package.json | 8 ++-- .../coffee/ExampleDocumentTests.coffee | 4 ++ .../acceptance/coffee/helpers/Client.coffee | 4 +- 6 files changed, 17 insertions(+), 61 deletions(-) delete mode 100644 services/clsi/config/settings.testing.coffee diff --git a/services/clsi/.gitignore b/services/clsi/.gitignore index 30aa9829f9..43c4dd14aa 100644 --- a/services/clsi/.gitignore +++ b/services/clsi/.gitignore @@ -12,3 +12,4 @@ app.js cache .vagrant db.sqlite +config/* diff --git a/services/clsi/Gruntfile.coffee b/services/clsi/Gruntfile.coffee index e95b7be7a4..53532e164b 100644 --- a/services/clsi/Gruntfile.coffee +++ b/services/clsi/Gruntfile.coffee @@ -34,27 +34,16 @@ module.exports = (grunt) -> dest: "test/smoke/js" ext: ".js" - watch: - app: - files: ['app/coffee/*.coffee'] - tasks: ['coffee'] - clean: app: ["app/js/"] unit_tests: ["test/unit/js"] acceptance_tests: ["test/acceptance/js"] smoke_tests: ["test/smoke/js"] - nodemon: - dev: - options: - file: 'app.js' - concurrent: - dev: - tasks: ['nodemon', 'watch'] - options: - logConcurrentOutput: true + execute: + app: + src: "app.js" mochaTest: unit: @@ -73,15 +62,14 @@ module.exports = (grunt) -> src: ["test/smoke/js/**/*.js"] grunt.loadNpmTasks 'grunt-contrib-coffee' - grunt.loadNpmTasks 'grunt-contrib-watch' grunt.loadNpmTasks 'grunt-contrib-clean' - grunt.loadNpmTasks 'grunt-nodemon' - grunt.loadNpmTasks 'grunt-concurrent' grunt.loadNpmTasks 'grunt-mocha-test' grunt.loadNpmTasks 'grunt-shell' + grunt.loadNpmTasks 'grunt-execute' + grunt.loadNpmTasks 'grunt-bunyan' grunt.registerTask 'compile:app', ['clean:app', 'coffee:app', 'coffee:app_src', 'coffee:smoke_tests'] - grunt.registerTask 'run', ['compile:app', 'concurrent'] + grunt.registerTask 'run', ['compile:app', 'bunyan', 'execute'] grunt.registerTask 'compile:unit_tests', ['clean:unit_tests', 'coffee:unit_tests'] grunt.registerTask 'test:unit', ['compile:app', 'compile:unit_tests', 'mochaTest:unit'] diff --git a/services/clsi/config/settings.testing.coffee b/services/clsi/config/settings.testing.coffee deleted file mode 100644 index ff7b0087e6..0000000000 --- a/services/clsi/config/settings.testing.coffee +++ /dev/null @@ -1,37 +0,0 @@ -Path = require "path" - -module.exports = - # Options are passed to Sequelize. - # See http://sequelizejs.com/documentation#usage-options for details - mysql: - clsi: - database: "clsi" - username: "clsi" - password: null - dialect: "sqlite" - storage: Path.resolve(__dirname + "/../db.sqlite") - - - path: - compilesDir: Path.resolve(__dirname + "/../compiles") - clsiCacheDir: Path.resolve(__dirname + "/../cache") - - # clsi: - # commandRunner: "docker-runner-sharelatex" - # docker: - # image: "quay.io/sharelatex/texlive-full" - # env: - # PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2013/bin/x86_64-linux/" - # HOME: "/tmp" - # socketPath: "/var/run/docker.sock" - # user: "tex" - - internal: - clsi: - port: 3013 - host: "localhost" - - apis: - clsi: - url: "http://localhost:3013" - diff --git a/services/clsi/package.json b/services/clsi/package.json index 0fdc15ab57..875e6e8566 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -24,13 +24,13 @@ "sinon": "~1.7.3", "grunt": "~0.4.2", "grunt-contrib-coffee": "~0.7.0", - "grunt-contrib-watch": "~0.5.3", - "grunt-concurrent": "~0.4.2", - "grunt-nodemon": "~0.1.2", "grunt-contrib-clean": "~0.5.0", "grunt-shell": "~0.6.1", "grunt-mocha-test": "~0.8.1", "sandboxed-module": "~0.3.0", - "timekeeper": "0.0.4" + "timekeeper": "0.0.4", + "grunt-execute": "^0.1.5", + "bunyan": "^0.22.1", + "grunt-bunyan": "^0.5.0" } } diff --git a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee index 448d22fa06..f7f82ac12c 100644 --- a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee +++ b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee @@ -6,6 +6,10 @@ ChildProcess = require "child_process" fixturePath = (path) -> __dirname + "/../fixtures/" + path +try + fs.mkdirSync(fixturePath("tmp")) +catch e + convertToPng = (pdfPath, pngPath, callback = (error) ->) -> convert = ChildProcess.exec "convert #{fixturePath(pdfPath)} #{fixturePath(pngPath)}" convert.on "exit", () -> diff --git a/services/clsi/test/acceptance/coffee/helpers/Client.coffee b/services/clsi/test/acceptance/coffee/helpers/Client.coffee index bb6ddc4087..7aa57c523e 100644 --- a/services/clsi/test/acceptance/coffee/helpers/Client.coffee +++ b/services/clsi/test/acceptance/coffee/helpers/Client.coffee @@ -1,11 +1,11 @@ request = require "request" fs = require "fs" -Settings = require "../../../../app/js/Settings" +Settings = require "settings-sharelatex" host = "localhost" module.exports = Client = - host: Settings.externalUrl + host: Settings.apis.clsi.url randomId: () -> Math.random().toString(16).slice(2) From 807dfdc22acedc8d0e0f7573fb748ce0441c94c9 Mon Sep 17 00:00:00 2001 From: James Allen Date: Wed, 2 Apr 2014 16:59:44 -0400 Subject: [PATCH 021/754] Add in defaults setting file --- services/clsi/config/settings.defaults.coffee | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 services/clsi/config/settings.defaults.coffee diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee new file mode 100644 index 0000000000..8b76dddedd --- /dev/null +++ b/services/clsi/config/settings.defaults.coffee @@ -0,0 +1,28 @@ +Path = require "path" + +module.exports = + # Options are passed to Sequelize. + # See http://sequelizejs.com/documentation#usage-options for details + mysql: + clsi: + database: "clsi" + username: "clsi" + password: null + dialect: "sqlite" + storage: Path.resolve(__dirname + "/../db.sqlite") + + + path: + compilesDir: Path.resolve(__dirname + "/../compiles") + clsiCacheDir: Path.resolve(__dirname + "/../cache") + + internal: + clsi: + port: 3013 + host: "localhost" + + apis: + clsi: + url: "http://localhost:3013" + + From 5caf5f8a6fec7adf30cd0c3d81a1b45b6b1ab2b4 Mon Sep 17 00:00:00 2001 From: James Allen Date: Thu, 3 Apr 2014 09:03:51 -0400 Subject: [PATCH 022/754] Add in acceptance test for asymptote --- services/clsi/Gruntfile.coffee | 1 + .../acceptance/coffee/helpers/Client.coffee | 2 +- .../fixtures/examples/asymptote/main.tex | 117 ++++++++++++++++++ .../fixtures/examples/asymptote/output.pdf | Bin 0 -> 121369 bytes 4 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 services/clsi/test/acceptance/fixtures/examples/asymptote/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/asymptote/output.pdf diff --git a/services/clsi/Gruntfile.coffee b/services/clsi/Gruntfile.coffee index 53532e164b..e8bc2194f6 100644 --- a/services/clsi/Gruntfile.coffee +++ b/services/clsi/Gruntfile.coffee @@ -54,6 +54,7 @@ module.exports = (grunt) -> options: reporter: "spec" timeout: 40000 + grep: grunt.option("grep") src: ["test/acceptance/js/**/*.js"] smoke: options: diff --git a/services/clsi/test/acceptance/coffee/helpers/Client.coffee b/services/clsi/test/acceptance/coffee/helpers/Client.coffee index 7aa57c523e..b1a8609a77 100644 --- a/services/clsi/test/acceptance/coffee/helpers/Client.coffee +++ b/services/clsi/test/acceptance/coffee/helpers/Client.coffee @@ -22,7 +22,7 @@ module.exports = Client = getOutputFile: (response, type) -> for file in response.compile.outputFiles - if file.type == type + if file.type == type and file.url.match("output.#{type}") return file return null diff --git a/services/clsi/test/acceptance/fixtures/examples/asymptote/main.tex b/services/clsi/test/acceptance/fixtures/examples/asymptote/main.tex new file mode 100644 index 0000000000..910cef5bf1 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/asymptote/main.tex @@ -0,0 +1,117 @@ +\documentclass[12pt]{article} + +% Use this form to include EPS (latex) or PDF (pdflatex) files: +\usepackage{asymptote} + +% Use this form with latex or pdflatex to include inline LaTeX code by default: +%\usepackage[inline]{asymptote} + +% Use this form with latex or pdflatex to create PDF attachments by default: +%\usepackage[attach]{asymptote} + +% Enable this line to support the attach option: +%\usepackage[dvips]{attachfile2} + +\begin{document} + +% Optional subdirectory for asy files (no spaces): +\def\asydir{} + +\begin{asydef} +// Global Asymptote definitions can be put here. +import three; +usepackage("bm"); +texpreamble("\def\V#1{\bm{#1}}"); +// One can globally override the default toolbar settings here: +// settings.toolbar=true; +\end{asydef} + +Here is a venn diagram produced with Asymptote, drawn to width 4cm: + +\def\A{A} +\def\B{\V{B}} + +%\begin{figure} +\begin{center} +\begin{asy} +size(4cm,0); +pen colour1=red; +pen colour2=green; + +pair z0=(0,0); +pair z1=(-1,0); +pair z2=(1,0); +real r=1.5; +path c1=circle(z1,r); +path c2=circle(z2,r); +fill(c1,colour1); +fill(c2,colour2); + +picture intersection=new picture; +fill(intersection,c1,colour1+colour2); +clip(intersection,c2); + +add(intersection); + +draw(c1); +draw(c2); + +//draw("$\A$",box,z1); // Requires [inline] package option. +//draw(Label("$\B$","$B$"),box,z2); // Requires [inline] package option. +draw("$A$",box,z1); +draw("$\V{B}$",box,z2); + +pair z=(0,-2); +real m=3; +margin BigMargin=Margin(0,m*dot(unit(z1-z),unit(z0-z))); + +draw(Label("$A\cap B$",0),conj(z)--z0,Arrow,BigMargin); +draw(Label("$A\cup B$",0),z--z0,Arrow,BigMargin); +draw(z--z1,Arrow,Margin(0,m)); +draw(z--z2,Arrow,Margin(0,m)); + +shipout(bbox(0.25cm)); +\end{asy} +%\caption{Venn diagram}\label{venn} +\end{center} +%\end{figure} + +Each graph is drawn in its own environment. One can specify the width +and height to \LaTeX\ explicitly. This 3D example can be viewed +interactively either with Adobe Reader or Asymptote's fast OpenGL-based +renderer. To support {\tt latexmk}, 3D figures should specify +\verb+inline=true+. It is sometimes desirable to embed 3D files as annotated +attachments; this requires the \verb+attach=true+ option as well as the +\verb+attachfile2+ \LaTeX\ package. +\begin{center} +\begin{asy}[height=4cm,inline=true,attach=false,viewportwidth=\linewidth] +currentprojection=orthographic(5,4,2); +draw(unitcube,blue); +label("$V-E+F=2$",(0,1,0.5),3Y,blue+fontsize(17pt)); +\end{asy} +\end{center} + +One can also scale the figure to the full line width: +\begin{center} +\begin{asy}[width=\the\linewidth,inline=true] +pair z0=(0,0); +pair z1=(2,0); +pair z2=(5,0); +pair zf=z1+0.75*(z2-z1); + +draw(z1--z2); +dot(z1,red+0.15cm); +dot(z2,darkgreen+0.3cm); +label("$m$",z1,1.2N,red); +label("$M$",z2,1.5N,darkgreen); +label("$\hat{\ }$",zf,0.2*S,fontsize(24pt)+blue); + +pair s=-0.2*I; +draw("$x$",z0+s--z1+s,N,red,Arrows,Bars,PenMargins); +s=-0.5*I; +draw("$\bar{x}$",z0+s--zf+s,blue,Arrows,Bars,PenMargins); +s=-0.95*I; +draw("$X$",z0+s--z2+s,darkgreen,Arrows,Bars,PenMargins); +\end{asy} +\end{center} +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/asymptote/output.pdf b/services/clsi/test/acceptance/fixtures/examples/asymptote/output.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0a7fb42c99125edefec74cb4b2c5217690998d39 GIT binary patch literal 121369 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 w3nx#|kz{Z7rQ+kO6Fc`WooZc& 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 JthyZbe*lzx1>67t literal 0 HcmV?d00001 From d2e93fa025179ef2188ade1acf30484b678f3afb Mon Sep 17 00:00:00 2001 From: James Allen Date: Fri, 4 Apr 2014 08:25:20 -0400 Subject: [PATCH 023/754] Add acceptance test for custom makeindex styles --- .../examples/makeindex-custom-style/main.tex | 12 ++++++++++++ .../examples/makeindex-custom-style/output.pdf | Bin 0 -> 31782 bytes .../examples/makeindex-custom-style/style.ist | 7 +++++++ 3 files changed, 19 insertions(+) create mode 100644 services/clsi/test/acceptance/fixtures/examples/makeindex-custom-style/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/makeindex-custom-style/output.pdf create mode 100644 services/clsi/test/acceptance/fixtures/examples/makeindex-custom-style/style.ist diff --git a/services/clsi/test/acceptance/fixtures/examples/makeindex-custom-style/main.tex b/services/clsi/test/acceptance/fixtures/examples/makeindex-custom-style/main.tex new file mode 100644 index 0000000000..fce282624c --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/makeindex-custom-style/main.tex @@ -0,0 +1,12 @@ +\documentclass{article} + +\usepackage{makeidx} +\makeindex + +\begin{document} + +To solve various problems in Physics \index{Physics} it can useful to express any arbitrary piecewise-smooth function as a Fourier Series \index{Fourier Series} composed of multiple sine and cosine funcions. + +\printindex + +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/makeindex-custom-style/output.pdf b/services/clsi/test/acceptance/fixtures/examples/makeindex-custom-style/output.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d7c812de82149a8b16d8c50ac16fe9b43a48ef0a GIT binary patch literal 31782 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%0DU1AZilmN$NVq3&p9?@& zo;ZRFR#D~3V&R6gMLd?U_N280&n-*6If`;Bk1nYLC<%5zZgggwbYExp!2%V(z+Y^i zM^!`aHPm6RFIl1*BFr#*oJh)c^B5sIGrX!=w-4pe14&-R^eyd_paI-hF0jk5iMb$j z(x$0~yc4&7Bv1G2Fe^{ht>5`x2V&b~d?)XQSBuK$J9gJ*HrCbl-w9sFNwG4)GW^!) z)jEO;+q7GZV+UC_22RE(`-UOb#Qx^Mmtq-bxkK zzrTixPuVU(C){J!90?yeRzi z5MD~2J5Lsd)lOG}6L~9}m8Tp2Z!WPjQ8V}&^ffY#_y)Hvk1&&@{!r&}q({a~3n-DH z#(QjLq*NeHBav$6s8(vv%K;ZEABpm+n#5?4sQ<|7w)DGN24|SkSjLjQDiHVbAOK_s zW)K@vX7X;cZj_C^6+#TzFTKX&P6&HfJ!|7UnsoY^YHgig??mDq+FyO(##zQf^TcwZ zwd}`krV%A-ZUsZI^(JBzr{}&x&FX5)O=Nq9)BLDcQi5o>zxJVYy1E#*Dpxs7QEaxH z0XlZFnfE#~`19f7J(lZn$qCwg%n51h`y^w^PV44;!7s&~yMxIxYh9erHXo)*y2`yz zaidEN>Y_})vKz_ur-1M ztj3~UJ*#z<`UUfhBdpFrL(Sks`r)~3^RKJ6@Ytr;GKcOttH{n`1&z!}nD zaaO!4iJZ?|jXyL#pc%6}G=G`#qdY|#lX1PS@kcVv47Lp#^ZX?6iLp>Hsx{k+$|2?} zRf{gzV#78wkQvFR%f&m)@RpR5cm^>k@t2Z%>ziy4qw-=ub#M3c(rW3|mNTn7aj<;Z zO^K|bg=~@S-48N1ROI4qLb$`hkCUho|H;oZk&$X}5vA$8n zF=RtjVHEfP>wx!!za@RWy)uc4`#b7BWhSg>X_9X_aj*z^c`+Gyrlv5Y5+F}TRu3K2iKd6q%QY8+65qxr{FK~I416l>F3**m zaRmUW6XWha8Z#Os+C1HmT0Z9j;$SpvU(mT6xZMPpB#m?Lgx=kmMoYKhFzm4Pu~{TD zyoPzZX>1h_I>0h%rknOO9Q24z&SO8|?1txfHArNniX`yA-`|RMjoPJ*`*YBmmy#p%_u=5Uuuywr1<8|CxHaY z3*1-QOHhI0u{XU;T)a7R$Eq8Gs~yxhY@RCvh|=YzXgIs`DLv6Rl2Vgh3)4@V#kZ

b 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�s@DSyJP?r#r5S< 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 Date: Fri, 4 Apr 2014 08:25:36 -0400 Subject: [PATCH 024/754] Add acceptance test for nomenclature --- .../fixtures/examples/nomenclature/main.tex | 25 ++++++++++++++++++ .../fixtures/examples/nomenclature/output.pdf | Bin 0 -> 33813 bytes 2 files changed, 25 insertions(+) create mode 100644 services/clsi/test/acceptance/fixtures/examples/nomenclature/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/nomenclature/output.pdf diff --git a/services/clsi/test/acceptance/fixtures/examples/nomenclature/main.tex b/services/clsi/test/acceptance/fixtures/examples/nomenclature/main.tex new file mode 100644 index 0000000000..3fb928cc89 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/nomenclature/main.tex @@ -0,0 +1,25 @@ +\documentclass{article} + +\usepackage{nomencl} +\makenomenclature + +\begin{document} + +\section*{Main equations} + +\begin{equation} +a=\frac{N}{A} +\end{equation}% + +\nomenclature{$a$}{The number of angels per unit area}% +\nomenclature{$N$}{The number of angels per needle point}% +\nomenclature{$A$}{The area of the needle point}% + +The equation $\sigma = m a$% +\nomenclature{$\sigma$}{The total mass of angels per unit area}% +\nomenclature{$m$}{The mass of one angel} +follows easily. + +\printnomenclature + +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/nomenclature/output.pdf b/services/clsi/test/acceptance/fixtures/examples/nomenclature/output.pdf new file mode 100644 index 0000000000000000000000000000000000000000..828349615d3c23a158bfb30f17a71a884331e5f5 GIT binary patch literal 33813 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

j0=W|$p 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 UihzM*Y;0m_#HFh0>hHz{0K(tQQ~&?~ literal 0 HcmV?d00001 From 0644cb316d42f311b4a122f3ca94525e08717dfd Mon Sep 17 00:00:00 2001 From: James Allen Date: Fri, 4 Apr 2014 08:26:01 -0400 Subject: [PATCH 025/754] Pick up .ist files in acceptance tests --- services/clsi/test/acceptance/coffee/helpers/Client.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/test/acceptance/coffee/helpers/Client.coffee b/services/clsi/test/acceptance/coffee/helpers/Client.coffee index b1a8609a77..aa452d1a94 100644 --- a/services/clsi/test/acceptance/coffee/helpers/Client.coffee +++ b/services/clsi/test/acceptance/coffee/helpers/Client.coffee @@ -46,7 +46,7 @@ module.exports = Client = return "#{entity}/#{subEntity}" else if stat.isFile() and entity != "output.pdf" extension = entity.split(".").pop() - if ["tex", "bib", "cls", "sty", "pdf_tex", "Rtex"].indexOf(extension) > -1 + if ["tex", "bib", "cls", "sty", "pdf_tex", "Rtex", "ist", "md", "Rmd"].indexOf(extension) > -1 resources.push path: entity content: fs.readFileSync("#{baseDirectory}/#{directory}/#{entity}").toString() From 129cf0bf6293b32730b81b4f81590595a89ed025 Mon Sep 17 00:00:00 2001 From: James Allen Date: Fri, 4 Apr 2014 09:56:20 -0400 Subject: [PATCH 026/754] Add in support for markdown files --- services/clsi/app/coffee/LatexRunner.coffee | 6 ++--- .../markdown-included/chapters/chapter1.md | 17 +++++++++++++ .../examples/markdown-included/main.tex | 9 +++++++ .../examples/markdown-included/output.pdf | Bin 0 -> 34051 bytes .../examples/markdown-standalone/main.md | 23 ++++++++++++++++++ .../examples/markdown-standalone/output.pdf | Bin 0 -> 95563 bytes 6 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 services/clsi/test/acceptance/fixtures/examples/markdown-included/chapters/chapter1.md create mode 100644 services/clsi/test/acceptance/fixtures/examples/markdown-included/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/markdown-included/output.pdf create mode 100644 services/clsi/test/acceptance/fixtures/examples/markdown-standalone/main.md create mode 100644 services/clsi/test/acceptance/fixtures/examples/markdown-standalone/output.pdf diff --git a/services/clsi/app/coffee/LatexRunner.coffee b/services/clsi/app/coffee/LatexRunner.coffee index 8f032cdad8..a35ae0b2d3 100644 --- a/services/clsi/app/coffee/LatexRunner.coffee +++ b/services/clsi/app/coffee/LatexRunner.coffee @@ -12,9 +12,9 @@ module.exports = LatexRunner = logger.log directory: directory, compiler: compiler, timeout: timeout, mainFile: mainFile, "starting compile" - # We want to run latexmk on the tex file which we it will automatically - # generate from the Rtex file. - mainFile = mainFile.replace(/\.Rtex$/, ".tex") + # We want to run latexmk on the tex file which we will automatically + # generate from the Rtex/Rmd/md file. + mainFile = mainFile.replace(/\.(Rtex|md|Rmd)$/, ".tex") if compiler == "pdflatex" command = LatexRunner._pdflatexCommand mainFile diff --git a/services/clsi/test/acceptance/fixtures/examples/markdown-included/chapters/chapter1.md b/services/clsi/test/acceptance/fixtures/examples/markdown-included/chapters/chapter1.md new file mode 100644 index 0000000000..920d9be763 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/markdown-included/chapters/chapter1.md @@ -0,0 +1,17 @@ +Section Title +------------- + +* List item one +* List item two + +: Sample grid table. + ++---------------+---------------+--------------------+ +| Fruit | Price | Advantages | ++===============+===============+====================+ +| Bananas | $1.34 | - built-in wrapper | +| | | - bright color | ++---------------+---------------+--------------------+ +| Oranges | $2.10 | - cures scurvy | +| | | - tasty | ++---------------+---------------+--------------------+ diff --git a/services/clsi/test/acceptance/fixtures/examples/markdown-included/main.tex b/services/clsi/test/acceptance/fixtures/examples/markdown-included/main.tex new file mode 100644 index 0000000000..bfda9624c2 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/markdown-included/main.tex @@ -0,0 +1,9 @@ +\documentclass{article} +\usepackage{longtable} +\usepackage{booktabs, multicol, multirow} + +\begin{document} + +\input{chapters/chapter1} + +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/markdown-included/output.pdf b/services/clsi/test/acceptance/fixtures/examples/markdown-included/output.pdf new file mode 100644 index 0000000000000000000000000000000000000000..81a1136e80d7998772ba056439ecb7aedd4f2383 GIT binary patch literal 34051 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 NHsDfKb@g}S0sy_uxYYmv literal 0 HcmV?d00001 diff --git a/services/clsi/test/acceptance/fixtures/examples/markdown-standalone/main.md b/services/clsi/test/acceptance/fixtures/examples/markdown-standalone/main.md new file mode 100644 index 0000000000..036d971ccd --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/markdown-standalone/main.md @@ -0,0 +1,23 @@ +% Title +% Author +% Date + +Chapter title +============= + +Section Title +------------- + +Hello world. Have a nice table: + +: Sample grid table. + ++---------------+---------------+--------------------+ +| Fruit | Price | Advantages | ++===============+===============+====================+ +| Bananas | $1.34 | - built-in wrapper | +| | | - bright color | ++---------------+---------------+--------------------+ +| Oranges | $2.10 | - cures scurvy | +| | | - tasty | ++---------------+---------------+--------------------+ diff --git a/services/clsi/test/acceptance/fixtures/examples/markdown-standalone/output.pdf b/services/clsi/test/acceptance/fixtures/examples/markdown-standalone/output.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9991ab82aefe9b86c25eee886e971bbf2e5acb7a GIT binary patch literal 95563 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 literal 0 HcmV?d00001 From 892e6bf6d1f508d03706da40664ec3b31ef0999b Mon Sep 17 00:00:00 2001 From: James Allen Date: Tue, 8 Apr 2014 15:18:56 +0100 Subject: [PATCH 027/754] Add in a synctex end point --- services/clsi/.gitignore | 1 + services/clsi/Gruntfile.coffee | 11 +- services/clsi/app.coffee | 3 + .../clsi/app/coffee/CompileController.coffee | 24 + .../clsi/app/coffee/CompileManager.coffee | 63 + services/clsi/app/coffee/LatexRunner.coffee | 8 +- services/clsi/config/settings.defaults.coffee | 1 + services/clsi/src/synctex.c | 66 + services/clsi/src/synctex/synctex_parser.c | 4249 +++++++++++++++++ services/clsi/src/synctex/synctex_parser.h | 346 ++ .../clsi/src/synctex/synctex_parser_local.h | 45 + .../src/synctex/synctex_parser_readme.txt | 141 + .../clsi/src/synctex/synctex_parser_utils.c | 479 ++ .../clsi/src/synctex/synctex_parser_utils.h | 141 + .../src/synctex/synctex_parser_version.txt | 1 + .../acceptance/coffee/SynctexTests.coffee | 38 + .../acceptance/coffee/helpers/Client.coffee | 23 + .../unit/coffee/CompileControllerTests.coffee | 59 + .../unit/coffee/CompileManagerTests.coffee | 66 + 19 files changed, 5760 insertions(+), 5 deletions(-) create mode 100644 services/clsi/src/synctex.c create mode 100644 services/clsi/src/synctex/synctex_parser.c create mode 100644 services/clsi/src/synctex/synctex_parser.h create mode 100644 services/clsi/src/synctex/synctex_parser_local.h create mode 100644 services/clsi/src/synctex/synctex_parser_readme.txt create mode 100644 services/clsi/src/synctex/synctex_parser_utils.c create mode 100644 services/clsi/src/synctex/synctex_parser_utils.h create mode 100644 services/clsi/src/synctex/synctex_parser_version.txt create mode 100644 services/clsi/test/acceptance/coffee/SynctexTests.coffee diff --git a/services/clsi/.gitignore b/services/clsi/.gitignore index 43c4dd14aa..99e9760877 100644 --- a/services/clsi/.gitignore +++ b/services/clsi/.gitignore @@ -13,3 +13,4 @@ cache .vagrant db.sqlite config/* +bin/synctex diff --git a/services/clsi/Gruntfile.coffee b/services/clsi/Gruntfile.coffee index e8bc2194f6..c9be97fd3d 100644 --- a/services/clsi/Gruntfile.coffee +++ b/services/clsi/Gruntfile.coffee @@ -1,3 +1,5 @@ +spawn = require("child_process").spawn + module.exports = (grunt) -> grunt.initConfig coffee: @@ -40,7 +42,6 @@ module.exports = (grunt) -> acceptance_tests: ["test/acceptance/js"] smoke_tests: ["test/smoke/js"] - execute: app: src: "app.js" @@ -69,6 +70,14 @@ module.exports = (grunt) -> grunt.loadNpmTasks 'grunt-execute' grunt.loadNpmTasks 'grunt-bunyan' + grunt.registerTask 'compile:bin', () -> + callback = @async() + proc = spawn "cc", [ + "-o", "bin/synctex", "-lz", "-Isrc/synctex", + "src/synctex.c", "src/synctex/synctex_parser.c", "src/synctex/synctex_parser_utils.c" + ], stdio: "inherit" + proc.on "close", callback + grunt.registerTask 'compile:app', ['clean:app', 'coffee:app', 'coffee:app_src', 'coffee:smoke_tests'] grunt.registerTask 'run', ['compile:app', 'bunyan', 'execute'] diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 3869bf8132..5084ffb1ad 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -14,6 +14,9 @@ app = express() app.post "/project/:project_id/compile", express.bodyParser(), CompileController.compile app.del "/project/:project_id", CompileController.clearCache +app.get "/project/:project_id/sync/code", CompileController.syncFromCode +app.get "/project/:project_id/sync/pdf", CompileController.syncFromPdf + staticServer = express.static(Settings.path.compilesDir) app.get "/project/:project_id/output/*", (req, res, next) -> req.url = "/#{req.params.project_id}/#{req.params[0]}" diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index c513450e8d..090c3f09fc 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -38,3 +38,27 @@ module.exports = CompileController = ProjectPersistenceManager.clearProject req.params.project_id, (error) -> return next(error) if error? res.send 204 # No content + + syncFromCode: (req, res, next = (error) ->) -> + file = req.query.file + line = parseInt(req.query.line, 10) + column = parseInt(req.query.column, 10) + project_id = req.params.project_id + + CompileManager.syncFromCode project_id, file, line, column, (error, pdfPositions) -> + return next(error) if error? + res.send JSON.stringify { + pdf: pdfPositions + } + + syncFromPdf: (req, res, next = (error) ->) -> + page = parseInt(req.query.page, 10) + h = parseFloat(req.query.h) + v = parseFloat(req.query.v) + project_id = req.params.project_id + + CompileManager.syncFromPdf project_id, page, h, v, (error, codePositions) -> + return next(error) if error? + res.send JSON.stringify { + code: codePositions + } diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index dce6a86365..0cca3335c1 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -52,3 +52,66 @@ module.exports = CompileManager = return callback(null) else return callback(new Error("rm -r #{compileDir} failed: #{stderr}")) + + syncFromCode: (project_id, file_name, line, column, callback = (error, pdfPositions) ->) -> + # If LaTeX was run in a virtual environment, the file path that synctex expects + # might not match the file path on the host. The .synctex.gz file however, will be accessed + # wherever it is on the host. + base_dir = Settings.path.synctexBaseDir(project_id) + file_path = Path.join(base_dir, file_name) + synctex_path = Path.join(Settings.path.compilesDir, project_id, "output.pdf") + CompileManager._runSynctex ["code", synctex_path, file_path, line, column], (error, stdout) -> + return callback(error) if error? + logger.log project_id: project_id, file_name: file_name, line: line, column: column, stdout: stdout, "synctex code output" + callback null, CompileManager._parseSynctexFromCodeOutput(stdout) + + syncFromPdf: (project_id, page, h, v, callback = (error, filePositions) ->) -> + base_dir = Settings.path.synctexBaseDir(project_id) + synctex_path = Path.join(Settings.path.compilesDir, project_id, "output.pdf") + CompileManager._runSynctex ["pdf", synctex_path, page, h, v], (error, stdout) -> + return callback(error) if error? + logger.log project_id: project_id, page: page, h: h, v:v, stdout: stdout, "synctex pdf output" + callback null, CompileManager._parseSynctexFromPdfOutput(stdout, base_dir) + + _runSynctex: (args, callback = (error, stdout) ->) -> + bin_path = Path.resolve(__dirname + "/../../bin/synctex") + proc = child_process.spawn bin_path, args + proc.on "error", callback + + stdout = "" + proc.stdout.on "data", (chunk) -> stdout += chunk.toString() + stderr = "" + proc.stderr.on "data", (chunk) -> stderr += chunk.toString() + + proc.on "close", (code) -> + if code == 0 + return callback(null, stdout) + else + return callback(new Error("synctex failed: #{stderr}")) + + _parseSynctexFromCodeOutput: (output) -> + results = [] + for line in output.split("\n") + [node, page, h, v, width, height] = line.split("\t") + if node == "NODE" + results.push { + page: parseInt(page, 10) + h: parseFloat(h) + v: parseFloat(v) + height: parseFloat(height) + width: parseFloat(width) + } + return results + + _parseSynctexFromPdfOutput: (output, base_dir) -> + results = [] + for line in output.split("\n") + [node, file_path, line, column] = line.split("\t") + if node == "NODE" + file = file_path.slice(base_dir.length + 1) + results.push { + file: file + line: parseInt(line, 10) + column: parseInt(column, 10) + } + return results \ No newline at end of file diff --git a/services/clsi/app/coffee/LatexRunner.coffee b/services/clsi/app/coffee/LatexRunner.coffee index a35ae0b2d3..cd6e356195 100644 --- a/services/clsi/app/coffee/LatexRunner.coffee +++ b/services/clsi/app/coffee/LatexRunner.coffee @@ -33,25 +33,25 @@ module.exports = LatexRunner = _pdflatexCommand: (mainFile) -> LatexRunner._latexmkBaseCommand.concat [ - "-pdf", "-e", "$pdflatex='pdflatex -interaction=batchmode %O %S'", + "-pdf", "-e", "$pdflatex='pdflatex -synctex=1 -interaction=batchmode %O %S'", Path.join("$COMPILE_DIR", mainFile) ] _latexCommand: (mainFile) -> LatexRunner._latexmkBaseCommand.concat [ - "-pdfdvi", "-e", "$latex='latex -interaction=batchmode %O %S'", + "-pdfdvi", "-e", "$latex='latex -synctex=1 -interaction=batchmode %O %S'", Path.join("$COMPILE_DIR", mainFile) ] _xelatexCommand: (mainFile) -> LatexRunner._latexmkBaseCommand.concat [ - "-xelatex", "-e", "$pdflatex='xelatex -interaction=batchmode %O %S'", + "-xelatex", "-e", "$pdflatex='xelatex -synctex=1 -interaction=batchmode %O %S'", Path.join("$COMPILE_DIR", mainFile) ] _lualatexCommand: (mainFile) -> LatexRunner._latexmkBaseCommand.concat [ - "-pdf", "-e", "$pdflatex='lualatex -interaction=batchmode %O %S'", + "-pdf", "-e", "$pdflatex='lualatex -synctex=1 -interaction=batchmode %O %S'", Path.join("$COMPILE_DIR", mainFile) ] diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index 8b76dddedd..18783ec4ed 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -15,6 +15,7 @@ module.exports = path: compilesDir: Path.resolve(__dirname + "/../compiles") clsiCacheDir: Path.resolve(__dirname + "/../cache") + synctexBaseDir: (project_id) -> Path.join(@compilesDir, project_id) internal: clsi: diff --git a/services/clsi/src/synctex.c b/services/clsi/src/synctex.c new file mode 100644 index 0000000000..5267f81f9c --- /dev/null +++ b/services/clsi/src/synctex.c @@ -0,0 +1,66 @@ +#include +#include +#include +#include + +#include "synctex/synctex_parser.h" + + +void print_usage() { + fprintf (stderr, "Usage: synctex code \n"); + fprintf (stderr, " synctex pdf \n"); +} + +int main(int argc, char *argv[], char *envp[]) { + synctex_scanner_t scanner; + + if (argc < 6 || (strcmp(argv[1], "code") != 0 && strcmp(argv[1], "pdf") != 0)) { + print_usage(); + return EXIT_FAILURE; + } + + const char* direction = argv[1]; + const char* synctex_file = argv[2]; + + scanner = synctex_scanner_new_with_output_file(synctex_file, NULL, 1); + + if(!(scanner = synctex_scanner_parse(scanner))) { + fprintf (stderr, "Could not parse output file\n"); + return EXIT_FAILURE; + } + + if (strcmp(direction, "code") == 0) { + const char* name = argv[3]; + int line = atoi(argv[4]); + int column = atoi(argv[5]); + + if(synctex_display_query(scanner, name, line, column) > 0) { + synctex_node_t node; + while((node = synctex_next_result(scanner))) { + int page = synctex_node_page(node); + float h = synctex_node_box_visible_h(node); + float v = synctex_node_box_visible_v(node); + float width = synctex_node_box_visible_width(node); + float height = synctex_node_box_visible_height(node); + printf ("NODE\t%d\t%.2f\t%.2f\t%.2f\t%.2f\n", page, h, v, width, height); + } + } + } else if (strcmp(direction, "pdf") == 0) { + int page = atoi(argv[3]); + float h = atof(argv[4]); + float v = atof(argv[5]); + + if(synctex_edit_query(scanner, page, h, v) > 0) { + synctex_node_t node; + while((node = synctex_next_result(scanner))) { + int tag = synctex_node_tag(node); + const char* name = synctex_scanner_get_name(scanner, tag); + int line = synctex_node_line(node); + int column = synctex_node_column(node); + printf ("NODE\t%s\t%d\t%d\n", name, line, column); + } + } + } + + return 0; +} \ No newline at end of file diff --git a/services/clsi/src/synctex/synctex_parser.c b/services/clsi/src/synctex/synctex_parser.c new file mode 100644 index 0000000000..0d3de08b48 --- /dev/null +++ b/services/clsi/src/synctex/synctex_parser.c @@ -0,0 +1,4249 @@ +/* +Copyright (c) 2008, 2009, 2010 , 2011 jerome DOT laurens AT u-bourgogne DOT fr + +This file is part of the SyncTeX package. + +Latest Revision: Tue Jun 14 08:23:30 UTC 2011 + +Version: 1.16 + +See synctex_parser_readme.txt for more details + +License: +-------- +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE + +Except as contained in this notice, the name of the copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written +authorization from the copyright holder. + +Acknowledgments: +---------------- +The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh, +and significant help from XeTeX developer Jonathan Kew + +Nota Bene: +---------- +If you include or use a significant part of the synctex package into a software, +I would appreciate to be listed as contributor and see "SyncTeX" highlighted. + +Version 1 +Thu Jun 19 09:39:21 UTC 2008 + +*/ + +/* We assume that high level application like pdf viewers will want + * to embed this code as is. We assume that they also have locale.h and setlocale. + * For other tools such as TeXLive tools, you must define SYNCTEX_USE_LOCAL_HEADER, + * when building. You also have to create and customize synctex_parser_local.h to fit your system. + * In particular, the HAVE_LOCALE_H and HAVE_SETLOCALE macros should be properly defined. + * With this design, you should not need to edit this file. */ + +# if defined(SYNCTEX_USE_LOCAL_HEADER) +# include "synctex_parser_local.h" +# else +# define HAVE_LOCALE_H 1 +# define HAVE_SETLOCALE 1 +# if defined(_MSC_VER) +# define SYNCTEX_INLINE __inline +# else +# define SYNCTEX_INLINE inline +# endif +# endif + +#include +#include +#include +#include +#include + +#if defined(HAVE_LOCALE_H) +#include +#endif + +/* The data is organized in a graph with multiple entries. + * The root object is a scanner, it is created with the contents on a synctex file. + * Each leaf of the tree is a synctex_node_t object. + * There are 3 subtrees, two of them sharing the same leaves. + * The first tree is the list of input records, where input file names are associated with tags. + * The second tree is the box tree as given by TeX when shipping pages out. + * First level objects are sheets, containing boxes, glues, kerns... + * The third tree allows to browse leaves according to tag and line. + */ + +#include "synctex_parser.h" +#include "synctex_parser_utils.h" + +/* These are the possible extensions of the synctex file */ +const char * synctex_suffix = ".synctex"; +const char * synctex_suffix_gz = ".gz"; + +/* each synctex node has a class */ +typedef struct __synctex_class_t _synctex_class_t; +typedef _synctex_class_t * synctex_class_t; + + +/* synctex_node_t is a pointer to a node + * _synctex_node is the target of the synctex_node_t pointer + * It is a pseudo object oriented program. + * class is a pointer to the class object the node belongs to. + * implementation is meant to contain the private data of the node + * basically, there are 2 kinds of information: navigation information and + * synctex information. Both will depend on the type of the node, + * thus different nodes will have different private data. + * There is no inheritancy overhead. + */ +typedef union _synctex_info_t { + int INT; + char * PTR; +} synctex_info_t; + +struct _synctex_node { + synctex_class_t class; + synctex_info_t * implementation; +}; + +/* Each node of the tree, except the scanner itself belongs to a class. + * The class object is just a struct declaring the owning scanner + * This is a pointer to the scanner as root of the tree. + * The type is used to identify the kind of node. + * The class declares pointers to a creator and a destructor method. + * The log and display fields are used to log and display the node. + * display will also display the child, sibling and parent sibling. + * parent, child and sibling are used to navigate the tree, + * from TeX box hierarchy point of view. + * The friend field points to a method which allows to navigate from friend to friend. + * A friend is a node with very close tag and line numbers. + * Finally, the info field point to a method giving the private node info offset. + */ + +typedef synctex_node_t *(*_synctex_node_getter_t)(synctex_node_t); +typedef synctex_info_t *(*_synctex_info_getter_t)(synctex_node_t); + +struct __synctex_class_t { + synctex_scanner_t scanner; + int type; + synctex_node_t (*new)(synctex_scanner_t scanner); + void (*free)(synctex_node_t); + void (*log)(synctex_node_t); + void (*display)(synctex_node_t); + _synctex_node_getter_t parent; + _synctex_node_getter_t child; + _synctex_node_getter_t sibling; + _synctex_node_getter_t friend; + _synctex_node_getter_t next_box; + _synctex_info_getter_t info; +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Abstract OBJECTS and METHODS +# endif + +/* These macros are shortcuts + * This macro checks if a message can be sent. + */ +# define SYNCTEX_CAN_PERFORM(NODE,SELECTOR)\ + (NULL!=((((NODE)->class))->SELECTOR)) + +/* This macro is some kind of objc_msg_send. + * It takes care of sending the proper message if possible. + */ +# define SYNCTEX_MSG_SEND(NODE,SELECTOR) if (NODE && SYNCTEX_CAN_PERFORM(NODE,SELECTOR)) {\ + (*((((NODE)->class))->SELECTOR))(NODE);\ + } + +/* read only safe getter + */ +# define SYNCTEX_GET(NODE,SELECTOR)((NODE && SYNCTEX_CAN_PERFORM(NODE,SELECTOR))?SYNCTEX_GETTER(NODE,SELECTOR)[0]:(NULL)) + +/* read/write getter + */ +# define SYNCTEX_GETTER(NODE,SELECTOR)\ + ((synctex_node_t *)((*((((NODE)->class))->SELECTOR))(NODE))) + +# define SYNCTEX_FREE(NODE) SYNCTEX_MSG_SEND(NODE,free); + +/* Parent getter and setter + */ +# define SYNCTEX_PARENT(NODE) SYNCTEX_GET(NODE,parent) +# define SYNCTEX_SET_PARENT(NODE,NEW_PARENT) if (NODE && NEW_PARENT && SYNCTEX_CAN_PERFORM(NODE,parent)){\ + SYNCTEX_GETTER(NODE,parent)[0]=NEW_PARENT;\ + } + +/* Child getter and setter + */ +# define SYNCTEX_CHILD(NODE) SYNCTEX_GET(NODE,child) +# define SYNCTEX_SET_CHILD(NODE,NEW_CHILD) if (NODE && NEW_CHILD){\ + SYNCTEX_GETTER(NODE,child)[0]=NEW_CHILD;\ + SYNCTEX_GETTER(NEW_CHILD,parent)[0]=NODE;\ + } + +/* Sibling getter and setter + */ +# define SYNCTEX_SIBLING(NODE) SYNCTEX_GET(NODE,sibling) +# define SYNCTEX_SET_SIBLING(NODE,NEW_SIBLING) if (NODE && NEW_SIBLING) {\ + SYNCTEX_GETTER(NODE,sibling)[0]=NEW_SIBLING;\ + if (SYNCTEX_CAN_PERFORM(NEW_SIBLING,parent) && SYNCTEX_CAN_PERFORM(NODE,parent)) {\ + SYNCTEX_GETTER(NEW_SIBLING,parent)[0]=SYNCTEX_GETTER(NODE,parent)[0];\ + }\ + } +/* Friend getter and setter. A friend is a kern, math, glue or void box node which tag and line numbers are similar. + * This is a first filter on the nodes that avoids testing all of them. + * Friends are used mainly in forward synchronization aka from source to output. + */ +# define SYNCTEX_FRIEND(NODE) SYNCTEX_GET(NODE,friend) +# define SYNCTEX_SET_FRIEND(NODE,NEW_FRIEND) if (NODE && NEW_FRIEND){\ + SYNCTEX_GETTER(NODE,friend)[0]=NEW_FRIEND;\ + } + +/* Next box getter and setter. The box tree can be traversed from one horizontal box to the other. + * Navigation starts with the deeper boxes. + */ +# define SYNCTEX_NEXT_HORIZ_BOX(NODE) SYNCTEX_GET(NODE,next_box) +# define SYNCTEX_SET_NEXT_HORIZ_BOX(NODE,NEXT_BOX) if (NODE && NEXT_BOX){\ + SYNCTEX_GETTER(NODE,next_box)[0]=NEXT_BOX;\ + } + +void _synctex_free_node(synctex_node_t node); +void _synctex_free_leaf(synctex_node_t node); + +/* A node is meant to own its child and sibling. + * It is not owned by its parent, unless it is its first child. + * This destructor is for all nodes with children. + */ +void _synctex_free_node(synctex_node_t node) { + if (node) { + (*((node->class)->sibling))(node); + SYNCTEX_FREE(SYNCTEX_SIBLING(node)); + SYNCTEX_FREE(SYNCTEX_CHILD(node)); + free(node); + } + return; +} + +/* A node is meant to own its child and sibling. + * It is not owned by its parent, unless it is its first child. + * This destructor is for nodes with no child. + */ +void _synctex_free_leaf(synctex_node_t node) { + if (node) { + SYNCTEX_FREE(SYNCTEX_SIBLING(node)); + free(node); + } + return; +} +# ifdef __SYNCTEX_WORK__ +# include "/usr/include/zlib.h" +# else +# include +# endif + +/* The synctex scanner is the root object. + * Is is initialized with the contents of a text file or a gzipped file. + * The buffer_? are first used to parse the text. + */ +struct __synctex_scanner_t { + gzFile file; /* The (possibly compressed) file */ + char * buffer_cur; /* current location in the buffer */ + char * buffer_start; /* start of the buffer */ + char * buffer_end; /* end of the buffer */ + char * output_fmt; /* dvi or pdf, not yet used */ + char * output; /* the output name used to create the scanner */ + char * synctex; /* the .synctex or .synctex.gz name used to create the scanner */ + int version; /* 1, not yet used */ + struct { + unsigned has_parsed:1; /* Whether the scanner has parsed its underlying synctex file. */ + unsigned reserved:sizeof(unsigned)-1; /* alignment */ + } flags; + int pre_magnification; /* magnification from the synctex preamble */ + int pre_unit; /* unit from the synctex preamble */ + int pre_x_offset; /* X offste from the synctex preamble */ + int pre_y_offset; /* Y offset from the synctex preamble */ + int count; /* Number of records, from the synctex postamble */ + float unit; /* real unit, from synctex preamble or post scriptum */ + float x_offset; /* X offset, from synctex preamble or post scriptum */ + float y_offset; /* Y Offset, from synctex preamble or post scriptum */ + synctex_node_t sheet; /* The first sheet node, its siblings are the other sheet nodes */ + synctex_node_t input; /* The first input node, its siblings are the other input nodes */ + int number_of_lists; /* The number of friend lists */ + synctex_node_t * lists_of_friends;/* The friend lists */ + _synctex_class_t class[synctex_node_number_of_types]; /* The classes of the nodes of the scanner */ +}; + +/* SYNCTEX_CUR, SYNCTEX_START and SYNCTEX_END are convenient shortcuts + */ +# define SYNCTEX_CUR (scanner->buffer_cur) +# define SYNCTEX_START (scanner->buffer_start) +# define SYNCTEX_END (scanner->buffer_end) + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark OBJECTS, their creators and destructors. +# endif + +/* Here, we define the indices for the different informations. + * They are used to declare the size of the implementation. + * For example, if one object uses SYNCTEX_HORIZ_IDX is its size, + * then its info will contain a tag, line, column, horiz but no width nor height nor depth + */ + +/* The sheet is a first level node. + * It has no parent (the parent is the scanner itself) + * Its sibling points to another sheet. + * Its child points to its first child, in general a box. + * A sheet node contains only one synctex information: the page. + * This is the 1 based page index as given by TeX. + */ +/* The next macros are used to access the node info + * SYNCTEX_INFO(node) points to the first synctex integer or pointer data of node + * SYNCTEX_INFO(node)[index] is the information at index + * for example, the page of a sheet is stored in SYNCTEX_INFO(sheet)[SYNCTEX_PAGE_IDX] + */ +# define SYNCTEX_INFO(NODE) ((*((((NODE)->class))->info))(NODE)) +# define SYNCTEX_PAGE_IDX 0 +# define SYNCTEX_PAGE(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_PAGE_IDX].INT + +/* This macro defines implementation offsets + * It is only used for pointer values + */ +# define SYNCTEX_MAKE_GET(SYNCTEX_GETTER,OFFSET)\ +synctex_node_t * SYNCTEX_GETTER (synctex_node_t node);\ +synctex_node_t * SYNCTEX_GETTER (synctex_node_t node) {\ + return node?(synctex_node_t *)((&((node)->implementation))+OFFSET):NULL;\ +} +SYNCTEX_MAKE_GET(_synctex_implementation_0,0) +SYNCTEX_MAKE_GET(_synctex_implementation_1,1) +SYNCTEX_MAKE_GET(_synctex_implementation_2,2) +SYNCTEX_MAKE_GET(_synctex_implementation_3,3) +SYNCTEX_MAKE_GET(_synctex_implementation_4,4) +SYNCTEX_MAKE_GET(_synctex_implementation_5,5) + +typedef struct { + synctex_class_t class; + synctex_info_t implementation[3+SYNCTEX_PAGE_IDX+1];/* child, sibling, next box, + * SYNCTEX_PAGE_IDX */ +} synctex_sheet_t; + +synctex_node_t _synctex_new_sheet(synctex_scanner_t scanner); +void _synctex_display_sheet(synctex_node_t sheet); +void _synctex_log_sheet(synctex_node_t sheet); + +static _synctex_class_t synctex_class_sheet = { + NULL, /* No scanner yet */ + synctex_node_type_sheet, /* Node type */ + &_synctex_new_sheet, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_sheet, /* log */ + &_synctex_display_sheet, /* display */ + NULL, /* No parent */ + &_synctex_implementation_0, /* child */ + &_synctex_implementation_1, /* sibling */ + NULL, /* No friend */ + &_synctex_implementation_2, /* Next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 /* info */ +}; + +/* sheet node creator */ +synctex_node_t _synctex_new_sheet(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_sheet_t)); + if (node) { + node->class = scanner?scanner->class+synctex_node_type_sheet:(synctex_class_t)&synctex_class_sheet; + } + return node; +} + +/* A box node contains navigation and synctex information + * There are different kind of boxes. + * Only horizontal boxes are treated differently because of their visible size. + */ +# define SYNCTEX_TAG_IDX 0 +# define SYNCTEX_LINE_IDX (SYNCTEX_TAG_IDX+1) +# define SYNCTEX_COLUMN_IDX (SYNCTEX_LINE_IDX+1) +# define SYNCTEX_HORIZ_IDX (SYNCTEX_COLUMN_IDX+1) +# define SYNCTEX_VERT_IDX (SYNCTEX_HORIZ_IDX+1) +# define SYNCTEX_WIDTH_IDX (SYNCTEX_VERT_IDX+1) +# define SYNCTEX_HEIGHT_IDX (SYNCTEX_WIDTH_IDX+1) +# define SYNCTEX_DEPTH_IDX (SYNCTEX_HEIGHT_IDX+1) +/* the corresponding info accessors */ +# define SYNCTEX_TAG(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_TAG_IDX].INT +# define SYNCTEX_LINE(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_LINE_IDX].INT +# define SYNCTEX_COLUMN(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_COLUMN_IDX].INT +# define SYNCTEX_HORIZ(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HORIZ_IDX].INT +# define SYNCTEX_VERT(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_VERT_IDX].INT +# define SYNCTEX_WIDTH(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_WIDTH_IDX].INT +# define SYNCTEX_HEIGHT(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HEIGHT_IDX].INT +# define SYNCTEX_DEPTH(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_DEPTH_IDX].INT +# define SYNCTEX_ABS_WIDTH(NODE) ((SYNCTEX_WIDTH(NODE)>0?SYNCTEX_WIDTH(NODE):-SYNCTEX_WIDTH(NODE))) +# define SYNCTEX_ABS_HEIGHT(NODE) ((SYNCTEX_HEIGHT(NODE)>0?SYNCTEX_HEIGHT(NODE):-SYNCTEX_HEIGHT(NODE))) +# define SYNCTEX_ABS_DEPTH(NODE) ((SYNCTEX_DEPTH(NODE)>0?SYNCTEX_DEPTH(NODE):-SYNCTEX_DEPTH(NODE))) + +typedef struct { + synctex_class_t class; + synctex_info_t implementation[5+SYNCTEX_DEPTH_IDX+1]; /* parent,child,sibling,friend,next box, + * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, + * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH,SYNCTEX_HEIGHT,SYNCTEX_DEPTH */ +} synctex_vert_box_node_t; + +synctex_node_t _synctex_new_vbox(synctex_scanner_t scanner); +void _synctex_log_box(synctex_node_t sheet); +void _synctex_display_vbox(synctex_node_t node); + +/* These are static class objects, each scanner will make a copy of them and setup the scanner field. + */ +static _synctex_class_t synctex_class_vbox = { + NULL, /* No scanner yet */ + synctex_node_type_vbox, /* Node type */ + &_synctex_new_vbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_box, /* log */ + &_synctex_display_vbox, /* display */ + &_synctex_implementation_0, /* parent */ + &_synctex_implementation_1, /* child */ + &_synctex_implementation_2, /* sibling */ + &_synctex_implementation_3, /* friend */ + &_synctex_implementation_4, /* next box */ + (_synctex_info_getter_t)&_synctex_implementation_5 +}; + +/* vertical box node creator */ +synctex_node_t _synctex_new_vbox(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_vert_box_node_t)); + if (node) { + node->class = scanner?scanner->class+synctex_node_type_vbox:(synctex_class_t)&synctex_class_vbox; + } + return node; +} + +# define SYNCTEX_HORIZ_V_IDX (SYNCTEX_DEPTH_IDX+1) +# define SYNCTEX_VERT_V_IDX (SYNCTEX_HORIZ_V_IDX+1) +# define SYNCTEX_WIDTH_V_IDX (SYNCTEX_VERT_V_IDX+1) +# define SYNCTEX_HEIGHT_V_IDX (SYNCTEX_WIDTH_V_IDX+1) +# define SYNCTEX_DEPTH_V_IDX (SYNCTEX_HEIGHT_V_IDX+1) +/* the corresponding info accessors */ +# define SYNCTEX_HORIZ_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HORIZ_V_IDX].INT +# define SYNCTEX_VERT_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_VERT_V_IDX].INT +# define SYNCTEX_WIDTH_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_WIDTH_V_IDX].INT +# define SYNCTEX_HEIGHT_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HEIGHT_V_IDX].INT +# define SYNCTEX_DEPTH_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_DEPTH_V_IDX].INT +# define SYNCTEX_ABS_WIDTH_V(NODE) ((SYNCTEX_WIDTH_V(NODE)>0?SYNCTEX_WIDTH_V(NODE):-SYNCTEX_WIDTH_V(NODE))) +# define SYNCTEX_ABS_HEIGHT_V(NODE) ((SYNCTEX_HEIGHT_V(NODE)>0?SYNCTEX_HEIGHT_V(NODE):-SYNCTEX_HEIGHT_V(NODE))) +# define SYNCTEX_ABS_DEPTH_V(NODE) ((SYNCTEX_DEPTH_V(NODE)>0?SYNCTEX_DEPTH_V(NODE):-SYNCTEX_DEPTH_V(NODE))) + +/* Horizontal boxes must contain visible size, because 0 width does not mean emptiness */ +typedef struct { + synctex_class_t class; + synctex_info_t implementation[5+SYNCTEX_DEPTH_V_IDX+1]; /*parent,child,sibling,friend,next box, + * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, + * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH,SYNCTEX_HEIGHT,SYNCTEX_DEPTH, + * SYNCTEX_HORIZ_V,SYNCTEX_VERT_V,SYNCTEX_WIDTH_V,SYNCTEX_HEIGHT_V,SYNCTEX_DEPTH_V*/ +} synctex_horiz_box_node_t; + +synctex_node_t _synctex_new_hbox(synctex_scanner_t scanner); +void _synctex_display_hbox(synctex_node_t node); +void _synctex_log_horiz_box(synctex_node_t sheet); + + +static _synctex_class_t synctex_class_hbox = { + NULL, /* No scanner yet */ + synctex_node_type_hbox, /* Node type */ + &_synctex_new_hbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_horiz_box, /* log */ + &_synctex_display_hbox, /* display */ + &_synctex_implementation_0, /* parent */ + &_synctex_implementation_1, /* child */ + &_synctex_implementation_2, /* sibling */ + &_synctex_implementation_3, /* friend */ + &_synctex_implementation_4, /* next box */ + (_synctex_info_getter_t)&_synctex_implementation_5 +}; + +/* horizontal box node creator */ +synctex_node_t _synctex_new_hbox(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_horiz_box_node_t)); + if (node) { + node->class = scanner?scanner->class+synctex_node_type_hbox:(synctex_class_t)&synctex_class_hbox; + } + return node; +} + +/* This void box node implementation is either horizontal or vertical + * It does not contain a child field. + */ +typedef struct { + synctex_class_t class; + synctex_info_t implementation[3+SYNCTEX_DEPTH_IDX+1]; /* parent,sibling,friend, + * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, + * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH,SYNCTEX_HEIGHT,SYNCTEX_DEPTH*/ +} synctex_void_box_node_t; + +synctex_node_t _synctex_new_void_vbox(synctex_scanner_t scanner); +void _synctex_log_void_box(synctex_node_t sheet); +void _synctex_display_void_vbox(synctex_node_t node); + +static _synctex_class_t synctex_class_void_vbox = { + NULL, /* No scanner yet */ + synctex_node_type_void_vbox,/* Node type */ + &_synctex_new_void_vbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_void_box, /* log */ + &_synctex_display_void_vbox,/* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; + +/* vertical void box node creator */ +synctex_node_t _synctex_new_void_vbox(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_void_box_node_t)); + if (node) { + node->class = scanner?scanner->class+synctex_node_type_void_vbox:(synctex_class_t)&synctex_class_void_vbox; + } + return node; +} + +synctex_node_t _synctex_new_void_hbox(synctex_scanner_t scanner); +void _synctex_display_void_hbox(synctex_node_t node); + +static _synctex_class_t synctex_class_void_hbox = { + NULL, /* No scanner yet */ + synctex_node_type_void_hbox,/* Node type */ + &_synctex_new_void_hbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_void_box, /* log */ + &_synctex_display_void_hbox,/* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; + +/* horizontal void box node creator */ +synctex_node_t _synctex_new_void_hbox(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_void_box_node_t)); + if (node) { + node->class = scanner?scanner->class+synctex_node_type_void_hbox:(synctex_class_t)&synctex_class_void_hbox; + } + return node; +} + +/* The medium nodes correspond to kern, glue, penalty and math nodes. */ +typedef struct { + synctex_class_t class; + synctex_info_t implementation[3+SYNCTEX_WIDTH_IDX+1]; /* parent,sibling,friend, + * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, + * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH */ +} synctex_medium_node_t; + +#define SYNCTEX_IS_BOX(NODE)\ + ((NODE->class->type == synctex_node_type_vbox)\ + || (NODE->class->type == synctex_node_type_void_vbox)\ + || (NODE->class->type == synctex_node_type_hbox)\ + || (NODE->class->type == synctex_node_type_void_hbox)) + +#define SYNCTEX_HAS_CHILDREN(NODE) (NODE && SYNCTEX_CHILD(NODE)) + +void _synctex_log_medium_node(synctex_node_t node); + +/* math node creator */ +synctex_node_t _synctex_new_math(synctex_scanner_t scanner); +void _synctex_display_math(synctex_node_t node); + +static _synctex_class_t synctex_class_math = { + NULL, /* No scanner yet */ + synctex_node_type_math, /* Node type */ + &_synctex_new_math, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_medium_node, /* log */ + &_synctex_display_math, /* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; + +synctex_node_t _synctex_new_math(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_medium_node_t)); + if (node) { + node->class = scanner?scanner->class+synctex_node_type_math:(synctex_class_t)&synctex_class_math; + } + return node; +} + +/* kern node creator */ +synctex_node_t _synctex_new_kern(synctex_scanner_t scanner); +void _synctex_display_kern(synctex_node_t node); + +static _synctex_class_t synctex_class_kern = { + NULL, /* No scanner yet */ + synctex_node_type_kern, /* Node type */ + &_synctex_new_kern, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_medium_node, /* log */ + &_synctex_display_kern, /* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; + +synctex_node_t _synctex_new_kern(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_medium_node_t)); + if (node) { + node->class = scanner?scanner->class+synctex_node_type_kern:(synctex_class_t)&synctex_class_kern; + } + return node; +} + +/* The small nodes correspond to glue and boundary nodes. */ +typedef struct { + synctex_class_t class; + synctex_info_t implementation[3+SYNCTEX_VERT_IDX+1]; /* parent,sibling,friend, + * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, + * SYNCTEX_HORIZ,SYNCTEX_VERT */ +} synctex_small_node_t; + +void _synctex_log_small_node(synctex_node_t node); +/* glue node creator */ +synctex_node_t _synctex_new_glue(synctex_scanner_t scanner); +void _synctex_display_glue(synctex_node_t node); + +static _synctex_class_t synctex_class_glue = { + NULL, /* No scanner yet */ + synctex_node_type_glue, /* Node type */ + &_synctex_new_glue, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_medium_node, /* log */ + &_synctex_display_glue, /* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; +synctex_node_t _synctex_new_glue(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_medium_node_t)); + if (node) { + node->class = scanner?scanner->class+synctex_node_type_glue:(synctex_class_t)&synctex_class_glue; + } + return node; +} + +/* boundary node creator */ +synctex_node_t _synctex_new_boundary(synctex_scanner_t scanner); +void _synctex_display_boundary(synctex_node_t node); + +static _synctex_class_t synctex_class_boundary = { + NULL, /* No scanner yet */ + synctex_node_type_boundary, /* Node type */ + &_synctex_new_boundary, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_small_node, /* log */ + &_synctex_display_boundary, /* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; + +synctex_node_t _synctex_new_boundary(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_small_node_t)); + if (node) { + node->class = scanner?scanner->class+synctex_node_type_boundary:(synctex_class_t)&synctex_class_boundary; + } + return node; +} + +# define SYNCTEX_NAME_IDX (SYNCTEX_TAG_IDX+1) +# define SYNCTEX_NAME(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_NAME_IDX].PTR + +/* Input nodes only know about their sibling, which is another input node. + * The synctex information is the SYNCTEX_TAG and SYNCTEX_NAME*/ +typedef struct { + synctex_class_t class; + synctex_info_t implementation[1+SYNCTEX_NAME_IDX+1]; /* sibling, + * SYNCTEX_TAG,SYNCTEX_NAME */ +} synctex_input_t; + +synctex_node_t _synctex_new_input(synctex_scanner_t scanner); +void _synctex_free_input(synctex_node_t node); +void _synctex_display_input(synctex_node_t node); +void _synctex_log_input(synctex_node_t sheet); + +static _synctex_class_t synctex_class_input = { + NULL, /* No scanner yet */ + synctex_node_type_input, /* Node type */ + &_synctex_new_input, /* creator */ + &_synctex_free_input, /* destructor */ + &_synctex_log_input, /* log */ + &_synctex_display_input, /* display */ + NULL, /* No parent */ + NULL, /* No child */ + &_synctex_implementation_0, /* sibling */ + NULL, /* No friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_1 +}; + +synctex_node_t _synctex_new_input(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_input_t)); + if (node) { + node->class = scanner?scanner->class+synctex_node_type_input:(synctex_class_t)&synctex_class_input; + } + return node; +} +void _synctex_free_input(synctex_node_t node){ + if (node) { + SYNCTEX_FREE(SYNCTEX_SIBLING(node)); + free(SYNCTEX_NAME(node)); + free(node); + } +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Navigation +# endif +synctex_node_t synctex_node_parent(synctex_node_t node) +{ + return SYNCTEX_PARENT(node); +} +synctex_node_t synctex_node_sheet(synctex_node_t node) +{ + while(node && node->class->type != synctex_node_type_sheet) { + node = SYNCTEX_PARENT(node); + } + /* exit the while loop either when node is NULL or node is a sheet */ + return node; +} +synctex_node_t synctex_node_child(synctex_node_t node) +{ + return SYNCTEX_CHILD(node); +} +synctex_node_t synctex_node_sibling(synctex_node_t node) +{ + return SYNCTEX_SIBLING(node); +} +synctex_node_t synctex_node_next(synctex_node_t node) { + if (SYNCTEX_CHILD(node)) { + return SYNCTEX_CHILD(node); + } +sibling: + if (SYNCTEX_SIBLING(node)) { + return SYNCTEX_SIBLING(node); + } + if ((node = SYNCTEX_PARENT(node))) { + if (node->class->type == synctex_node_type_sheet) {/* EXC_BAD_ACCESS? */ + return NULL; + } + goto sibling; + } + return NULL; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark CLASS +# endif + +/* Public node accessor: the type */ +synctex_node_type_t synctex_node_type(synctex_node_t node) { + if (node) { + return (((node)->class))->type; + } + return synctex_node_type_error; +} + +/* Public node accessor: the human readable type */ +const char * synctex_node_isa(synctex_node_t node) { +static const char * isa[synctex_node_number_of_types] = + {"Not a node","input","sheet","vbox","void vbox","hbox","void hbox","kern","glue","math","boundary"}; + return isa[synctex_node_type(node)]; +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark SYNCTEX_LOG +# endif + +# define SYNCTEX_LOG(NODE) SYNCTEX_MSG_SEND(NODE,log) + +/* Public node logger */ +void synctex_node_log(synctex_node_t node) { + SYNCTEX_LOG(node); +} + +# define SYNCTEX_DISPLAY(NODE) SYNCTEX_MSG_SEND(NODE,display) + +void synctex_node_display(synctex_node_t node) { + SYNCTEX_DISPLAY(node); +} + +void _synctex_display_input(synctex_node_t node) { + printf("....Input:%i:%s\n", + SYNCTEX_TAG(node), + SYNCTEX_NAME(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_log_sheet(synctex_node_t sheet) { + if (sheet) { + printf("%s:%i\n",synctex_node_isa(sheet),SYNCTEX_PAGE(sheet)); + printf("SELF:%p",(void *)sheet); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(sheet)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(sheet)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(sheet)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(sheet)); + } +} + +void _synctex_log_small_node(synctex_node_t node) { + printf("%s:%i,%i:%i,%i\n", + synctex_node_isa(node), + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node)); + printf("SELF:%p",(void *)node); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); +} + +void _synctex_log_medium_node(synctex_node_t node) { + printf("%s:%i,%i:%i,%i:%i\n", + synctex_node_isa(node), + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node)); + printf("SELF:%p",(void *)node); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); +} + +void _synctex_log_void_box(synctex_node_t node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",SYNCTEX_TAG(node)); + printf(",%i",SYNCTEX_LINE(node)); + printf(",%i",0); + printf(":%i",SYNCTEX_HORIZ(node)); + printf(",%i",SYNCTEX_VERT(node)); + printf(":%i",SYNCTEX_WIDTH(node)); + printf(",%i",SYNCTEX_HEIGHT(node)); + printf(",%i",SYNCTEX_DEPTH(node)); + printf("\nSELF:%p",(void *)node); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); +} + +void _synctex_log_box(synctex_node_t node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",SYNCTEX_TAG(node)); + printf(",%i",SYNCTEX_LINE(node)); + printf(",%i",0); + printf(":%i",SYNCTEX_HORIZ(node)); + printf(",%i",SYNCTEX_VERT(node)); + printf(":%i",SYNCTEX_WIDTH(node)); + printf(",%i",SYNCTEX_HEIGHT(node)); + printf(",%i",SYNCTEX_DEPTH(node)); + printf("\nSELF:%p",(void *)node); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); +} + +void _synctex_log_horiz_box(synctex_node_t node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",SYNCTEX_TAG(node)); + printf(",%i",SYNCTEX_LINE(node)); + printf(",%i",0); + printf(":%i",SYNCTEX_HORIZ(node)); + printf(",%i",SYNCTEX_VERT(node)); + printf(":%i",SYNCTEX_WIDTH(node)); + printf(",%i",SYNCTEX_HEIGHT(node)); + printf(",%i",SYNCTEX_DEPTH(node)); + printf("/%i",SYNCTEX_HORIZ_V(node)); + printf(",%i",SYNCTEX_VERT_V(node)); + printf(":%i",SYNCTEX_WIDTH_V(node)); + printf(",%i",SYNCTEX_HEIGHT_V(node)); + printf(",%i",SYNCTEX_DEPTH_V(node)); + printf("\nSELF:%p",(void *)node); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); +} + +void _synctex_log_input(synctex_node_t node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",SYNCTEX_TAG(node)); + printf(",%s",SYNCTEX_NAME(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); +} + +void _synctex_display_sheet(synctex_node_t sheet) { + if (sheet) { + printf("....{%i\n",SYNCTEX_PAGE(sheet)); + SYNCTEX_DISPLAY(SYNCTEX_CHILD(sheet)); + printf("....}\n"); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(sheet)); + } +} + +void _synctex_display_vbox(synctex_node_t node) { + printf("....[%i,%i:%i,%i:%i,%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node), + SYNCTEX_HEIGHT(node), + SYNCTEX_DEPTH(node)); + SYNCTEX_DISPLAY(SYNCTEX_CHILD(node)); + printf("....]\n"); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_hbox(synctex_node_t node) { + printf("....(%i,%i:%i,%i:%i,%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node), + SYNCTEX_HEIGHT(node), + SYNCTEX_DEPTH(node)); + SYNCTEX_DISPLAY(SYNCTEX_CHILD(node)); + printf("....)\n"); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_void_vbox(synctex_node_t node) { + printf("....v%i,%i;%i,%i:%i,%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node), + SYNCTEX_HEIGHT(node), + SYNCTEX_DEPTH(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_void_hbox(synctex_node_t node) { + printf("....h%i,%i:%i,%i:%i,%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node), + SYNCTEX_HEIGHT(node), + SYNCTEX_DEPTH(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_glue(synctex_node_t node) { + printf("....glue:%i,%i:%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_math(synctex_node_t node) { + printf("....math:%i,%i:%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_kern(synctex_node_t node) { + printf("....kern:%i,%i:%i,%i:%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_boundary(synctex_node_t node) { + printf("....boundary:%i,%i:%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark SCANNER +# endif + +/* Here are gathered all the possible status that the next scanning functions will return. + * All these functions return a status, and pass their result through pointers. + * Negative values correspond to errors. + * The management of the buffer is causing some significant overhead. + * Every function that may access the buffer returns a status related to the buffer and file state. + * status >= SYNCTEX_STATUS_OK means the function worked as expected + * status < SYNCTEX_STATUS_OK means the function did not work as expected + * status == SYNCTEX_STATUS_NOT_OK means the function did not work as expected but there is still some material to parse. + * status == SYNCTEX_STATUS_EOF means the function did not work as expected and there is no more material. + * statusfile) + +/* Actually, the minimum buffer size is driven by integer and float parsing. + * ±0.123456789e123 + */ +# define SYNCTEX_BUFFER_MIN_SIZE 16 +# define SYNCTEX_BUFFER_SIZE 32768 + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Prototypes +# endif +void _synctex_log_void_box(synctex_node_t node); +void _synctex_log_box(synctex_node_t node); +void _synctex_log_horiz_box(synctex_node_t node); +void _synctex_log_input(synctex_node_t node); +synctex_status_t _synctex_buffer_get_available_size(synctex_scanner_t scanner, size_t * size_ptr); +synctex_status_t _synctex_next_line(synctex_scanner_t scanner); +synctex_status_t _synctex_match_string(synctex_scanner_t scanner, const char * the_string); +synctex_status_t _synctex_decode_int(synctex_scanner_t scanner, int* value_ref); +synctex_status_t _synctex_decode_string(synctex_scanner_t scanner, char ** value_ref); +synctex_status_t _synctex_scan_input(synctex_scanner_t scanner); +synctex_status_t _synctex_scan_preamble(synctex_scanner_t scanner); +synctex_status_t _synctex_scan_float_and_dimension(synctex_scanner_t scanner, float * value_ref); +synctex_status_t _synctex_scan_post_scriptum(synctex_scanner_t scanner); +int _synctex_scan_postamble(synctex_scanner_t scanner); +synctex_status_t _synctex_setup_visible_box(synctex_node_t box); +synctex_status_t _synctex_horiz_box_setup_visible(synctex_node_t node,int h, int v); +synctex_status_t _synctex_scan_sheet(synctex_scanner_t scanner, synctex_node_t parent); +synctex_status_t _synctex_scan_nested_sheet(synctex_scanner_t scanner); +synctex_status_t _synctex_scan_content(synctex_scanner_t scanner); +int synctex_scanner_pre_x_offset(synctex_scanner_t scanner); +int synctex_scanner_pre_y_offset(synctex_scanner_t scanner); +const char * synctex_scanner_get_output_fmt(synctex_scanner_t scanner); +int _synctex_node_is_box(synctex_node_t node); +int _synctex_bail(void); + +/* Try to ensure that the buffer contains at least size bytes. + * Passing a huge size argument means the whole buffer length. + * Passing a null size argument means return the available buffer length, without reading the file. + * In that case, the return status is always SYNCTEX_STATUS_OK unless the given scanner is NULL, + * in which case, SYNCTEX_STATUS_BAD_ARGUMENT is returned. + * The value returned in size_ptr is the number of bytes now available in the buffer. + * This is a nonnegative integer, it may take the value 0. + * It is the responsibility of the caller to test whether this size is conforming to its needs. + * Negative values may return in case of error, actually + * when there was an error reading the synctex file. */ +synctex_status_t _synctex_buffer_get_available_size(synctex_scanner_t scanner, size_t * size_ptr) { + size_t available = 0; + if (NULL == scanner || NULL == size_ptr) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } +# define size (* size_ptr) + if (size>SYNCTEX_BUFFER_SIZE){ + size = SYNCTEX_BUFFER_SIZE; + } + available = SYNCTEX_END - SYNCTEX_CUR; /* available is the number of unparsed chars in the buffer */ + if (size<=available) { + /* There are already sufficiently many characters in the buffer */ + size = available; + return SYNCTEX_STATUS_OK; + } + if (SYNCTEX_FILE) { + /* Copy the remaining part of the buffer to the beginning, + * then read the next part of the file */ + int already_read = 0; + if (available) { + memmove(SYNCTEX_START, SYNCTEX_CUR, available); + } + SYNCTEX_CUR = SYNCTEX_START + available; /* the next character after the move, will change. */ + /* Fill the buffer up to its end */ + already_read = gzread(SYNCTEX_FILE,(void *)SYNCTEX_CUR,SYNCTEX_BUFFER_SIZE - available); + if (already_read>0) { + /* We assume that 0already_read) { + /* There is a possible error in reading the file */ + int errnum = 0; + const char * error_string = gzerror(SYNCTEX_FILE, &errnum); + if (Z_ERRNO == errnum) { + /* There is an error in zlib caused by the file system */ + _synctex_error("gzread error from the file system (%i)",errno); + return SYNCTEX_STATUS_ERROR; + } else if (errnum) { + _synctex_error("gzread error (%i:%i,%s)",already_read,errnum,error_string); + return SYNCTEX_STATUS_ERROR; + } + } + /* Nothing was read, we are at the end of the file. */ + gzclose(SYNCTEX_FILE); + SYNCTEX_FILE = NULL; + SYNCTEX_END = SYNCTEX_CUR; + SYNCTEX_CUR = SYNCTEX_START; + * SYNCTEX_END = '\0';/* Terminate the string properly.*/ + size = SYNCTEX_END - SYNCTEX_CUR; + return SYNCTEX_STATUS_EOF; /* there might be a bit of text left */ + } + /* We cannot enlarge the buffer because the end of the file was reached. */ + size = available; + return SYNCTEX_STATUS_EOF; +# undef size +} + +/* Used when parsing the synctex file. + * Advance to the next character starting a line. + * Actually, only '\n' is recognized as end of line marker. + * On normal completion, the returned value is the number of unparsed characters available in the buffer. + * In general, it is a positive value, 0 meaning that the end of file was reached. + * -1 is returned in case of error, actually because there was an error while feeding the buffer. + * When the function returns with no error, SYNCTEX_CUR points to the first character of the next line, if any. + * J. Laurens: Sat May 10 07:52:31 UTC 2008 + */ +synctex_status_t _synctex_next_line(synctex_scanner_t scanner) { + synctex_status_t status = SYNCTEX_STATUS_OK; + size_t available = 0; + if (NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } +infinite_loop: + while(SYNCTEX_CUR=remaining_len) { + /* The buffer is sufficiently big to hold the expected number of characters. */ + if (strncmp((char *)SYNCTEX_CUR,the_string,remaining_len)) { + return SYNCTEX_STATUS_NOT_OK; + } +return_OK: + /* Advance SYNCTEX_CUR to the next character after the_string. */ + SYNCTEX_CUR += remaining_len; + return SYNCTEX_STATUS_OK; + } else if (strncmp((char *)SYNCTEX_CUR,the_string,available)) { + /* No need to goo further, this is not the expected string in the buffer. */ + return SYNCTEX_STATUS_NOT_OK; + } else if (SYNCTEX_FILE) { + /* The buffer was too small to contain remaining_len characters. + * We have to cut the string into pieces. */ + z_off_t offset = 0L; + /* the first part of the string is found, advance the_string to the next untested character. */ + the_string += available; + /* update the remaining length and the parsed length. */ + remaining_len -= available; + tested_len += available; + SYNCTEX_CUR += available; /* We validate the tested characters. */ + if (0 == remaining_len) { + /* Nothing left to test, we have found the given string, we return the length. */ + return tested_len; + } + /* We also have to record the current state of the file cursor because + * if the_string does not match, all this should be a totally blank operation, + * for which the file and buffer states should not be modified at all. + * In fact, the states of the buffer before and after this function are in general different + * but they are totally equivalent as long as the values of the buffer before SYNCTEX_CUR + * can be safely discarded. */ + offset = gztell(SYNCTEX_FILE); + /* offset now corresponds to the first character of the file that was not buffered. */ + available = SYNCTEX_CUR - SYNCTEX_START; /* available can be used as temporary placeholder. */ + /* available now corresponds to the number of chars that where already buffered and + * that match the head of the_string. If in fine the_string does not match, all these chars must be recovered + * because the buffer contents is completely replaced by _synctex_buffer_get_available_size. + * They were buffered from offset-len location in the file. */ + offset -= available; +more_characters: + /* There is still some work to be done, so read another bunch of file. + * This is the second call to _synctex_buffer_get_available_size, + * which means that the actual contents of the buffer will be discarded. + * We will definitely have to recover the previous state in case we do not find the expected string. */ + available = remaining_len; + status = _synctex_buffer_get_available_size(scanner,&available); + if (statusptr) { + SYNCTEX_CUR = end; + if (value_ref) { + * value_ref = result; + } + return SYNCTEX_STATUS_OK;/* Successfully scanned an int */ + } + return SYNCTEX_STATUS_NOT_OK;/* Could not scan an int */ +} + +/* The purpose of this function is to read a string. + * A string is an array of characters from the current parser location + * and before the next '\n' character. + * If a string was properly decoded, it is returned in value_ref and + * the cursor points to the new line marker. + * The returned string was alloced on the heap, the caller is the owner and + * is responsible to free it in due time. + * If no string is parsed, * value_ref is undefined. + * The maximum length of a string that a scanner can decode is platform dependent, namely UINT_MAX. + * If you just want to blindly parse the file up to the end of the current line, + * use _synctex_next_line instead. + * On return, the scanner cursor is unchanged if a string could not be scanned or + * points to the terminating '\n' character otherwise. As a consequence, + * _synctex_next_line is necessary after. + * If either scanner or value_ref is NULL, it is considered as an error and + * SYNCTEX_STATUS_BAD_ARGUMENT is returned. + */ +synctex_status_t _synctex_decode_string(synctex_scanner_t scanner, char ** value_ref) { + char * end = NULL; + size_t current_size = 0; + size_t new_size = 0; + size_t len = 0;/* The number of bytes to copy */ + size_t available = 0; + synctex_status_t status = 0; + if (NULL == scanner || NULL == value_ref) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + /* The buffer must at least contain one character: the '\n' end of line marker */ + if (SYNCTEX_CUR>=SYNCTEX_END) { + available = 1; + status = _synctex_buffer_get_available_size(scanner,&available); + if (status < 0) { + return status; + } + if (0 == available) { + return SYNCTEX_STATUS_EOF; + } + } + /* Now we are sure that there is at least one available character, either because + * SYNCTEX_CUR was already < SYNCTEX_END, or because the buffer has been properly filled. */ + /* end will point to the next unparsed '\n' character in the file, when mapped to the buffer. */ + end = SYNCTEX_CUR; + * value_ref = NULL;/* Initialize, it will be realloc'ed */ + /* We scan all the characters up to the next '\n' */ +next_character: + if (endUINT_MAX-len-1) { + /* But we have reached the limit: we do not have current_size+len+1>UINT_MAX. + * We return the missing amount of memory. + * This will never occur in practice. */ + return UINT_MAX-len-1 - current_size; + } + new_size = current_size+len; + /* We have current_size+len+1<=UINT_MAX + * or equivalently new_sizeUINT_MAX-len-1) { + /* We have reached the limit. */ + _synctex_error("limit reached (missing %i).",current_size-(UINT_MAX-len-1)); + return SYNCTEX_STATUS_ERROR; + } + new_size = current_size+len; + if ((* value_ref = realloc(* value_ref,new_size+1)) != NULL) { + if (memcpy((*value_ref)+current_size,SYNCTEX_CUR,len)) { + (* value_ref)[new_size]='\0'; /* Terminate the string */ + SYNCTEX_CUR = SYNCTEX_END;/* Advance the cursor to the end of the bufer */ + return SYNCTEX_STATUS_OK; + } + free(* value_ref); + * value_ref = NULL; + _synctex_error("could not copy memory (2)."); + return SYNCTEX_STATUS_ERROR; + } + /* Huge memory problem */ + _synctex_error("could not allocate memory (2)."); + return SYNCTEX_STATUS_ERROR; + } +} + +/* Used when parsing the synctex file. + * Read an Input record. + */ +synctex_status_t _synctex_scan_input(synctex_scanner_t scanner) { + synctex_status_t status = 0; + size_t available = 0; + synctex_node_t input = NULL; + if (NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + status = _synctex_match_string(scanner,"Input:"); + if (statusinput); + scanner->input = input; + return _synctex_next_line(scanner);/* read the line termination character, if any */ + /* Now, set up the path */ +} + +typedef synctex_status_t (*synctex_decoder_t)(synctex_scanner_t,void *); + +synctex_status_t _synctex_scan_named(synctex_scanner_t scanner,const char * name,void * value_ref,synctex_decoder_t decoder); + +/* Used when parsing the synctex file. + * Read one of the settings. + * On normal completion, returns SYNCTEX_STATUS_OK. + * On error, returns SYNCTEX_STATUS_ERROR. + * Both arguments must not be NULL. + * On return, the scanner points to the next character after the decoded object whatever it is. + * It is the responsibility of the caller to prepare the scanner for the next line. + */ +synctex_status_t _synctex_scan_named(synctex_scanner_t scanner,const char * name,void * value_ref,synctex_decoder_t decoder) { + synctex_status_t status = 0; + if (NULL == scanner || NULL == name || NULL == value_ref || NULL == decoder) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } +not_found: + status = _synctex_match_string(scanner,name); + if (statusversion),(synctex_decoder_t)&_synctex_decode_int); + if (statusoutput_fmt),(synctex_decoder_t)&_synctex_decode_string); + if (statuspre_magnification),(synctex_decoder_t)&_synctex_decode_int); + if (statuspre_unit),(synctex_decoder_t)&_synctex_decode_int); + if (statuspre_x_offset),(synctex_decoder_t)&_synctex_decode_int); + if (statuspre_y_offset),(synctex_decoder_t)&_synctex_decode_int); + if (status= SYNCTEX_STATUS_OK) { + f *= 72.27f*65536; + } else if (status= SYNCTEX_STATUS_OK) { + f *= 72.27f*65536/2.54f; + } else if (status<0) { + goto report_unit_error; + } else if ((status = _synctex_match_string(scanner,"mm")) >= SYNCTEX_STATUS_OK) { + f *= 72.27f*65536/25.4f; + } else if (status<0) { + goto report_unit_error; + } else if ((status = _synctex_match_string(scanner,"pt")) >= SYNCTEX_STATUS_OK) { + f *= 65536.0f; + } else if (status<0) { + goto report_unit_error; + } else if ((status = _synctex_match_string(scanner,"bp")) >= SYNCTEX_STATUS_OK) { + f *= 72.27f/72*65536.0f; + } else if (status<0) { + goto report_unit_error; + } else if ((status = _synctex_match_string(scanner,"pc")) >= SYNCTEX_STATUS_OK) { + f *= 12.0*65536.0f; + } else if (status<0) { + goto report_unit_error; + } else if ((status = _synctex_match_string(scanner,"sp")) >= SYNCTEX_STATUS_OK) { + f *= 1.0f; + } else if (status<0) { + goto report_unit_error; + } else if ((status = _synctex_match_string(scanner,"dd")) >= SYNCTEX_STATUS_OK) { + f *= 1238.0f/1157*65536.0f; + } else if (status<0) { + goto report_unit_error; + } else if ((status = _synctex_match_string(scanner,"cc")) >= SYNCTEX_STATUS_OK) { + f *= 14856.0f/1157*65536; + } else if (status<0) { + goto report_unit_error; + } else if ((status = _synctex_match_string(scanner,"nd")) >= SYNCTEX_STATUS_OK) { + f *= 685.0f/642*65536; + } else if (status<0) { + goto report_unit_error; + } else if ((status = _synctex_match_string(scanner,"nc")) >= SYNCTEX_STATUS_OK) { + f *= 1370.0f/107*65536; + } else if (status<0) { + goto report_unit_error; + } + *value_ref = f; + return SYNCTEX_STATUS_OK; +} + +/* parse the post scriptum + * SYNCTEX_STATUS_OK is returned on completion + * a negative error is returned otherwise */ +synctex_status_t _synctex_scan_post_scriptum(synctex_scanner_t scanner) { + synctex_status_t status = 0; + char * endptr = NULL; +#ifdef HAVE_SETLOCALE + char * loc = setlocale(LC_NUMERIC, NULL); +#endif + if (NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + /* Scan the file until a post scriptum line is found */ +post_scriptum_not_found: + status = _synctex_match_string(scanner,"Post scriptum:"); + if (statusunit = strtod(SYNCTEX_CUR,&endptr); +#ifdef HAVE_SETLOCALE + setlocale(LC_NUMERIC, loc); +#endif + if (endptr == SYNCTEX_CUR) { + _synctex_error("bad magnification in the post scriptum, a float was expected."); + return SYNCTEX_STATUS_ERROR; + } + if (scanner->unit<=0) { + _synctex_error("bad magnification in the post scriptum, a positive float was expected."); + return SYNCTEX_STATUS_ERROR; + } + SYNCTEX_CUR = endptr; + goto next_line; + } + if (statusx_offset)); + if (statusy_offset)); + if (statuscount),(synctex_decoder_t)&_synctex_decode_int); + if (status < SYNCTEX_STATUS_EOF) { + return status; /* forward the error */ + } else if (status < SYNCTEX_STATUS_OK) { /* No Count record found */ + status = _synctex_next_line(scanner); /* Advance one more line */ + if (statusclass->type) { + case synctex_node_type_hbox: + if (SYNCTEX_INFO(box) != NULL) { + SYNCTEX_HORIZ_V(box) = SYNCTEX_HORIZ(box); + SYNCTEX_VERT_V(box) = SYNCTEX_VERT(box); + SYNCTEX_WIDTH_V(box) = SYNCTEX_WIDTH(box); + SYNCTEX_HEIGHT_V(box) = SYNCTEX_HEIGHT(box); + SYNCTEX_DEPTH_V(box) = SYNCTEX_DEPTH(box); + return SYNCTEX_STATUS_OK; + } + return SYNCTEX_STATUS_ERROR; + } + } + return SYNCTEX_STATUS_BAD_ARGUMENT; +} + +/* This method is sent to an horizontal box to setup the visible size + * Some box have 0 width but do contain text material. + * With this method, one can enlarge the box to contain the given point (h,v). + */ +synctex_status_t _synctex_horiz_box_setup_visible(synctex_node_t node,int h, int v) { +# ifdef __DARWIN_UNIX03 +# pragma unused(v) +# endif + int itsBtm, itsTop; + if (NULL == node || node->class->type != synctex_node_type_hbox) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + if (SYNCTEX_WIDTH_V(node)<0) { + itsBtm = SYNCTEX_HORIZ_V(node); + itsTop = SYNCTEX_HORIZ_V(node)-SYNCTEX_WIDTH_V(node); + if (hitsTop) { + SYNCTEX_WIDTH_V(node) = SYNCTEX_HORIZ_V(node) - h; + } + } else { + itsBtm = SYNCTEX_HORIZ_V(node); + itsTop = SYNCTEX_HORIZ_V(node)+SYNCTEX_WIDTH_V(node); + if (hitsTop) { + SYNCTEX_WIDTH_V(node) = h - SYNCTEX_HORIZ_V(node); + } + } + return SYNCTEX_STATUS_OK; +} + +/* Here are the control characters that strat each line of the synctex output file. + * Their values define the meaning of the line. + */ +# define SYNCTEX_CHAR_BEGIN_SHEET '{' +# define SYNCTEX_CHAR_END_SHEET '}' +# define SYNCTEX_CHAR_BEGIN_VBOX '[' +# define SYNCTEX_CHAR_END_VBOX ']' +# define SYNCTEX_CHAR_BEGIN_HBOX '(' +# define SYNCTEX_CHAR_END_HBOX ')' +# define SYNCTEX_CHAR_ANCHOR '!' +# define SYNCTEX_CHAR_VOID_VBOX 'v' +# define SYNCTEX_CHAR_VOID_HBOX 'h' +# define SYNCTEX_CHAR_KERN 'k' +# define SYNCTEX_CHAR_GLUE 'g' +# define SYNCTEX_CHAR_MATH '$' +# define SYNCTEX_CHAR_BOUNDARY 'x' + +# define SYNCTEX_RETURN(STATUS) return STATUS; + +/* Used when parsing the synctex file. A '{' character has just been parsed. + * The purpose is to gobble everything until the closing '}'. + * Actually only one nesting depth has been observed when using the clip option + * of \includegraphics option. Here we use arbitrary level of depth. + */ +synctex_status_t _synctex_scan_nested_sheet(synctex_scanner_t scanner) { + unsigned int depth = 0; +deeper: + ++depth; + if (_synctex_next_line(scanner)0) { + goto scan_next_line; + } else { + SYNCTEX_RETURN(SYNCTEX_STATUS_OK); + } + } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_BEGIN_SHEET) { + ++SYNCTEX_CUR; + goto deeper; + + } else if (_synctex_next_line(scanner)class->type != synctex_node_type_sheet + || _synctex_next_line(scanner)0){ + _synctex_error("Uncomplete sheet(0)"); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } else { + goto prepare_loop; + } + } + _synctex_bail(); +/* The child loop means that we go do one level, when we just created a box node, + * the next node created is a child of this box. */ +child_loop: + if (SYNCTEX_CURclass->type == synctex_node_type_vbox) { + #define SYNCTEX_UPDATE_BOX_FRIEND(NODE)\ + friend_index = ((SYNCTEX_INFO(NODE))[SYNCTEX_TAG_IDX].INT+(SYNCTEX_INFO(NODE))[SYNCTEX_LINE_IDX].INT)%(scanner->number_of_lists);\ + SYNCTEX_SET_FRIEND(NODE,(scanner->lists_of_friends)[friend_index]);\ + (scanner->lists_of_friends)[friend_index] = NODE; + if (NULL == SYNCTEX_CHILD(parent)) { + /* only void boxes are friends */ + SYNCTEX_UPDATE_BOX_FRIEND(parent); + } + child = parent; + parent = SYNCTEX_PARENT(child); + } else { + _synctex_error("Unexpected end of vbox, ignored."); + } + if (_synctex_next_line(scanner)class->type == synctex_node_type_hbox) { + if (NULL == child) { + /* Only boxes with no children are friends, + * boxes with children are indirectly friends through one of their descendants. */ + SYNCTEX_UPDATE_BOX_FRIEND(parent); + } + /* setting the next horizontal box at the end ensures that a child is recorded before any of its ancestors. */ + SYNCTEX_SET_NEXT_HORIZ_BOX(box,parent); + box = parent; + child = parent; + parent = SYNCTEX_PARENT(child); + } else { + _synctex_error("Unexpected enf of hbox, ignored."); + } + if (_synctex_next_line(scanner)number_of_lists);\ + SYNCTEX_SET_FRIEND(NODE,(scanner->lists_of_friends)[friend_index]);\ + (scanner->lists_of_friends)[friend_index] = NODE; + SYNCTEX_UPDATE_FRIEND(child); + goto sibling_loop; + } else { + _synctex_error("Can't create vbox record."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_VOID_HBOX) { + ++SYNCTEX_CUR; + if (NULL != (child = _synctex_new_void_hbox(scanner)) + && NULL != (info = SYNCTEX_INFO(child))) { + if (SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HEIGHT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_DEPTH_IDX) + || _synctex_next_line(scanner)0){ + _synctex_error("Uncomplete sheet(0)"); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } else { + goto child_loop; + } + } + _synctex_bail(); +/* The vertical loop means that we are on the same level, for example when we just ended a box. + * If a node is created now, it will be a sibling of the current node, sharing the same parent. */ +sibling_loop: + if (SYNCTEX_CUR0){ + goto sibling_loop; + } else { + _synctex_error("Uncomplete sheet(2)"); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } +# undef SYNCTEX_DECODE_FAILED +} + +/* Used when parsing the synctex file + */ +synctex_status_t _synctex_scan_content(synctex_scanner_t scanner) { + synctex_node_t sheet = NULL; + synctex_status_t status = 0; + if (NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + /* set up the lists of friends */ + if (NULL == scanner->lists_of_friends) { + scanner->number_of_lists = 1024; + scanner->lists_of_friends = (synctex_node_t *)_synctex_malloc(scanner->number_of_lists*sizeof(synctex_node_t)); + if (NULL == scanner->lists_of_friends) { + _synctex_error("malloc:2"); + return SYNCTEX_STATUS_ERROR; + } + } + /* Find where this section starts */ +content_not_found: + status = _synctex_match_string(scanner,"Content:"); + if (statussheet); + scanner->sheet = sheet; + sheet = NULL; + /* Now read the list of Inputs between 2 sheets. */ + do { + status = _synctex_scan_input(scanner); + if (status= SYNCTEX_STATUS_OK); + goto next_sheet; +} + +int _synctex_open(const char * output, const char * build_directory, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_modeRef); + +/* Where the synctex scanner is created. */ +synctex_scanner_t synctex_scanner_new_with_output_file(const char * output, const char * build_directory, int parse) { + gzFile file = NULL; + char * synctex = NULL; + synctex_scanner_t scanner = NULL; + synctex_io_mode_t io_mode = 0; + /* Here we assume that int are smaller than void * */ + if (sizeof(int)>sizeof(void*)) { + _synctex_error("INTERNAL INCONSISTENCY: int's are unexpectedly bigger than pointers, bailing out."); + return NULL; + } + /* We ensure that SYNCTEX_BUFFER_SIZE < UINT_MAX, I don't know if it makes sense... */ + if (SYNCTEX_BUFFER_SIZE >= UINT_MAX) { + _synctex_error("SyncTeX BUG: Internal inconsistency, bad SYNCTEX_BUFFER_SIZE (1)"); + return NULL; + } + /* for integers: */ + if (SYNCTEX_BUFFER_SIZE < SYNCTEX_BUFFER_MIN_SIZE) { + _synctex_error("SyncTeX BUG: Internal inconsistency, bad SYNCTEX_BUFFER_SIZE (2)"); + return NULL; + } + /* now open the synctex file */ + if (_synctex_open(output,build_directory,&synctex,&file,synctex_ADD_QUOTES,&io_mode) || !file) { + if (_synctex_open(output,build_directory,&synctex,&file,synctex_DONT_ADD_QUOTES,&io_mode) || !file) { + return NULL; + } + } + scanner = (synctex_scanner_t)_synctex_malloc(sizeof(_synctex_scanner_t)); + if (NULL == scanner) { + _synctex_error("SyncTeX: malloc problem"); + free(synctex); + gzclose(file); + return NULL; + } + /* make a private copy of output for the scanner */ + if (NULL == (scanner->output = (char *)malloc(strlen(output)+1))){ + _synctex_error("! synctex_scanner_new_with_output_file: Memory problem (2), scanner's output is not reliable."); + } else if (scanner->output != strcpy(scanner->output,output)) { + _synctex_error("! synctex_scanner_new_with_output_file: Copy problem, scanner's output is not reliable."); + } + scanner->synctex = synctex;/* Now the scanner owns synctex */ + SYNCTEX_FILE = file; + return parse? synctex_scanner_parse(scanner):scanner; +} + +int __synctex_open(const char * output, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_mode_ref); + +/* This functions opens the file at the "output" given location. + * It manages the problem of quoted filenames that appear with pdftex and filenames containing the space character. + * In TeXLive 2008, the synctex file created with pdftex did contain unexpected quotes. + * This function will remove them if possible. + * All the reference arguments will take a value on return. They must be non NULL. + * 0 on success, non 0 on error. */ +int __synctex_open(const char * output, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_mode_ref) { + if (synctex_name_ref && file_ref && io_mode_ref) { + /* 1 local variables that uses dynamic memory */ + char * synctex_name = NULL; + gzFile the_file = NULL; + char * quoteless_synctex_name = NULL; + size_t size = 0; + synctex_io_mode_t io_mode = *io_mode_ref; + const char * mode = _synctex_get_io_mode_name(io_mode); + /* now create the synctex file name */ + size = strlen(output)+strlen(synctex_suffix)+strlen(synctex_suffix_gz)+1; + synctex_name = (char *)malloc(size); + if (NULL == synctex_name) { + _synctex_error("! __synctex_open: Memory problem (1)\n"); + return 1; + } + /* we have reserved for synctex enough memory to copy output (including its 2 eventual quotes), both suffices, + * including the terminating character. size is free now. */ + if (synctex_name != strcpy(synctex_name,output)) { + _synctex_error("! __synctex_open: Copy problem\n"); +return_on_error: + free(synctex_name); + free(quoteless_synctex_name); + return 2; + } + /* remove the last path extension if any */ + _synctex_strip_last_path_extension(synctex_name); + if (!strlen(synctex_name)) { + goto return_on_error; + } + /* now insert quotes. */ + if (add_quotes) { + char * quoted = NULL; + if (_synctex_copy_with_quoting_last_path_component(synctex_name,"ed,size) || (NULL == quoted)) { + /* There was an error or quoting does not make sense: */ + goto return_on_error; + } + quoteless_synctex_name = synctex_name; + synctex_name = quoted; + } + /* Now add to synctex_name the first path extension. */ + if (synctex_name != strcat(synctex_name,synctex_suffix)){ + _synctex_error("! __synctex_open: Concatenation problem (can't add suffix '%s')\n",synctex_suffix); + goto return_on_error; + } + /* Add to quoteless_synctex_name as well, if relevant. */ + if (quoteless_synctex_name && (quoteless_synctex_name != strcat(quoteless_synctex_name,synctex_suffix))){ + free(quoteless_synctex_name); + quoteless_synctex_name = NULL; + } + if (NULL == (the_file = gzopen(synctex_name,mode))) { + /* Could not open this file */ + if (errno != ENOENT) { + /* The file does exist, this is a lower level error, I can't do anything. */ + _synctex_error("SyncTeX: could not open %s, error %i\n",synctex_name,errno); + goto return_on_error; + } + /* Apparently, there is no uncompressed synctex file. Try the compressed version */ + if (synctex_name != strcat(synctex_name,synctex_suffix_gz)){ + _synctex_error("! __synctex_open: Concatenation problem (can't add suffix '%s')\n",synctex_suffix_gz); + goto return_on_error; + } + io_mode |= synctex_io_gz_mask; + mode = _synctex_get_io_mode_name(io_mode); /* the file is a compressed and is a binary file, this caused errors on Windows */ + /* Add the suffix to the quoteless_synctex_name as well. */ + if (quoteless_synctex_name && (quoteless_synctex_name != strcat(quoteless_synctex_name,synctex_suffix_gz))){ + free(quoteless_synctex_name); + quoteless_synctex_name = NULL; + } + if (NULL == (the_file = gzopen(synctex_name,mode))) { + /* Could not open this file */ + if (errno != ENOENT) { + /* The file does exist, this is a lower level error, I can't do anything. */ + _synctex_error("SyncTeX: could not open %s, error %i\n",synctex_name,errno); + } + goto return_on_error; + } + } + /* At this point, the file is properly open. + * If we are in the add_quotes mode, we change the file name by removing the quotes. */ + if (quoteless_synctex_name) { + gzclose(the_file); + if (rename(synctex_name,quoteless_synctex_name)) { + _synctex_error("SyncTeX: could not rename %s to %s, error %i\n",synctex_name,quoteless_synctex_name,errno); + /* We could not rename, reopen the file with the quoted name. */ + if (NULL == (the_file = gzopen(synctex_name,mode))) { + /* No luck, could not re open this file, something has happened meanwhile */ + if (errno != ENOENT) { + /* The file does not exist any more, it has certainly be removed somehow + * this is a lower level error, I can't do anything. */ + _synctex_error("SyncTeX: could not open again %s, error %i\n",synctex_name,errno); + } + goto return_on_error; + } + } else { + /* The file has been successfully renamed */ + if (NULL == (the_file = gzopen(quoteless_synctex_name,mode))) { + /* Could not open this file */ + if (errno != ENOENT) { + /* The file does exist, this is a lower level error, I can't do anything. */ + _synctex_error("SyncTeX: could not open renamed %s, error %i\n",quoteless_synctex_name,errno); + } + goto return_on_error; + } + /* The quote free file name should replace the old one:*/ + free(synctex_name); + synctex_name = quoteless_synctex_name; + quoteless_synctex_name = NULL; + } + } + /* The operation is successfull, return the arguments by value. */ + * file_ref = the_file; + * io_mode_ref = io_mode; + * synctex_name_ref = synctex_name; + return 0; + } + return 3; /* Bad parameter. */ +} + +/* Opens the ouput file, taking into account the eventual build_directory. + * 0 on success, non 0 on error. */ +int _synctex_open(const char * output, const char * build_directory, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_mode_ref) { +# define synctex_name (*synctex_name_ref) +# define the_file (*file_ref) + int result = __synctex_open(output,synctex_name_ref,file_ref,add_quotes,io_mode_ref); + if ((result || !*file_ref) && build_directory && strlen(build_directory)) { + char * build_output; + const char *lpc; + size_t size; + synctex_bool_t is_absolute; + build_output = NULL; + lpc = _synctex_last_path_component(output); + size = strlen(build_directory)+strlen(lpc)+2; /* One for the '/' and one for the '\0'. */ + is_absolute = _synctex_path_is_absolute(build_directory); + if (!is_absolute) { + size += strlen(output); + } + if ((build_output = (char *)malloc(size))) { + if (is_absolute) { + build_output[0] = '\0'; + } else { + if (build_output != strcpy(build_output,output)) { + return -4; + } + build_output[lpc-output]='\0'; + } + if (build_output == strcat(build_output,build_directory)) { + /* Append a path separator if necessary. */ + if (!SYNCTEX_IS_PATH_SEPARATOR(build_output[strlen(build_directory)-1])) { + if (build_output != strcat(build_output,"/")) { + return -2; + } + } + /* Append the last path component of the output. */ + if (build_output != strcat(build_output,lpc)) { + return -3; + } + return __synctex_open(build_output,synctex_name_ref,file_ref,add_quotes,io_mode_ref); + } + } + return -1; + } + return result; +# undef synctex_name +# undef the_file +} + +/* The scanner destructor + */ +void synctex_scanner_free(synctex_scanner_t scanner) { + if (NULL == scanner) { + return; + } + if (SYNCTEX_FILE) { + gzclose(SYNCTEX_FILE); + SYNCTEX_FILE = NULL; + } + SYNCTEX_FREE(scanner->sheet); + SYNCTEX_FREE(scanner->input); + free(SYNCTEX_START); + free(scanner->output_fmt); + free(scanner->output); + free(scanner->synctex); + free(scanner->lists_of_friends); + free(scanner); +} + +/* Where the synctex scanner parses the contents of the file. */ +synctex_scanner_t synctex_scanner_parse(synctex_scanner_t scanner) { + synctex_status_t status = 0; + if (!scanner || scanner->flags.has_parsed) { + return scanner; + } + scanner->flags.has_parsed=1; + scanner->pre_magnification = 1000; + scanner->pre_unit = 8192; + scanner->pre_x_offset = scanner->pre_y_offset = 578; + /* initialize the offset with a fake unprobable value, + * If there is a post scriptum section, this value will be overriden by the real life value */ + scanner->x_offset = scanner->y_offset = 6.027e23f; + scanner->class[synctex_node_type_sheet] = synctex_class_sheet; + scanner->class[synctex_node_type_input] = synctex_class_input; + (scanner->class[synctex_node_type_input]).scanner = scanner; + (scanner->class[synctex_node_type_sheet]).scanner = scanner; + scanner->class[synctex_node_type_vbox] = synctex_class_vbox; + (scanner->class[synctex_node_type_vbox]).scanner = scanner; + scanner->class[synctex_node_type_void_vbox] = synctex_class_void_vbox; + (scanner->class[synctex_node_type_void_vbox]).scanner = scanner; + scanner->class[synctex_node_type_hbox] = synctex_class_hbox; + (scanner->class[synctex_node_type_hbox]).scanner = scanner; + scanner->class[synctex_node_type_void_hbox] = synctex_class_void_hbox; + (scanner->class[synctex_node_type_void_hbox]).scanner = scanner; + scanner->class[synctex_node_type_kern] = synctex_class_kern; + (scanner->class[synctex_node_type_kern]).scanner = scanner; + scanner->class[synctex_node_type_glue] = synctex_class_glue; + (scanner->class[synctex_node_type_glue]).scanner = scanner; + scanner->class[synctex_node_type_math] = synctex_class_math; + (scanner->class[synctex_node_type_math]).scanner = scanner; + scanner->class[synctex_node_type_boundary] = synctex_class_boundary; + (scanner->class[synctex_node_type_boundary]).scanner = scanner; + SYNCTEX_START = (char *)malloc(SYNCTEX_BUFFER_SIZE+1); /* one more character for null termination */ + if (NULL == SYNCTEX_START) { + _synctex_error("SyncTeX: malloc error"); + synctex_scanner_free(scanner); + return NULL; + } + SYNCTEX_END = SYNCTEX_START+SYNCTEX_BUFFER_SIZE; + /* SYNCTEX_END always points to a null terminating character. + * Maybe there is another null terminating character between SYNCTEX_CUR and SYNCTEX_END-1. + * At least, we are sure that SYNCTEX_CUR points to a string covering a valid part of the memory. */ + *SYNCTEX_END = '\0'; + SYNCTEX_CUR = SYNCTEX_END; + status = _synctex_scan_preamble(scanner); + if (statuspre_unit)/65536 pt = (scanner->pre_unit)/65781.76 bp + * 1 pt = 65536 sp */ + if (scanner->pre_unit<=0) { + scanner->pre_unit = 8192; + } + if (scanner->pre_magnification<=0) { + scanner->pre_magnification = 1000; + } + if (scanner->unit <= 0) { + /* no post magnification */ + scanner->unit = scanner->pre_unit / 65781.76;/* 65781.76 or 65536.0*/ + } else { + /* post magnification */ + scanner->unit *= scanner->pre_unit / 65781.76; + } + scanner->unit *= scanner->pre_magnification / 1000.0; + if (scanner->x_offset > 6e23) { + /* no post offset */ + scanner->x_offset = scanner->pre_x_offset * (scanner->pre_unit / 65781.76); + scanner->y_offset = scanner->pre_y_offset * (scanner->pre_unit / 65781.76); + } else { + /* post offset */ + scanner->x_offset /= 65781.76f; + scanner->y_offset /= 65781.76f; + } + return scanner; + #undef SYNCTEX_FILE +} + +/* Scanner accessors. + */ +int synctex_scanner_pre_x_offset(synctex_scanner_t scanner){ + return scanner?scanner->pre_x_offset:0; +} +int synctex_scanner_pre_y_offset(synctex_scanner_t scanner){ + return scanner?scanner->pre_y_offset:0; +} +int synctex_scanner_x_offset(synctex_scanner_t scanner){ + return scanner?scanner->x_offset:0; +} +int synctex_scanner_y_offset(synctex_scanner_t scanner){ + return scanner?scanner->y_offset:0; +} +float synctex_scanner_magnification(synctex_scanner_t scanner){ + return scanner?scanner->unit:1; +} +void synctex_scanner_display(synctex_scanner_t scanner) { + if (NULL == scanner) { + return; + } + printf("The scanner:\noutput:%s\noutput_fmt:%s\nversion:%i\n",scanner->output,scanner->output_fmt,scanner->version); + printf("pre_unit:%i\nx_offset:%i\ny_offset:%i\n",scanner->pre_unit,scanner->pre_x_offset,scanner->pre_y_offset); + printf("count:%i\npost_magnification:%f\npost_x_offset:%f\npost_y_offset:%f\n", + scanner->count,scanner->unit,scanner->x_offset,scanner->y_offset); + printf("The input:\n"); + SYNCTEX_DISPLAY(scanner->input); + if (scanner->count<1000) { + printf("The sheets:\n"); + SYNCTEX_DISPLAY(scanner->sheet); + printf("The friends:\n"); + if (scanner->lists_of_friends) { + int i = scanner->number_of_lists; + synctex_node_t node; + while(i--) { + printf("Friend index:%i\n",i); + node = (scanner->lists_of_friends)[i]; + while(node) { + printf("%s:%i,%i\n", + synctex_node_isa(node), + SYNCTEX_TAG(node), + SYNCTEX_LINE(node) + ); + node = SYNCTEX_FRIEND(node); + } + } + } + } else { + printf("SyncTeX Warning: Too many objects\n"); + } +} +/* Public*/ +const char * synctex_scanner_get_name(synctex_scanner_t scanner,int tag) { + synctex_node_t input = NULL; + if (NULL == scanner) { + return NULL; + } + input = scanner->input; + do { + if (tag == SYNCTEX_TAG(input)) { + return (SYNCTEX_NAME(input)); + } + } while((input = SYNCTEX_SIBLING(input)) != NULL); + return NULL; +} + +int _synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name); +int _synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name) { + synctex_node_t input = NULL; + if (NULL == scanner) { + return 0; + } + input = scanner->input; + do { + if (_synctex_is_equivalent_file_name(name,(SYNCTEX_NAME(input)))) { + return SYNCTEX_TAG(input); + } + } while((input = SYNCTEX_SIBLING(input)) != NULL); + return 0; +} + +int synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name) { + size_t char_index = strlen(name); + if ((scanner = synctex_scanner_parse(scanner)) && (0 < char_index)) { + /* the name is not void */ + char_index -= 1; + if (!SYNCTEX_IS_PATH_SEPARATOR(name[char_index])) { + /* the last character of name is not a path separator */ + int result = _synctex_scanner_get_tag(scanner,name); + if (result) { + return result; + } else { + /* the given name was not the one known by TeX + * try a name relative to the enclosing directory of the scanner->output file */ + const char * relative = name; + const char * ptr = scanner->output; + while((strlen(relative) > 0) && (strlen(ptr) > 0) && (*relative == *ptr)) + { + relative += 1; + ptr += 1; + } + /* Find the last path separator before relative */ + while(relative > name) { + if (SYNCTEX_IS_PATH_SEPARATOR(*(relative-1))) { + break; + } + relative -= 1; + } + if ((relative > name) && (result = _synctex_scanner_get_tag(scanner,relative))) { + return result; + } + if (SYNCTEX_IS_PATH_SEPARATOR(name[0])) { + /* No tag found for the given absolute name, + * Try each relative path starting from the shortest one */ + while(0input:NULL; +} +const char * synctex_scanner_get_output_fmt(synctex_scanner_t scanner) { + return NULL != scanner && scanner->output_fmt?scanner->output_fmt:""; +} +const char * synctex_scanner_get_output(synctex_scanner_t scanner) { + return NULL != scanner && scanner->output?scanner->output:""; +} +const char * synctex_scanner_get_synctex(synctex_scanner_t scanner) { + return NULL != scanner && scanner->synctex?scanner->synctex:""; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Public node attributes +# endif +int synctex_node_h(synctex_node_t node){ + if (!node) { + return 0; + } + return SYNCTEX_HORIZ(node); +} +int synctex_node_v(synctex_node_t node){ + if (!node) { + return 0; + } + return SYNCTEX_VERT(node); +} +int synctex_node_width(synctex_node_t node){ + if (!node) { + return 0; + } + return SYNCTEX_WIDTH(node); +} +int synctex_node_box_h(synctex_node_t node){ + if (!node) { + return 0; + } + if (SYNCTEX_IS_BOX(node)) { +result: + return SYNCTEX_HORIZ(node); + } + if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +int synctex_node_box_v(synctex_node_t node){ + if (!node) { + return 0; + } + if (SYNCTEX_IS_BOX(node)) { +result: + return SYNCTEX_VERT(node); + } + if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +int synctex_node_box_width(synctex_node_t node){ + if (!node) { + return 0; + } + if (SYNCTEX_IS_BOX(node)) { +result: + return SYNCTEX_WIDTH(node); + } + if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +int synctex_node_box_height(synctex_node_t node){ + if (!node) { + return 0; + } + if (SYNCTEX_IS_BOX(node)) { +result: + return SYNCTEX_HEIGHT(node); + } + if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +int synctex_node_box_depth(synctex_node_t node){ + if (!node) { + return 0; + } + if (SYNCTEX_IS_BOX(node)) { +result: + return SYNCTEX_DEPTH(node); + } + if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Public node visible attributes +# endif +float synctex_node_visible_h(synctex_node_t node){ + if (!node) { + return 0; + } + return SYNCTEX_HORIZ(node)*node->class->scanner->unit+node->class->scanner->x_offset; +} +float synctex_node_visible_v(synctex_node_t node){ + if (!node) { + return 0; + } + return SYNCTEX_VERT(node)*node->class->scanner->unit+node->class->scanner->y_offset; +} +float synctex_node_visible_width(synctex_node_t node){ + if (!node) { + return 0; + } + return SYNCTEX_WIDTH(node)*node->class->scanner->unit; +} +float synctex_node_box_visible_h(synctex_node_t node){ + if (!node) { + return 0; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + return SYNCTEX_HORIZ(node)*node->class->scanner->unit+node->class->scanner->x_offset; + case synctex_node_type_hbox: +result: + return SYNCTEX_HORIZ_V(node)*node->class->scanner->unit+node->class->scanner->x_offset; + } + if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +float synctex_node_box_visible_v(synctex_node_t node){ + if (!node) { + return 0; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + return SYNCTEX_VERT(node)*node->class->scanner->unit+node->class->scanner->y_offset; + case synctex_node_type_hbox: +result: + return SYNCTEX_VERT_V(node)*node->class->scanner->unit+node->class->scanner->y_offset; + } + if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +float synctex_node_box_visible_width(synctex_node_t node){ + if (!node) { + return 0; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + return SYNCTEX_WIDTH(node)*node->class->scanner->unit; + case synctex_node_type_hbox: +result: + return SYNCTEX_WIDTH_V(node)*node->class->scanner->unit; + } + if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +float synctex_node_box_visible_height(synctex_node_t node){ + if (!node) { + return 0; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + return SYNCTEX_HEIGHT(node)*node->class->scanner->unit; + case synctex_node_type_hbox: +result: + return SYNCTEX_HEIGHT_V(node)*node->class->scanner->unit; + } + if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +float synctex_node_box_visible_depth(synctex_node_t node){ + if (!node) { + return 0; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + return SYNCTEX_DEPTH(node)*node->class->scanner->unit; + case synctex_node_type_hbox: +result: + return SYNCTEX_DEPTH_V(node)*node->class->scanner->unit; + } + if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Other public node attributes +# endif + +int synctex_node_page(synctex_node_t node){ + synctex_node_t parent = NULL; + if (!node) { + return -1; + } + parent = SYNCTEX_PARENT(node); + while(parent) { + node = parent; + parent = SYNCTEX_PARENT(node); + } + if (node->class->type == synctex_node_type_sheet) { + return SYNCTEX_PAGE(node); + } + return -1; +} +int synctex_node_tag(synctex_node_t node) { + return node?SYNCTEX_TAG(node):-1; +} +int synctex_node_line(synctex_node_t node) { + return node?SYNCTEX_LINE(node):-1; +} +int synctex_node_column(synctex_node_t node) { +# ifdef __DARWIN_UNIX03 +# pragma unused(node) +# endif + return -1; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Sheet +# endif + +synctex_node_t synctex_sheet_content(synctex_scanner_t scanner,int page) { + if (scanner) { + synctex_node_t sheet = scanner->sheet; + while(sheet) { + if (page == SYNCTEX_PAGE(sheet)) { + return SYNCTEX_CHILD(sheet); + } + sheet = SYNCTEX_SIBLING(sheet); + } + } + return NULL; +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Query +# endif + +int synctex_display_query(synctex_scanner_t scanner,const char * name,int line,int column) { +# ifdef __DARWIN_UNIX03 +# pragma unused(column) +# endif + int tag = synctex_scanner_get_tag(scanner,name); + size_t size = 0; + int friend_index = 0; + int max_line = 0; + synctex_node_t node = NULL; + if (tag == 0) { + printf("SyncTeX Warning: No tag for %s\n",name); + return -1; + } + free(SYNCTEX_START); + SYNCTEX_CUR = SYNCTEX_END = SYNCTEX_START = NULL; + max_line = line < INT_MAX-scanner->number_of_lists ? line+scanner->number_of_lists:INT_MAX; + while(linenumber_of_lists); + if ((node = (scanner->lists_of_friends)[friend_index])) { + do { + if ((synctex_node_type(node)>=synctex_node_type_boundary) + && (tag == SYNCTEX_TAG(node)) + && (line == SYNCTEX_LINE(node))) { + if (SYNCTEX_CUR == SYNCTEX_END) { + size += 16; + SYNCTEX_END = realloc(SYNCTEX_START,size*sizeof(synctex_node_t *)); + SYNCTEX_CUR += SYNCTEX_END - SYNCTEX_START; + SYNCTEX_START = SYNCTEX_END; + SYNCTEX_END = SYNCTEX_START + size*sizeof(synctex_node_t *); + } + *(synctex_node_t *)SYNCTEX_CUR = node; + SYNCTEX_CUR += sizeof(synctex_node_t); + } + } while((node = SYNCTEX_FRIEND(node))); + if (SYNCTEX_START == NULL) { + /* We did not find any matching boundary, retry with glue or kern */ + node = (scanner->lists_of_friends)[friend_index];/* no need to test it again, already done */ + do { + if ((synctex_node_type(node)>=synctex_node_type_kern) + && (tag == SYNCTEX_TAG(node)) + && (line == SYNCTEX_LINE(node))) { + if (SYNCTEX_CUR == SYNCTEX_END) { + size += 16; + SYNCTEX_END = realloc(SYNCTEX_START,size*sizeof(synctex_node_t *)); + SYNCTEX_CUR += SYNCTEX_END - SYNCTEX_START; + SYNCTEX_START = SYNCTEX_END; + SYNCTEX_END = SYNCTEX_START + size*sizeof(synctex_node_t *); + } + *(synctex_node_t *)SYNCTEX_CUR = node; + SYNCTEX_CUR += sizeof(synctex_node_t); + } + } while((node = SYNCTEX_FRIEND(node))); + if (SYNCTEX_START == NULL) { + /* We did not find any matching glue or kern, retry with boxes */ + node = (scanner->lists_of_friends)[friend_index];/* no need to test it again, already done */ + do { + if ((tag == SYNCTEX_TAG(node)) + && (line == SYNCTEX_LINE(node))) { + if (SYNCTEX_CUR == SYNCTEX_END) { + size += 16; + SYNCTEX_END = realloc(SYNCTEX_START,size*sizeof(synctex_node_t *)); + SYNCTEX_CUR += SYNCTEX_END - SYNCTEX_START; + SYNCTEX_START = SYNCTEX_END; + SYNCTEX_END = SYNCTEX_START + size*sizeof(synctex_node_t *); + } + *(synctex_node_t *)SYNCTEX_CUR = node; + SYNCTEX_CUR += sizeof(synctex_node_t); + } + } while((node = SYNCTEX_FRIEND(node))); + } + } + SYNCTEX_END = SYNCTEX_CUR; + /* Now reverse the order to have nodes in display order, and keep just a few nodes */ + if ((SYNCTEX_START) && (SYNCTEX_END)) + { + synctex_node_t * start_ref = (synctex_node_t *)SYNCTEX_START; + synctex_node_t * end_ref = (synctex_node_t *)SYNCTEX_END; + end_ref -= 1; + while(start_ref < end_ref) { + node = *start_ref; + *start_ref = *end_ref; + *end_ref = node; + start_ref += 1; + end_ref -= 1; + } + /* Basically, we keep the first node for each parent. + * More precisely, we keep only nodes that are not descendants of + * their predecessor's parent. */ + start_ref = (synctex_node_t *)SYNCTEX_START; + end_ref = (synctex_node_t *)SYNCTEX_START; + next_end: + end_ref += 1; /* we allways have start_ref<= end_ref*/ + if (end_ref < (synctex_node_t *)SYNCTEX_END) { + node = *end_ref; + while((node = SYNCTEX_PARENT(node))) { + if (SYNCTEX_PARENT(*start_ref) == node) { + goto next_end; + } + } + start_ref += 1; + *start_ref = *end_ref; + goto next_end; + } + start_ref += 1; + SYNCTEX_END = (char *)start_ref; + SYNCTEX_CUR = NULL;// added on behalf of Jose Alliste + return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t);// added on behalf Jan Sundermeyer + } + SYNCTEX_CUR = NULL; + // return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t); removed on behalf Jan Sundermeyer + } +# if defined(__SYNCTEX_STRONG_DISPLAY_QUERY__) + break; +# else + ++line; +# endif + } + return 0; +} + +synctex_node_t synctex_next_result(synctex_scanner_t scanner) { + if (NULL == SYNCTEX_CUR) { + SYNCTEX_CUR = SYNCTEX_START; + } else { + SYNCTEX_CUR+=sizeof(synctex_node_t); + } + if (SYNCTEX_CUR= scanner->unit) {/* scanner->unit must be >0 */ + return 0; + } + /* Convert the given point to scanner integer coordinates */ + hitPoint.h = (h-scanner->x_offset)/scanner->unit; + hitPoint.v = (v-scanner->y_offset)/scanner->unit; + /* We will store in the scanner's buffer the result of the query. */ + free(SYNCTEX_START); + SYNCTEX_START = SYNCTEX_END = SYNCTEX_CUR = NULL; + /* Find the proper sheet */ + sheet = scanner->sheet; + while((sheet) && SYNCTEX_PAGE(sheet) != page) { + sheet = SYNCTEX_SIBLING(sheet); + } + if (NULL == sheet) { + return -1; + } + /* Now sheet points to the sheet node with proper page number */ + /* Here is how we work: + * At first we do not consider the visible box dimensions. This will cover the most frequent cases. + * Then we try with the visible box dimensions. + * We try to find a non void box containing the hit point. + * We browse all the horizontal boxes until we find one containing the hit point. */ + if ((node = SYNCTEX_NEXT_HORIZ_BOX(sheet))) { + do { + if (_synctex_point_in_box(hitPoint,node,synctex_YES)) { + /* Maybe the hitPoint belongs to a contained vertical box. */ +end: + /* This trick is for catching overlapping boxes */ + if ((other_node = SYNCTEX_NEXT_HORIZ_BOX(node))) { + do { + if (_synctex_point_in_box(hitPoint,other_node,synctex_YES)) { + node = _synctex_smallest_container(other_node,node); + } + } while((other_node = SYNCTEX_NEXT_HORIZ_BOX(other_node))); + } + /* node is the smallest horizontal box that contains hitPoint. */ + if ((bestContainer = _synctex_eq_deepest_container(hitPoint,node,synctex_YES))) { + node = bestContainer; + } + _synctex_eq_get_closest_children_in_box(hitPoint,node,&bestNodes,&bestDistances,synctex_YES); + if (bestNodes.right && bestNodes.left) { + if ((SYNCTEX_TAG(bestNodes.right)!=SYNCTEX_TAG(bestNodes.left)) + || (SYNCTEX_LINE(bestNodes.right)!=SYNCTEX_LINE(bestNodes.left)) + || (SYNCTEX_COLUMN(bestNodes.right)!=SYNCTEX_COLUMN(bestNodes.left))) { + if ((SYNCTEX_START = malloc(2*sizeof(synctex_node_t)))) { + if (bestDistances.left>bestDistances.right) { + ((synctex_node_t *)SYNCTEX_START)[0] = bestNodes.right; + ((synctex_node_t *)SYNCTEX_START)[1] = bestNodes.left; + } else { + ((synctex_node_t *)SYNCTEX_START)[0] = bestNodes.left; + ((synctex_node_t *)SYNCTEX_START)[1] = bestNodes.right; + } + SYNCTEX_END = SYNCTEX_START + 2*sizeof(synctex_node_t); + SYNCTEX_CUR = NULL; + return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t); + } + return SYNCTEX_STATUS_ERROR; + } + /* both nodes have the same input coordinates + * We choose the one closest to the hit point */ + if (bestDistances.left>bestDistances.right) { + bestNodes.left = bestNodes.right; + } + bestNodes.right = NULL; + } else if (bestNodes.right) { + bestNodes.left = bestNodes.right; + } else if (!bestNodes.left){ + bestNodes.left = node; + } + if ((SYNCTEX_START = malloc(sizeof(synctex_node_t)))) { + * (synctex_node_t *)SYNCTEX_START = bestNodes.left; + SYNCTEX_END = SYNCTEX_START + sizeof(synctex_node_t); + SYNCTEX_CUR = NULL; + return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t); + } + return SYNCTEX_STATUS_ERROR; + } + } while ((node = SYNCTEX_NEXT_HORIZ_BOX(node))); + /* All the horizontal boxes have been tested, + * None of them contains the hit point. + */ + } + /* We are not lucky */ + if ((node = SYNCTEX_CHILD(sheet))) { + goto end; + } + return 0; +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Utilities +# endif + +int _synctex_bail(void) { + _synctex_error("SyncTeX ERROR\n"); + return -1; +} +/* Rougly speaking, this is: + * node's h coordinate - hitPoint's h coordinate. + * If node is to the right of the hit point, then this distance is positive, + * if node is to the left of the hit point, this distance is negative.*/ +int _synctex_point_h_distance(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible); +int _synctex_point_h_distance(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible) { + if (node) { + int min,med,max; + switch(node->class->type) { + /* The distance between a point and a box is special. + * It is not the euclidian distance, nor something similar. + * We have to take into account the particular layout, + * and the box hierarchy. + * Given a box, there are 9 regions delimited by the lines of the edges of the box. + * The origin being at the top left corner of the page, + * we also give names to the vertices of the box. + * + * 1 | 2 | 3 + * ---A---B---> + * 4 | 5 | 6 + * ---C---D---> + * 7 | 8 | 9 + * v v + */ + case synctex_node_type_hbox: + /* getting the box bounds, taking into account negative width, height and depth. */ + min = visible?SYNCTEX_HORIZ_V(node):SYNCTEX_HORIZ(node); + max = min + (visible?SYNCTEX_ABS_WIDTH_V(node):SYNCTEX_ABS_WIDTH(node)); + /* We allways have min <= max */ + if (hitPoint.h 0 */ + } else if (hitPoint.h>max) { + return max - hitPoint.h; /* regions 3+6+9, result is < 0 */ + } else { + return 0; /* regions 2+5+8, inside the box, except for vertical coordinates */ + } + break; + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + /* getting the box bounds, taking into account negative width, height and depth. + * For these boxes, no visible dimension available */ + min = SYNCTEX_HORIZ(node); + max = min + SYNCTEX_ABS_WIDTH(node); + /* We allways have min <= max */ + if (hitPoint.h 0 */ + } else if (hitPoint.h>max) { + return max - hitPoint.h; /* regions 3+6+9, result is < 0 */ + } else { + return 0; /* regions 2+5+8, inside the box, except for vertical coordinates */ + } + break; + case synctex_node_type_kern: + /* IMPORTANT NOTICE: the location of the kern is recorded AFTER the move. + * The distance to the kern is very special, + * in general, there is no text material in the kern, + * this is why we compute the offset relative to the closest edge of the kern.*/ + max = SYNCTEX_WIDTH(node); + if (max<0) { + min = SYNCTEX_HORIZ(node); + max = min - max; + } else { + min = -max; + max = SYNCTEX_HORIZ(node); + min += max; + } + med = (min+max)/2; + /* positive kern: '.' means text, '>' means kern offset + * ............. + * min>>>>med>>>>max + * ............... + * negative kern: '.' means text, '<' means kern offset + * ............................ + * min<<<max) { + return max - hitPoint.h - 1; /* same kind of penalty */ + } else if (hitPoint.h>med) { + /* do things like if the node had 0 width and was placed at the max edge + 1*/ + return max - hitPoint.h + 1; /* positive, the kern is to the right of the hitPoint */ + } else { + return min - hitPoint.h - 1; /* negative, the kern is to the left of the hitPoint */ + } + case synctex_node_type_glue: + case synctex_node_type_math: + return SYNCTEX_HORIZ(node) - hitPoint.h; + } + } + return INT_MAX;/* We always assume that the node is faraway to the right*/ +} +/* Rougly speaking, this is: + * node's v coordinate - hitPoint's v coordinate. + * If node is at the top of the hit point, then this distance is positive, + * if node is at the bottom of the hit point, this distance is negative.*/ +int _synctex_point_v_distance(synctex_point_t hitPoint, synctex_node_t node,synctex_bool_t visible); +int _synctex_point_v_distance(synctex_point_t hitPoint, synctex_node_t node,synctex_bool_t visible) { +# ifdef __DARWIN_UNIX03 +# pragma unused(visible) +# endif + if (node) { + int min,max; + switch(node->class->type) { + /* The distance between a point and a box is special. + * It is not the euclidian distance, nor something similar. + * We have to take into account the particular layout, + * and the box hierarchy. + * Given a box, there are 9 regions delimited by the lines of the edges of the box. + * The origin being at the top left corner of the page, + * we also give names to the vertices of the box. + * + * 1 | 2 | 3 + * ---A---B---> + * 4 | 5 | 6 + * ---C---D---> + * 7 | 8 | 9 + * v v + */ + case synctex_node_type_hbox: + /* getting the box bounds, taking into account negative width, height and depth. */ + min = SYNCTEX_VERT_V(node); + max = min + SYNCTEX_ABS_DEPTH_V(node); + min -= SYNCTEX_ABS_HEIGHT_V(node); + /* We allways have min <= max */ + if (hitPoint.v 0 */ + } else if (hitPoint.v>max) { + return max - hitPoint.v; /* regions 7+8+9, result is < 0 */ + } else { + return 0; /* regions 4.5.6, inside the box, except for horizontal coordinates */ + } + break; + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + /* getting the box bounds, taking into account negative width, height and depth. */ + min = SYNCTEX_VERT(node); + max = min + SYNCTEX_ABS_DEPTH(node); + min -= SYNCTEX_ABS_HEIGHT(node); + /* We allways have min <= max */ + if (hitPoint.v 0 */ + } else if (hitPoint.v>max) { + return max - hitPoint.v; /* regions 7+8+9, result is < 0 */ + } else { + return 0; /* regions 4.5.6, inside the box, except for horizontal coordinates */ + } + break; + case synctex_node_type_kern: + case synctex_node_type_glue: + case synctex_node_type_math: + return SYNCTEX_VERT(node) - hitPoint.v; + } + } + return INT_MAX;/* We always assume that the node is faraway to the top*/ +} + +SYNCTEX_INLINE static synctex_node_t _synctex_smallest_container(synctex_node_t node, synctex_node_t other_node) { + float height, other_height; + if (SYNCTEX_ABS_WIDTH(node)SYNCTEX_ABS_WIDTH(other_node)) { + return other_node; + } + height = SYNCTEX_ABS_DEPTH(node) + SYNCTEX_ABS_HEIGHT(node); + other_height = SYNCTEX_ABS_DEPTH(other_node) + SYNCTEX_ABS_HEIGHT(other_node); + if (heightother_height) { + return other_node; + } + return node; +} + +synctex_bool_t _synctex_point_in_box(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible) { + if (node) { + if (0 == _synctex_point_h_distance(hitPoint,node,visible) + && 0 == _synctex_point_v_distance(hitPoint,node,visible)) { + return synctex_YES; + } + } + return synctex_NO; +} + +int _synctex_node_distance_to_point(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible) { +# ifdef __DARWIN_UNIX03 +# pragma unused(visible) +# endif + int result = INT_MAX; /* when the distance is meaning less (sheet, input...) */ + if (node) { + int minH,maxH,minV,maxV; + switch(node->class->type) { + /* The distance between a point and a box is special. + * It is not the euclidian distance, nor something similar. + * We have to take into account the particular layout, + * and the box hierarchy. + * Given a box, there are 9 regions delimited by the lines of the edges of the box. + * The origin being at the top left corner of the page, + * we also give names to the vertices of the box. + * + * 1 | 2 | 3 + * ---A---B---> + * 4 | 5 | 6 + * ---C---D---> + * 7 | 8 | 9 + * v v + * In each region, there is a different formula. + * In the end we have a continuous distance which may not be a mathematical distance but who cares. */ + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_hbox: + case synctex_node_type_void_hbox: + /* getting the box bounds, taking into account negative widths. */ + minH = SYNCTEX_HORIZ(node); + maxH = minH + SYNCTEX_ABS_WIDTH(node); + minV = SYNCTEX_VERT(node); + maxV = minV + SYNCTEX_ABS_DEPTH(node); + minV -= SYNCTEX_ABS_HEIGHT(node); + /* In what region is the point hitPoint=(H,V) ? */ + if (hitPoint.vminV) { + result = hitPoint.v - minV + minH - hitPoint.h; + } else { + result = minV - hitPoint.v + minH - hitPoint.h; + } + } else if (hitPoint.h>maxH) { + if (hitPoint.v>minV) { + result = hitPoint.v - minV + hitPoint.h - maxH; + } else { + result = minV - hitPoint.v + hitPoint.h - maxH; + } + } else if (hitPoint.v>minV) { + result = hitPoint.v - minV; + } else { + result = minV - hitPoint.v; + } + break; + case synctex_node_type_glue: + case synctex_node_type_math: + minH = SYNCTEX_HORIZ(node); + minV = SYNCTEX_VERT(node); + if (hitPoint.hminV) { + result = hitPoint.v - minV + minH - hitPoint.h; + } else { + result = minV - hitPoint.v + minH - hitPoint.h; + } + } else if (hitPoint.v>minV) { + result = hitPoint.v - minV + hitPoint.h - minH; + } else { + result = minV - hitPoint.v + hitPoint.h - minH; + } + break; + } + } + return result; +} + +static synctex_node_t _synctex_eq_deepest_container(synctex_point_t hitPoint,synctex_node_t node, synctex_bool_t visible) { + if (node) { + synctex_node_t result = NULL; + synctex_node_t child = NULL; + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_hbox: + /* test the deep nodes first */ + if ((child = SYNCTEX_CHILD(node))) { + do { + if ((result = _synctex_eq_deepest_container(hitPoint,child,visible))) { + return result; + } + } while((child = SYNCTEX_SIBLING(child))); + } + /* is the hit point inside the box? */ + if (_synctex_point_in_box(hitPoint,node,visible)) { + /* for vboxes we try to use some node inside. + * Walk through the list of siblings until we find the closest one. + * Only consider siblings with children. */ + if ((node->class->type == synctex_node_type_vbox) && (child = SYNCTEX_CHILD(node))) { + int bestDistance = INT_MAX; + do { + if (SYNCTEX_CHILD(child)) { + int distance = _synctex_node_distance_to_point(hitPoint,child,visible); + if (distance < bestDistance) { + bestDistance = distance; + node = child; + } + } + } while((child = SYNCTEX_SIBLING(child))); + } + return node; + } + } + } + return NULL; +} + +/* Compares the locations of the hitPoint with the locations of the various nodes contained in the box. + * As it is an horizontal box, we only compare horizontal coordinates. */ +SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_hbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef, synctex_bool_t visible); +SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_hbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef, synctex_bool_t visible) { + int result = 0; + if ((node = SYNCTEX_CHILD(node))) { + do { + int off7 = _synctex_point_h_distance(hitPoint,node,visible); + if (off7 > 0) { + /* node is to the right of the hit point. + * We compare node and the previously recorded one, through the recorded distance. + * If the nodes have the same tag, prefer the one with the smallest line number, + * if the nodes also have the same line number, prefer the one with the smallest column. */ + if (bestDistancesRef->right > off7) { + bestDistancesRef->right = off7; + bestNodesRef->right = node; + result |= SYNCTEX_MASK_RIGHT; + } else if (bestDistancesRef->right == off7 && bestNodesRef->right) { + if (SYNCTEX_TAG(bestNodesRef->right) == SYNCTEX_TAG(node) + && (SYNCTEX_LINE(bestNodesRef->right) > SYNCTEX_LINE(node) + || (SYNCTEX_LINE(bestNodesRef->right) == SYNCTEX_LINE(node) + && SYNCTEX_COLUMN(bestNodesRef->right) > SYNCTEX_COLUMN(node)))) { + bestNodesRef->right = node; + result |= SYNCTEX_MASK_RIGHT; + } + } + } else if (off7 == 0) { + /* hitPoint is inside node. */ + bestDistancesRef->left = bestDistancesRef->right = 0; + bestNodesRef->left = node; + bestNodesRef->right = NULL; + result |= SYNCTEX_MASK_LEFT; + } else { /* here off7 < 0, hitPoint is to the right of node */ + off7 = -off7; + if (bestDistancesRef->left > off7) { + bestDistancesRef->left = off7; + bestNodesRef->left = node; + result |= SYNCTEX_MASK_LEFT; + } else if (bestDistancesRef->left == off7 && bestNodesRef->left) { + if (SYNCTEX_TAG(bestNodesRef->left) == SYNCTEX_TAG(node) + && (SYNCTEX_LINE(bestNodesRef->left) > SYNCTEX_LINE(node) + || (SYNCTEX_LINE(bestNodesRef->left) == SYNCTEX_LINE(node) + && SYNCTEX_COLUMN(bestNodesRef->left) > SYNCTEX_COLUMN(node)))) { + bestNodesRef->left = node; + result |= SYNCTEX_MASK_LEFT; + } + } + } + } while((node = SYNCTEX_SIBLING(node))); + if (result & SYNCTEX_MASK_LEFT) { + /* the left node is new, try to narrow the result */ + if ((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->left,visible))) { + bestNodesRef->left = node; + } + if ((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->left,visible))) { + bestNodesRef->left = node; + } + } + if (result & SYNCTEX_MASK_RIGHT) { + /* the right node is new, try to narrow the result */ + if ((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->right,visible))) { + bestNodesRef->right = node; + } + if ((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->right,visible))) { + bestNodesRef->right = node; + } + } + } + return result; +} +SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_vbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef,synctex_bool_t visible); +SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_vbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef,synctex_bool_t visible) { + int result = 0; + if ((node = SYNCTEX_CHILD(node))) { + do { + int off7 = _synctex_point_v_distance(hitPoint,node,visible);/* this is what makes the difference with the h version above */ + if (off7 > 0) { + /* node is to the top of the hit point (below because TeX is oriented from top to bottom. + * We compare node and the previously recorded one, through the recorded distance. + * If the nodes have the same tag, prefer the one with the smallest line number, + * if the nodes also have the same line number, prefer the one with the smallest column. */ + if (bestDistancesRef->right > off7) { + bestDistancesRef->right = off7; + bestNodesRef->right = node; + result |= SYNCTEX_MASK_RIGHT; + } else if (bestDistancesRef->right == off7 && bestNodesRef->right) { + if (SYNCTEX_TAG(bestNodesRef->right) == SYNCTEX_TAG(node) + && (SYNCTEX_LINE(bestNodesRef->right) > SYNCTEX_LINE(node) + || (SYNCTEX_LINE(bestNodesRef->right) == SYNCTEX_LINE(node) + && SYNCTEX_COLUMN(bestNodesRef->right) > SYNCTEX_COLUMN(node)))) { + bestNodesRef->right = node; + result |= SYNCTEX_MASK_RIGHT; + } + } + } else if (off7 == 0) { + bestDistancesRef->left = bestDistancesRef->right = 0; + bestNodesRef->left = node; + bestNodesRef->right = NULL; + result |= SYNCTEX_MASK_LEFT; + } else { /* here off7 < 0 */ + off7 = -off7; + if (bestDistancesRef->left > off7) { + bestDistancesRef->left = off7; + bestNodesRef->left = node; + result |= SYNCTEX_MASK_LEFT; + } else if (bestDistancesRef->left == off7 && bestNodesRef->left) { + if (SYNCTEX_TAG(bestNodesRef->left) == SYNCTEX_TAG(node) + && (SYNCTEX_LINE(bestNodesRef->left) > SYNCTEX_LINE(node) + || (SYNCTEX_LINE(bestNodesRef->left) == SYNCTEX_LINE(node) + && SYNCTEX_COLUMN(bestNodesRef->left) > SYNCTEX_COLUMN(node)))) { + bestNodesRef->left = node; + result |= SYNCTEX_MASK_LEFT; + } + } + } + } while((node = SYNCTEX_SIBLING(node))); + if (result & SYNCTEX_MASK_LEFT) { + /* the left node is new, try to narrow the result */ + if ((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->left,visible))) { + bestNodesRef->left = node; + } + if ((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->left,visible))) { + bestNodesRef->left = node; + } + } + if (result & SYNCTEX_MASK_RIGHT) { + /* the right node is new, try to narrow the result */ + if ((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->right,visible))) { + bestNodesRef->right = node; + } + if ((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->right,visible))) { + bestNodesRef->right = node; + } + } + } + return result; +} +SYNCTEX_INLINE static int _synctex_eq_get_closest_children_in_box(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef,synctex_bool_t visible) { + if (node) { + switch(node->class->type) { + case synctex_node_type_hbox: + return __synctex_eq_get_closest_children_in_hbox(hitPoint, node, bestNodesRef, bestDistancesRef,visible); + case synctex_node_type_vbox: + return __synctex_eq_get_closest_children_in_vbox(hitPoint, node, bestNodesRef, bestDistancesRef,visible); + } + } + return 0; +} + +SYNCTEX_INLINE static synctex_node_t __synctex_eq_closest_child(synctex_point_t hitPoint, synctex_node_t node,int* distanceRef, synctex_bool_t visible); +SYNCTEX_INLINE static synctex_node_t __synctex_eq_closest_child(synctex_point_t hitPoint, synctex_node_t node,int* distanceRef, synctex_bool_t visible) { + synctex_node_t best_node = NULL; + if ((node = SYNCTEX_CHILD(node))) { + do { + int distance = _synctex_node_distance_to_point(hitPoint,node,visible); + synctex_node_t candidate = NULL; + if (distance<=*distanceRef) { + *distanceRef = distance; + best_node = node; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_hbox: + if ((candidate = __synctex_eq_closest_child(hitPoint,node,distanceRef,visible))) { + best_node = candidate; + } + } + } while((node = SYNCTEX_SIBLING(node))); + } + return best_node; +} +SYNCTEX_INLINE static synctex_node_t _synctex_eq_closest_child(synctex_point_t hitPoint,synctex_node_t node, synctex_bool_t visible) { + if (node) { + switch(node->class->type) { + case synctex_node_type_hbox: + case synctex_node_type_vbox: + { + int best_distance = INT_MAX; + synctex_node_t best_node = __synctex_eq_closest_child(hitPoint,node,&best_distance,visible); + if ((best_node)) { + synctex_node_t child = NULL; + switch(best_node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_hbox: + if ((child = SYNCTEX_CHILD(best_node))) { + best_distance = _synctex_node_distance_to_point(hitPoint,child,visible); + while((child = SYNCTEX_SIBLING(child))) { + int distance = _synctex_node_distance_to_point(hitPoint,child,visible); + if (distance<=best_distance) { + best_distance = distance; + best_node = child; + } + } + } + } + } + return best_node; + } + } + } + return NULL; +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Updater +# endif + +typedef int (*synctex_fprintf_t)(void *, const char * , ...); /* print formatted to either FILE * or gzFile */ + +# define SYNCTEX_BITS_PER_BYTE 8 + +struct __synctex_updater_t { + void *file; /* the foo.synctex or foo.synctex.gz I/O identifier */ + synctex_fprintf_t fprintf; /* either fprintf or gzprintf */ + int length; /* the number of chars appended */ + struct _flags { + unsigned int no_gz:1; /* Whether zlib is used or not */ + unsigned int reserved:SYNCTEX_BITS_PER_BYTE*sizeof(int)-1; /* Align */ + } flags; +}; +# define SYNCTEX_FILE updater->file +# define SYNCTEX_NO_GZ ((updater->flags).no_gz) +# define SYNCTEX_fprintf (*(updater->fprintf)) + +synctex_updater_t synctex_updater_new_with_output_file(const char * output, const char * build_directory) { + synctex_updater_t updater = NULL; + char * synctex = NULL; + synctex_io_mode_t io_mode = 0; + const char * mode = NULL; + /* prepare the updater, the memory is the only one dynamically allocated */ + updater = (synctex_updater_t)_synctex_malloc(sizeof(synctex_updater_t)); + if (NULL == updater) { + _synctex_error("! synctex_updater_new_with_file: malloc problem"); + return NULL; + } + if (_synctex_open(output,build_directory,&synctex,&SYNCTEX_FILE,synctex_ADD_QUOTES,&io_mode) + && _synctex_open(output,build_directory,&synctex,&SYNCTEX_FILE,synctex_DONT_ADD_QUOTES,&io_mode)) { +return_on_error: + free(updater); + updater = NULL; + return NULL; + } + /* OK, the file exists, we close it and reopen it with the correct mode. + * The receiver is now the owner of the "synctex" variable. */ + gzclose(SYNCTEX_FILE); + SYNCTEX_FILE = NULL; + SYNCTEX_NO_GZ = (io_mode&synctex_io_gz_mask)?synctex_NO:synctex_YES; + mode = _synctex_get_io_mode_name(io_mode|synctex_io_append_mask);/* either "a" or "ab", depending on the file extension */ + if (SYNCTEX_NO_GZ) { + if (NULL == (SYNCTEX_FILE = (void *)fopen(synctex,mode))) { +no_write_error: + _synctex_error("! synctex_updater_new_with_file: Can't append to %s",synctex); + free(synctex); + goto return_on_error; + } + updater->fprintf = (synctex_fprintf_t)(&fprintf); + } else { + if (NULL == (SYNCTEX_FILE = (void *)gzopen(synctex,mode))) { + goto no_write_error; + } + updater->fprintf = (synctex_fprintf_t)(&gzprintf); + } + printf("SyncTeX: updating %s...",synctex); + free(synctex); + return updater; +} + + +void synctex_updater_append_magnification(synctex_updater_t updater, char * magnification){ + if (NULL==updater) { + return; + } + if (magnification && strlen(magnification)) { + updater->length += SYNCTEX_fprintf(SYNCTEX_FILE,"Magnification:%s\n",magnification); + } +} + +void synctex_updater_append_x_offset(synctex_updater_t updater, char * x_offset){ + if (NULL==updater) { + return; + } + if (x_offset && strlen(x_offset)) { + updater->length += SYNCTEX_fprintf(SYNCTEX_FILE,"X Offset:%s\n",x_offset); + } +} + +void synctex_updater_append_y_offset(synctex_updater_t updater, char * y_offset){ + if (NULL==updater) { + return; + } + if (y_offset && strlen(y_offset)) { + updater->length += SYNCTEX_fprintf(SYNCTEX_FILE,"Y Offset:%s\n",y_offset); + } +} + +void synctex_updater_free(synctex_updater_t updater){ + if (NULL==updater) { + return; + } + if (updater->length>0) { + SYNCTEX_fprintf(SYNCTEX_FILE,"!%i\n",updater->length); + } + if (SYNCTEX_NO_GZ) { + fclose((FILE *)SYNCTEX_FILE); + } else { + gzclose((gzFile)SYNCTEX_FILE); + } + free(updater); + printf("... done.\n"); + return; +} diff --git a/services/clsi/src/synctex/synctex_parser.h b/services/clsi/src/synctex/synctex_parser.h new file mode 100644 index 0000000000..4aca41501e --- /dev/null +++ b/services/clsi/src/synctex/synctex_parser.h @@ -0,0 +1,346 @@ +/* +Copyright (c) 2008, 2009, 2010 , 2011 jerome DOT laurens AT u-bourgogne DOT fr + +This file is part of the SyncTeX package. + +Latest Revision: Tue Jun 14 08:23:30 UTC 2011 + +Version: 1.16 + +See synctex_parser_readme.txt for more details + +License: +-------- +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE + +Except as contained in this notice, the name of the copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written +authorization from the copyright holder. + +Acknowledgments: +---------------- +The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh, +and significant help from XeTeX developer Jonathan Kew + +Nota Bene: +---------- +If you include or use a significant part of the synctex package into a software, +I would appreciate to be listed as contributor and see "SyncTeX" highlighted. + +Version 1 +Thu Jun 19 09:39:21 UTC 2008 + +*/ + +#ifndef __SYNCTEX_PARSER__ +# define __SYNCTEX_PARSER__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* synctex_node_t is the type for all synctex nodes. + * The synctex file is parsed into a tree of nodes, either sheet, boxes, math nodes... */ +typedef struct _synctex_node * synctex_node_t; + +/* The main synctex object is a scanner + * Its implementation is considered private. + * The basic workflow is + * - create a "synctex scanner" with the contents of a file + * - perform actions on that scanner like display or edit queries + * - free the scanner when the work is done + */ +typedef struct __synctex_scanner_t _synctex_scanner_t; +typedef _synctex_scanner_t * synctex_scanner_t; + +/* This is the designated method to create a new synctex scanner object. + * output is the pdf/dvi/xdv file associated to the synctex file. + * If necessary, it can be the tex file that originated the synctex file + * but this might cause problems if the \jobname has a custom value. + * Despite this method can accept a relative path in practice, + * you should only pass a full path name. + * The path should be encoded by the underlying file system, + * assuming that it is based on 8 bits characters, including UTF8, + * not 16 bits nor 32 bits. + * The last file extension is removed and replaced by the proper extension. + * Then the private method _synctex_scanner_new_with_contents_of_file is called. + * NULL is returned in case of an error or non existent file. + * Once you have a scanner, use the synctex_display_query and synctex_edit_query below. + * The new "build_directory" argument is available since version 1.5. + * It is the directory where all the auxiliary stuff is created. + * Sometimes, the synctex output file and the pdf, dvi or xdv files are not created in the same directory. + * This is the case in MikTeX (I will include this into TeX Live). + * This directory path can be nil, it will be ignored then. + * It can be either absolute or relative to the directory of the output pdf (dvi or xdv) file. + * If no synctex file is found in the same directory as the output file, then we try to find one in the build directory. + * Please note that this new "build_directory" is provided as a convenient argument but should not be used. + * In fact, this is implempented as a work around of a bug in MikTeX where the synctex file does not follow the pdf file. + * The new "parse" argument is available since version 1.5. In general, use 1. + * Use 0 only if you do not want to parse the content but just check the existence. + */ +synctex_scanner_t synctex_scanner_new_with_output_file(const char * output, const char * build_directory, int parse); + +/* This is the designated method to delete a synctex scanner object. + * Frees all the memory, you must call it when you are finished with the scanner. + */ +void synctex_scanner_free(synctex_scanner_t scanner); + +/* Send this message to force the scanner to parse the contents of the synctex output file. + * Nothing is performed if the file was already parsed. + * In each query below, this message is sent, but if you need to access information more directly, + * you must be sure that the parsing did occur. + * Usage: + * if((my_scanner = synctex_scanner_parse(my_scanner))) { + * continue with my_scanner... + * } else { + * there was a problem + * } + */ +synctex_scanner_t synctex_scanner_parse(synctex_scanner_t scanner); + +/* The main entry points. + * Given the file name, a line and a column number, synctex_display_query returns the number of nodes + * satisfying the contrain. Use code like + * + * if(synctex_display_query(scanner,name,line,column)>0) { + * synctex_node_t node; + * while((node = synctex_next_result(scanner))) { + * // do something with node + * ... + * } + * } + * + * For example, one can + * - highlight each resulting node in the output, using synctex_node_h and synctex_node_v + * - highlight all the rectangles enclosing those nodes, using synctex_box_... functions + * - highlight just the character using that information + * + * Given the page and the position in the page, synctex_edit_query returns the number of nodes + * satisfying the contrain. Use code like + * + * if(synctex_edit_query(scanner,page,h,v)>0) { + * synctex_node_t node; + * while(node = synctex_next_result(scanner)) { + * // do something with node + * ... + * } + * } + * + * For example, one can + * - highlight each resulting line in the input, + * - highlight just the character using that information + * + * page is 1 based + * h and v are coordinates in 72 dpi unit, relative to the top left corner of the page. + * If you make a new query, the result of the previous one is discarded. + * If one of this function returns a non positive integer, + * it means that an error occurred. + * + * Both methods are conservative, in the sense that matching is weak. + * If the exact column number is not found, there will be an answer with the whole line. + * + * Sumatra-PDF, Skim, iTeXMac2 and Texworks are examples of open source software that use this library. + * You can browse their code for a concrete implementation. + */ +int synctex_display_query(synctex_scanner_t scanner,const char * name,int line,int column); +int synctex_edit_query(synctex_scanner_t scanner,int page,float h,float v); +synctex_node_t synctex_next_result(synctex_scanner_t scanner); + +/* Display all the information contained in the scanner object. + * If the records are too numerous, only the first ones are displayed. + * This is mainly for informatinal purpose to help developers. + */ +void synctex_scanner_display(synctex_scanner_t scanner); + +/* The x and y offset of the origin in TeX coordinates. The magnification + These are used by pdf viewers that want to display the real box size. + For example, getting the horizontal coordinates of a node would require + synctex_node_box_h(node)*synctex_scanner_magnification(scanner)+synctex_scanner_x_offset(scanner) + Getting its TeX width would simply require + synctex_node_box_width(node)*synctex_scanner_magnification(scanner) + but direct methods are available for that below. + */ +int synctex_scanner_x_offset(synctex_scanner_t scanner); +int synctex_scanner_y_offset(synctex_scanner_t scanner); +float synctex_scanner_magnification(synctex_scanner_t scanner); + +/* Managing the input file names. + * Given a tag, synctex_scanner_get_name will return the corresponding file name. + * Conversely, given a file name, synctex_scanner_get_tag will retur, the corresponding tag. + * The file name must be the very same as understood by TeX. + * For example, if you \input myDir/foo.tex, the file name is myDir/foo.tex. + * No automatic path expansion is performed. + * Finally, synctex_scanner_input is the first input node of the scanner. + * To browse all the input node, use a loop like + * + * if((input_node = synctex_scanner_input(scanner))){ + * do { + * blah + * } while((input_node=synctex_node_sibling(input_node))); + * } + * + * The output is the name that was used to create the scanner. + * The synctex is the real name of the synctex file, + * it was obtained from output by setting the proper file extension. + */ +const char * synctex_scanner_get_name(synctex_scanner_t scanner,int tag); +int synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name); +synctex_node_t synctex_scanner_input(synctex_scanner_t scanner); +const char * synctex_scanner_get_output(synctex_scanner_t scanner); +const char * synctex_scanner_get_synctex(synctex_scanner_t scanner); + +/* Browsing the nodes + * parent, child and sibling are standard names for tree nodes. + * The parent is one level higher, the child is one level deeper, + * and the sibling is at the same level. + * The sheet of a node is the first ancestor, it is of type sheet. + * A node and its sibling have the same parent. + * A node is the parent of its child. + * A node is either the child of its parent, + * or belongs to the sibling chain of its parent's child. + * The next node is either the child, the sibling or the parent's sibling, + * unless the parent is a sheet. + * This allows to navigate through all the nodes of a given sheet node: + * + * synctex_node_t node = sheet; + * while((node = synctex_node_next(node))) { + * // do something with node + * } + * + * With synctex_sheet_content, you can retrieve the sheet node given the page. + * The page is 1 based, according to TeX standards. + * Conversely synctex_node_sheet allows to retrieve the sheet containing a given node. + */ +synctex_node_t synctex_node_parent(synctex_node_t node); +synctex_node_t synctex_node_sheet(synctex_node_t node); +synctex_node_t synctex_node_child(synctex_node_t node); +synctex_node_t synctex_node_sibling(synctex_node_t node); +synctex_node_t synctex_node_next(synctex_node_t node); +synctex_node_t synctex_sheet_content(synctex_scanner_t scanner,int page); + +/* These are the types of the synctex nodes */ +typedef enum { + synctex_node_type_error = 0, + synctex_node_type_input, + synctex_node_type_sheet, + synctex_node_type_vbox, + synctex_node_type_void_vbox, + synctex_node_type_hbox, + synctex_node_type_void_hbox, + synctex_node_type_kern, + synctex_node_type_glue, + synctex_node_type_math, + synctex_node_type_boundary, + synctex_node_number_of_types +} synctex_node_type_t; + +/* synctex_node_type gives the type of a given node, + * synctex_node_isa gives the same information as a human readable text. */ +synctex_node_type_t synctex_node_type(synctex_node_t node); +const char * synctex_node_isa(synctex_node_t node); + +/* This is primarily used for debugging purpose. + * The second one logs information for the node and recursively displays information for its next node */ +void synctex_node_log(synctex_node_t node); +void synctex_node_display(synctex_node_t node); + +/* Given a node, access to its tag, line and column. + * The line and column numbers are 1 based. + * The latter is not yet fully supported in TeX, the default implementation returns 0 which means the whole line. + * When the tag is known, the scanner of the node will give the corresponding file name. + * When the tag is known, the scanner of the node will give the name. + */ +int synctex_node_tag(synctex_node_t node); +int synctex_node_line(synctex_node_t node); +int synctex_node_column(synctex_node_t node); + +/* This is the page where the node appears. + * This is a 1 based index as given by TeX. + */ +int synctex_node_page(synctex_node_t node); + +/* For quite all nodes, horizontal, vertical coordinates, and width. + * These are expressed in TeX small points coordinates, with origin at the top left corner. + */ +int synctex_node_h(synctex_node_t node); +int synctex_node_v(synctex_node_t node); +int synctex_node_width(synctex_node_t node); + +/* For all nodes, dimensions of the enclosing box. + * These are expressed in TeX small points coordinates, with origin at the top left corner. + * A box is enclosing itself. + */ +int synctex_node_box_h(synctex_node_t node); +int synctex_node_box_v(synctex_node_t node); +int synctex_node_box_width(synctex_node_t node); +int synctex_node_box_height(synctex_node_t node); +int synctex_node_box_depth(synctex_node_t node); + +/* For quite all nodes, horizontal, vertical coordinates, and width. + * The visible dimensions are bigger than real ones to compensate 0 width boxes + * that do contain nodes. + * These are expressed in page coordinates, with origin at the top left corner. + * A box is enclosing itself. + */ +float synctex_node_visible_h(synctex_node_t node); +float synctex_node_visible_v(synctex_node_t node); +float synctex_node_visible_width(synctex_node_t node); +/* For all nodes, visible dimensions of the enclosing box. + * A box is enclosing itself. + * The visible dimensions are bigger than real ones to compensate 0 width boxes + * that do contain nodes. + */ +float synctex_node_box_visible_h(synctex_node_t node); +float synctex_node_box_visible_v(synctex_node_t node); +float synctex_node_box_visible_width(synctex_node_t node); +float synctex_node_box_visible_height(synctex_node_t node); +float synctex_node_box_visible_depth(synctex_node_t node); + +/* The main synctex updater object. + * This object is used to append information to the synctex file. + * Its implementation is considered private. + * It is used by the synctex command line tool to take into account modifications + * that could occur while postprocessing files by dvipdf like filters. + */ +typedef struct __synctex_updater_t _synctex_updater_t; +typedef _synctex_updater_t * synctex_updater_t; + +/* Designated initializer. + * Once you are done with your whole job, + * free the updater */ +synctex_updater_t synctex_updater_new_with_output_file(const char * output, const char * directory); + +/* Use the next functions to append records to the synctex file, + * no consistency tests made on the arguments */ +void synctex_updater_append_magnification(synctex_updater_t updater, char * magnification); +void synctex_updater_append_x_offset(synctex_updater_t updater, char * x_offset); +void synctex_updater_append_y_offset(synctex_updater_t updater, char * y_offset); + +/* You MUST free the updater, once everything is properly appended */ +void synctex_updater_free(synctex_updater_t updater); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/services/clsi/src/synctex/synctex_parser_local.h b/services/clsi/src/synctex/synctex_parser_local.h new file mode 100644 index 0000000000..6573b2638a --- /dev/null +++ b/services/clsi/src/synctex/synctex_parser_local.h @@ -0,0 +1,45 @@ +/* +Copyright (c) 2008, 2009, 2010 , 2011 jerome DOT laurens AT u-bourgogne DOT fr + +This file is part of the SyncTeX package. + +Latest Revision: Tue Jun 14 08:23:30 UTC 2011 + +Version: 1.16 + +See synctex_parser_readme.txt for more details + +License: +-------- +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE + +Except as contained in this notice, the name of the copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written +authorization from the copyright holder. + +*/ + +/* This local header file is for TEXLIVE, use your own header to fit your system */ +# include /* for inline && HAVE_xxx */ +/* No inlining for synctex tool in texlive. */ +# define SYNCTEX_INLINE diff --git a/services/clsi/src/synctex/synctex_parser_readme.txt b/services/clsi/src/synctex/synctex_parser_readme.txt new file mode 100644 index 0000000000..ebc06bb7bd --- /dev/null +++ b/services/clsi/src/synctex/synctex_parser_readme.txt @@ -0,0 +1,141 @@ +This file is part of the SyncTeX package. + +The Synchronization TeXnology named SyncTeX is a new feature +of recent TeX engines designed by Jerome Laurens. +It allows to synchronize between input and output, which means to +navigate from the source document to the typeset material and vice versa. +More informations on http://itexmac2.sourceforge.net/SyncTeX.html + +This package is mainly for developers, it mainly contains the following files: + +synctex_parser_readme.txt +synctex_parser_version.txt +synctex_parser_utils.c +synctex_parser_utils.h +synctex_parser_local.h +synctex_parser.h +synctex_parser.c + +The file you are reading contains more informations about the SyncTeX parser history. + +In order to support SyncTeX in a viewer, it is sufficient to include +in the source the files synctex_parser.h and synctex_parser.c. +The synctex parser usage is described in synctex_parser.h header file. + +The other files are used by tex engines or by the synctex command line utility: + +ChangeLog +README.txt +am +man1 +man5 +synctex-common.h +synctex-convert.sh +synctex-e-mem.ch0 +synctex-e-mem.ch1 +synctex-e-rec.ch0 +synctex-e-rec.ch1 +synctex-etex.h +synctex-mem.ch0 +synctex-mem.ch1 +synctex-mem.ch2 +synctex-pdf-rec.ch2 +synctex-pdftex.h +synctex-rec.ch0 +synctex-rec.ch1 +synctex-rec.ch2 +synctex-tex.h +synctex-xe-mem.ch2 +synctex-xe-rec.ch2 +synctex-xe-rec.ch3 +synctex-xetex.h +synctex.c +synctex.defines +synctex.h +synctex_main.c +tests + + +Version: +-------- +This is version 1, which refers to the synctex output file format. +The files are identified by a build number. +In order to help developers to automatically manage the version and build numbers +and download the parser only when necessary, the synctex_parser.version +is an ASCII text file just containing the current version and build numbers. + +History: +-------- +1.1: Thu Jul 17 09:28:13 UTC 2008 +- First official version available in TeXLive 2008 DVD. + Unfortunately, the backwards synchronization is not working properly mainly for ConTeXt users, see below. +1.2: Tue Sep 2 10:28:32 UTC 2008 +- Correction for ConTeXt support in the edit query. + The previous method was assuming that TeX boxes do not overlap, + which is reasonable for LaTeX but not for ConTeXt. + This assumption is no longer considered. +1.3: Fri Sep 5 09:39:57 UTC 2008 +- Local variable "read" renamed to "already_read" to avoid conflicts. +- "inline" compiler directive renamed to "SYNCTEX_INLINE" for code support and maintenance +- _synctex_error cannot be inlined due to variable arguments (thanks Christiaan Hofman) +- Correction in the display query, extra boundary nodes are used for a more precise forwards synchronization +1.4: Fri Sep 12 08:12:34 UTC 2008 +- For an unknown reason, the previous version was not the real 1.3 (as used in iTeXMac2 build 747). + As a consequence, a crash was observed. +- Some typos are fixed. +1.6: Mon Nov 3 20:20:02 UTC 2008 +- The bug that prevented synchronization with compressed files on windows has been fixed. +- New interface to allow system specific customization. +- Note that some APIs have changed. +1.8: Mer 8 jul 2009 11:32:38 UTC +Note that version 1.7 was delivered privately. +- bug fix: synctex was causing a memory leak in pdftex and xetex, thus some processing speed degradation +- bug fix: the synctex command line tool was broken when updating a .synctex file +- enhancement: better accuracy of the synchronization process +- enhancement: the pdf output file and the associated .synctex file no longer need to live in the same directory. + The new -d option of the synctex command line tool manages this situation. + This is handy when using something like tex -output-directory=DIR ... +1.9: Wed Nov 4 11:52:35 UTC 2009 +- Various typo fixed +- OutputDebugString replaced by OutputDebugStringA to deliberately disable unicode preprocessing +- New conditional created because OutputDebugStringA is only available since Windows 2K professional +1.10: Sun Jan 10 10:12:32 UTC 2010 +- Bug fix in synctex_parser.c to solve a synchronization problem with amsmath's gather environment. + Concerns the synctex tool. +1.11: Sun Jan 17 09:12:31 UTC 2010 +- Bug fix in synctex_parser.c, function synctex_node_box_visible_v: 'x' replaced by 'y'. + Only 3rd party tools are concerned. +1.12: Mon Jul 19 21:52:10 UTC 2010 +- Bug fix in synctex_parser.c, function __synctex_open: the io_mode was modified even in case of a non zero return, +causing a void .synctex.gz file to be created even if it was not expected. Reported by Marek Kasik concerning a bug on evince. +1.13: Fri Mar 11 07:39:12 UTC 2011 +- Bug fix in synctex_parser.c, better synchronization as suggested by Jan Sundermeyer (near line 3388). +- Stronger code design in synctex_parser_utils.c, function _synctex_get_name (really neutral behavior). + Only 3rd party tools are concerned. +1.14: Fri Apr 15 19:10:57 UTC 2011 +- taking output_directory into account +- Replaced FOPEN_WBIN_MODE by FOPEN_W_MODE when opening the text version of the .synctex file. +- Merging with LuaTeX's version of synctex.c +1.15: Fri Jun 10 14:10:17 UTC 2011 +This concerns the synctex command line tool and 3rd party developers. +TeX and friends are not concerned by these changes. +- Bug fixed in _synctex_get_io_mode_name, sometimes the wrong mode was returned +- Support for LuaTeX convention of './' file prefixing +1.16: Tue Jun 14 08:23:30 UTC 2011 +This concerns the synctex command line tool and 3rd party developers. +TeX and friends are not concerned by these changes. +- Better forward search (thanks Jose Alliste) +- Support for LuaTeX convention of './' file prefixing now for everyone, not only for Windows + +Acknowledgments: +---------------- +The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh, +and significant help from XeTeX developer Jonathan Kew + +Nota Bene: +---------- +If you include or use a significant part of the synctex package into a software, +I would appreciate to be listed as contributor and see "SyncTeX" highlighted. + +Copyright (c) 2008-2011 jerome DOT laurens AT u-bourgogne DOT fr + diff --git a/services/clsi/src/synctex/synctex_parser_utils.c b/services/clsi/src/synctex/synctex_parser_utils.c new file mode 100644 index 0000000000..569f7e96ca --- /dev/null +++ b/services/clsi/src/synctex/synctex_parser_utils.c @@ -0,0 +1,479 @@ +/* +Copyright (c) 2008, 2009, 2010 , 2011 jerome DOT laurens AT u-bourgogne DOT fr + +This file is part of the SyncTeX package. + +Latest Revision: Tue Jun 14 08:23:30 UTC 2011 + +Version: 1.16 + +See synctex_parser_readme.txt for more details + +License: +-------- +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE + +Except as contained in this notice, the name of the copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written +authorization from the copyright holder. + +*/ + +/* In this file, we find all the functions that may depend on the operating system. */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#if defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__) || defined(__WINDOWS__) +#define SYNCTEX_WINDOWS 1 +#endif + +#ifdef _WIN32_WINNT_WINXP +#define SYNCTEX_RECENT_WINDOWS 1 +#endif + +#ifdef SYNCTEX_WINDOWS +#include +#endif + +void *_synctex_malloc(size_t size) { + void * ptr = malloc(size); + if(ptr) { +/* There used to be a switch to use bzero because it is more secure. JL */ + memset(ptr,0, size); + } + return (void *)ptr; +} + +int _synctex_error(const char * reason,...) { + va_list arg; + int result; + va_start (arg, reason); +# ifdef SYNCTEX_RECENT_WINDOWS + {/* This code is contributed by William Blum. + As it does not work on some older computers, + the _WIN32 conditional here is replaced with a SYNCTEX_RECENT_WINDOWS one. + According to http://msdn.microsoft.com/en-us/library/aa363362(VS.85).aspx + Minimum supported client Windows 2000 Professional + Minimum supported server Windows 2000 Server + People running Windows 2K standard edition will not have OutputDebugStringA. + JL.*/ + char *buff; + size_t len; + OutputDebugStringA("SyncTeX ERROR: "); + len = _vscprintf(reason, arg) + 1; + buff = (char*)malloc( len * sizeof(char) ); + result = vsprintf(buff, reason, arg) +strlen("SyncTeX ERROR: "); + OutputDebugStringA(buff); + OutputDebugStringA("\n"); + free(buff); + } +# else + result = fprintf(stderr,"SyncTeX ERROR: "); + result += vfprintf(stderr, reason, arg); + result += fprintf(stderr,"\n"); +# endif + va_end (arg); + return result; +} + +/* strip the last extension of the given string, this string is modified! */ +void _synctex_strip_last_path_extension(char * string) { + if(NULL != string){ + char * last_component = NULL; + char * last_extension = NULL; + char * next = NULL; + /* first we find the last path component */ + if(NULL == (last_component = strstr(string,"/"))){ + last_component = string; + } else { + ++last_component; + while((next = strstr(last_component,"/"))){ + last_component = next+1; + } + } +# ifdef SYNCTEX_WINDOWS + /* On Windows, the '\' is also a path separator. */ + while((next = strstr(last_component,"\\"))){ + last_component = next+1; + } +# endif + /* then we find the last path extension */ + if((last_extension = strstr(last_component,"."))){ + ++last_extension; + while((next = strstr(last_extension,"."))){ + last_extension = next+1; + } + --last_extension;/* back to the "." */ + if(last_extension>last_component){/* filter out paths like ....my/dir/.hidden"*/ + last_extension[0] = '\0'; + } + } + } +} + +const char * synctex_ignore_leading_dot_slash(const char * name) +{ + while(SYNCTEX_IS_DOT(*name) && SYNCTEX_IS_PATH_SEPARATOR(name[1])) { + name += 2; + while (SYNCTEX_IS_PATH_SEPARATOR(*name)) { + ++name; + } + } + return name; +} + +/* Compare two file names, windows is sometimes case insensitive... */ +synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs) { + /* Remove the leading regex '(\./+)*' in both rhs and lhs */ + lhs = synctex_ignore_leading_dot_slash(lhs); + rhs = synctex_ignore_leading_dot_slash(rhs); +# if SYNCTEX_WINDOWS + /* On Windows, filename should be compared case insensitive. + * The characters '/' and '\' are both valid path separators. + * There will be a very serious problem concerning UTF8 because + * not all the characters must be toupper... + * I would like to have URL's instead of filenames. */ +next_character: + if(SYNCTEX_IS_PATH_SEPARATOR(*lhs)) {/* lhs points to a path separator */ + if(!SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* but not rhs */ + return synctex_NO; + } + } else if(SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* rhs points to a path separator but not lhs */ + return synctex_NO; + } else if(toupper(*lhs) != toupper(*rhs)){/* uppercase do not match */ + return synctex_NO; + } else if (!*lhs) {/* lhs is at the end of the string */ + return *rhs ? synctex_NO : synctex_YES; + } else if(!*rhs) {/* rhs is at the end of the string but not lhs */ + return synctex_NO; + } + ++lhs; + ++rhs; + goto next_character; +# else + return 0 == strcmp(lhs,rhs)?synctex_YES:synctex_NO; +# endif +} + +synctex_bool_t _synctex_path_is_absolute(const char * name) { + if(!strlen(name)) { + return synctex_NO; + } +# if SYNCTEX_WINDOWS + if(strlen(name)>2) { + return (name[1]==':' && SYNCTEX_IS_PATH_SEPARATOR(name[2]))?synctex_YES:synctex_NO; + } + return synctex_NO; +# else + return SYNCTEX_IS_PATH_SEPARATOR(name[0])?synctex_YES:synctex_NO; +# endif +} + +/* We do not take care of UTF-8 */ +const char * _synctex_last_path_component(const char * name) { + const char * c = name+strlen(name); + if(c>name) { + if(!SYNCTEX_IS_PATH_SEPARATOR(*c)) { + do { + --c; + if(SYNCTEX_IS_PATH_SEPARATOR(*c)) { + return c+1; + } + } while(c>name); + } + return c;/* the last path component is the void string*/ + } + return c; +} + +int _synctex_copy_with_quoting_last_path_component(const char * src, char ** dest_ref, size_t size) { + const char * lpc; + if(src && dest_ref) { +# define dest (*dest_ref) + dest = NULL; /* Default behavior: no change and sucess. */ + lpc = _synctex_last_path_component(src); + if(strlen(lpc)) { + if(strchr(lpc,' ') && lpc[0]!='"' && lpc[strlen(lpc)-1]!='"') { + /* We are in the situation where adding the quotes is allowed. */ + /* Time to add the quotes. */ + /* Consistency test: we must have dest+size>dest+strlen(dest)+2 + * or equivalently: strlen(dest)+20) { + char * result = NULL; + ++size; + /* Create the memory storage */ + if(NULL!=(result = (char *)malloc(size))) { + char * dest = result; + va_start (arg, first); + temp = first; + do { + if((size = strlen(temp))>0) { + /* There is something to merge */ + if(dest != strncpy(dest,temp,size)) { + _synctex_error("! _synctex_merge_strings: Copy problem"); + free(result); + result = NULL; + return NULL; + } + dest += size; + } + } while( (temp = va_arg(arg, const char *)) != NULL); + va_end(arg); + dest[0]='\0';/* Terminate the merged string */ + return result; + } + _synctex_error("! _synctex_merge_strings: Memory problem"); + return NULL; + } + return NULL; +} + +/* The purpose of _synctex_get_name is to find the name of the synctex file. + * There is a list of possible filenames from which we return the most recent one and try to remove all the others. + * With two runs of pdftex or xetex we are sure the the synctex file is really the most appropriate. + */ +int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_io_mode_t * io_mode_ref) +{ + if(output && synctex_name_ref && io_mode_ref) { + /* If output is already absolute, we just have to manage the quotes and the compress mode */ + size_t size = 0; + char * synctex_name = NULL; + synctex_io_mode_t io_mode = *io_mode_ref; + const char * base_name = _synctex_last_path_component(output); /* do not free, output is the owner. base name of output*/ + /* Do we have a real base name ? */ + if(strlen(base_name)>0) { + /* Yes, we do. */ + const char * temp = NULL; + char * core_name = NULL; /* base name of output without path extension. */ + char * dir_name = NULL; /* dir name of output */ + char * quoted_core_name = NULL; + char * basic_name = NULL; + char * gz_name = NULL; + char * quoted_name = NULL; + char * quoted_gz_name = NULL; + char * build_name = NULL; + char * build_gz_name = NULL; + char * build_quoted_name = NULL; + char * build_quoted_gz_name = NULL; + struct stat buf; + time_t the_time = 0; + /* Create core_name: let temp point to the dot before the path extension of base_name; + * We start form the \0 terminating character and scan the string upward until we find a dot. + * The leading dot is not accepted. */ + if((temp = strrchr(base_name,'.')) && (size = temp - base_name)>0) { + /* There is a dot and it is not at the leading position */ + if(NULL == (core_name = (char *)malloc(size+1))) { + _synctex_error("! _synctex_get_name: Memory problem 1"); + return -1; + } + if(core_name != strncpy(core_name,base_name,size)) { + _synctex_error("! _synctex_get_name: Copy problem 1"); + free(core_name); + dir_name = NULL; + return -2; + } + core_name[size] = '\0'; + } else { + /* There is no path extension, + * Just make a copy of base_name */ + core_name = _synctex_merge_strings(base_name); + } + /* core_name is properly set up, owned by "self". */ + /* creating dir_name. */ + size = strlen(output)-strlen(base_name); + if(size>0) { + /* output contains more than one path component */ + if(NULL == (dir_name = (char *)malloc(size+1))) { + _synctex_error("! _synctex_get_name: Memory problem"); + free(core_name); + dir_name = NULL; + return -1; + } + if(dir_name != strncpy(dir_name,output,size)) { + _synctex_error("! _synctex_get_name: Copy problem"); + free(dir_name); + dir_name = NULL; + free(core_name); + dir_name = NULL; + return -2; + } + dir_name[size] = '\0'; + } + /* dir_name is properly set up. It ends with a path separator, if non void. */ + /* creating quoted_core_name. */ + if(strchr(core_name,' ')) { + quoted_core_name = _synctex_merge_strings("\"",core_name,"\""); + } + /* quoted_core_name is properly set up. */ + if(dir_name &&strlen(dir_name)>0) { + basic_name = _synctex_merge_strings(dir_name,core_name,synctex_suffix,NULL); + if(quoted_core_name && strlen(quoted_core_name)>0) { + quoted_name = _synctex_merge_strings(dir_name,quoted_core_name,synctex_suffix,NULL); + } + } else { + basic_name = _synctex_merge_strings(core_name,synctex_suffix,NULL); + if(quoted_core_name && strlen(quoted_core_name)>0) { + quoted_name = _synctex_merge_strings(quoted_core_name,synctex_suffix,NULL); + } + } + if(!_synctex_path_is_absolute(output) && build_directory && (size = strlen(build_directory))) { + temp = build_directory + size - 1; + if(_synctex_path_is_absolute(temp)) { + build_name = _synctex_merge_strings(build_directory,basic_name,NULL); + if(quoted_core_name && strlen(quoted_core_name)>0) { + build_quoted_name = _synctex_merge_strings(build_directory,quoted_name,NULL); + } + } else { + build_name = _synctex_merge_strings(build_directory,"/",basic_name,NULL); + if(quoted_core_name && strlen(quoted_core_name)>0) { + build_quoted_name = _synctex_merge_strings(build_directory,"/",quoted_name,NULL); + } + } + } + if(basic_name) { + gz_name = _synctex_merge_strings(basic_name,synctex_suffix_gz,NULL); + } + if(quoted_name) { + quoted_gz_name = _synctex_merge_strings(quoted_name,synctex_suffix_gz,NULL); + } + if(build_name) { + build_gz_name = _synctex_merge_strings(build_name,synctex_suffix_gz,NULL); + } + if(build_quoted_name) { + build_quoted_gz_name = _synctex_merge_strings(build_quoted_name,synctex_suffix_gz,NULL); + } + /* All the others names are properly set up... */ + /* retain the most recently modified file */ +# define TEST(FILENAME,COMPRESS_MODE) \ + if(FILENAME) {\ + if (stat(FILENAME, &buf)) { \ + free(FILENAME);\ + FILENAME = NULL;\ + } else if (buf.st_mtime>the_time) { \ + the_time=buf.st_mtime; \ + synctex_name = FILENAME; \ + if (COMPRESS_MODE) { \ + io_mode |= synctex_io_gz_mask; \ + } else { \ + io_mode &= ~synctex_io_gz_mask; \ + } \ + } \ + } + TEST(basic_name,synctex_DONT_COMPRESS); + TEST(gz_name,synctex_COMPRESS); + TEST(quoted_name,synctex_DONT_COMPRESS); + TEST(quoted_gz_name,synctex_COMPRESS); + TEST(build_name,synctex_DONT_COMPRESS); + TEST(build_gz_name,synctex_COMPRESS); + TEST(build_quoted_name,synctex_DONT_COMPRESS); + TEST(build_quoted_gz_name,synctex_COMPRESS); +# undef TEST + /* Free all the intermediate filenames, except the one that will be used as returned value. */ +# define CLEAN_AND_REMOVE(FILENAME) \ + if(FILENAME && (FILENAME!=synctex_name)) {\ + remove(FILENAME);\ + printf("synctex tool info: %s removed\n",FILENAME);\ + free(FILENAME);\ + FILENAME = NULL;\ + } + CLEAN_AND_REMOVE(basic_name); + CLEAN_AND_REMOVE(gz_name); + CLEAN_AND_REMOVE(quoted_name); + CLEAN_AND_REMOVE(quoted_gz_name); + CLEAN_AND_REMOVE(build_name); + CLEAN_AND_REMOVE(build_gz_name); + CLEAN_AND_REMOVE(build_quoted_name); + CLEAN_AND_REMOVE(build_quoted_gz_name); +# undef CLEAN_AND_REMOVE + /* set up the returned values */ + * synctex_name_ref = synctex_name; + * io_mode_ref = io_mode; + return 0; + } + return -1;/* bad argument */ + } + return -2; +} + +const char * _synctex_get_io_mode_name(synctex_io_mode_t io_mode) { + static const char * synctex_io_modes[4] = {"r","rb","a","ab"}; + unsigned index = ((io_mode & synctex_io_gz_mask)?1:0) + ((io_mode & synctex_io_append_mask)?2:0);// bug pointed out by Jose Alliste + return synctex_io_modes[index]; +} diff --git a/services/clsi/src/synctex/synctex_parser_utils.h b/services/clsi/src/synctex/synctex_parser_utils.h new file mode 100644 index 0000000000..e67f8f56ed --- /dev/null +++ b/services/clsi/src/synctex/synctex_parser_utils.h @@ -0,0 +1,141 @@ +/* +Copyright (c) 2008, 2009, 2010, 2011 jerome DOT laurens AT u-bourgogne DOT fr + +This file is part of the SyncTeX package. + +Latest Revision: Tue Jun 14 08:23:30 UTC 2011 + +Version: 1.16 + +See synctex_parser_readme.txt for more details + +License: +-------- +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE + +Except as contained in this notice, the name of the copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written +authorization from the copyright holder. + +*/ + +/* The utilities declared here are subject to conditional implementation. + * All the operating system special stuff goes here. + * The problem mainly comes from file name management: path separator, encoding... + */ + +# define synctex_bool_t int +# define synctex_YES -1 +# define synctex_ADD_QUOTES -1 +# define synctex_COMPRESS -1 +# define synctex_NO 0 +# define synctex_DONT_ADD_QUOTES 0 +# define synctex_DONT_COMPRESS 0 + +#ifndef __SYNCTEX_PARSER_UTILS__ +# define __SYNCTEX_PARSER_UTILS__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +# if _WIN32 +# define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c || '\\' == c) +# else +# define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c) +# endif + +# if _WIN32 +# define SYNCTEX_IS_DOT(c) ('.' == c) +# else +# define SYNCTEX_IS_DOT(c) ('.' == c) +# endif + +/* This custom malloc functions initializes to 0 the newly allocated memory. + * There is no bzero function on windows. */ +void *_synctex_malloc(size_t size); + +/* This is used to log some informational message to the standard error stream. + * On Windows, the stderr stream is not exposed and another method is used. + * The return value is the number of characters printed. */ +int _synctex_error(const char * reason,...); + +/* strip the last extension of the given string, this string is modified! + * This function depends on the OS because the path separator may differ. + * This should be discussed more precisely. */ +void _synctex_strip_last_path_extension(char * string); + +/* Compare two file names, windows is sometimes case insensitive... + * The given strings may differ stricto sensu, but represent the same file name. + * It might not be the real way of doing things. + * The return value is an undefined non 0 value when the two file names are equivalent. + * It is 0 otherwise. */ +synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs); + +/* Description forthcoming.*/ +synctex_bool_t _synctex_path_is_absolute(const char * name); + +/* Description forthcoming...*/ +const char * _synctex_last_path_component(const char * name); + +/* If the core of the last path component of src is not already enclosed with double quotes ('"') + * and contains a space character (' '), then a new buffer is created, the src is copied and quotes are added. + * In all other cases, no destination buffer is created and the src is not copied. + * 0 on success, which means no error, something non 0 means error, mainly due to memory allocation failure, or bad parameter. + * This is used to fix a bug in the first version of pdftex with synctex (1.40.9) for which names with spaces + * were not managed in a standard way. + * On success, the caller owns the buffer pointed to by dest_ref (is any) and + * is responsible of freeing the memory when done. + * The size argument is the size of the src buffer. On return the dest_ref points to a buffer sized size+2.*/ +int _synctex_copy_with_quoting_last_path_component(const char * src, char ** dest_ref, size_t size); + +/* These are the possible extensions of the synctex file */ +extern const char * synctex_suffix; +extern const char * synctex_suffix_gz; + +typedef unsigned int synctex_io_mode_t; + +typedef enum { + synctex_io_append_mask = 1, + synctex_io_gz_mask = synctex_io_append_mask<<1 +} synctex_io_mode_masks_t; + +typedef enum { + synctex_compress_mode_none = 0, + synctex_compress_mode_gz = 1 +} synctex_compress_mode_t; + +int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_io_mode_t * io_mode_ref); + +/* returns the correct mode required by fopen and gzopen from the given io_mode */ +const char * _synctex_get_io_mode_name(synctex_io_mode_t io_mode); + +const char * synctex_ignore_leading_dot_slash(const char * name); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/services/clsi/src/synctex/synctex_parser_version.txt b/services/clsi/src/synctex/synctex_parser_version.txt new file mode 100644 index 0000000000..03ff897167 --- /dev/null +++ b/services/clsi/src/synctex/synctex_parser_version.txt @@ -0,0 +1 @@ +1.16 \ No newline at end of file diff --git a/services/clsi/test/acceptance/coffee/SynctexTests.coffee b/services/clsi/test/acceptance/coffee/SynctexTests.coffee new file mode 100644 index 0000000000..330eaff22e --- /dev/null +++ b/services/clsi/test/acceptance/coffee/SynctexTests.coffee @@ -0,0 +1,38 @@ +Client = require "./helpers/Client" +request = require "request" +require("chai").should() +expect = require("chai").expect + +describe "Syncing", -> + before (done) -> + @request = + resources: [ + path: "main.tex" + content: ''' + \\documentclass{article} + \\begin{document} + Hello world + \\end{document} + ''' + ] + @project_id = Client.randomId() + Client.compile @project_id, @request, (@error, @res, @body) => done() + + describe "from code to pdf", -> + it "should return the correct location", (done) -> + Client.syncFromCode @project_id, "main.tex", 3, 5, (error, pdfPositions) -> + throw error if error? + expect(pdfPositions).to.deep.equal( + pdf: [ { page: 1, h: 133.77, v: 134.76, height: 6.92, width: 343.71 } ] + ) + done() + + describe "from pdf to code", -> + it "should return the correct location", (done) -> + Client.syncFromPdf @project_id, 1, 100, 200, (error, codePositions) -> + throw error if error? + expect(codePositions).to.deep.equal( + code: [ { file: 'main.tex', line: 3, column: -1 } ] + ) + done() + diff --git a/services/clsi/test/acceptance/coffee/helpers/Client.coffee b/services/clsi/test/acceptance/coffee/helpers/Client.coffee index aa452d1a94..7025b79cce 100644 --- a/services/clsi/test/acceptance/coffee/helpers/Client.coffee +++ b/services/clsi/test/acceptance/coffee/helpers/Client.coffee @@ -32,6 +32,29 @@ module.exports = Client = app.use express.static(directory) app.listen(port, host) + syncFromCode: (project_id, file, line, column, callback = (error, pdfPositions) ->) -> + request.get { + url: "#{@host}/project/#{project_id}/sync/code" + qs: { + file: file + line: line + column: column + } + }, (error, response, body) -> + return callback(error) if error? + callback null, JSON.parse(body) + + syncFromPdf: (project_id, page, h, v, callback = (error, pdfPositions) ->) -> + request.get { + url: "#{@host}/project/#{project_id}/sync/pdf" + qs: { + page: page, + h: h, v: v + } + }, (error, response, body) -> + return callback(error) if error? + callback null, JSON.parse(body) + compileDirectory: (project_id, baseDirectory, directory, serverPort, callback = (error, res, body) ->) -> resources = [] entities = fs.readdirSync("#{baseDirectory}/#{directory}") diff --git a/services/clsi/test/unit/coffee/CompileControllerTests.coffee b/services/clsi/test/unit/coffee/CompileControllerTests.coffee index 6e14b42e0a..2aefdb544a 100644 --- a/services/clsi/test/unit/coffee/CompileControllerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileControllerTests.coffee @@ -18,6 +18,7 @@ describe "CompileController", -> @Settings.externalUrl = "http://www.example.com" @req = {} @res = {} + @next = sinon.stub() describe "compile", -> beforeEach -> @@ -90,3 +91,61 @@ describe "CompileController", -> ) .should.equal true + describe "syncFromCode", -> + beforeEach -> + @file = "main.tex" + @line = 42 + @column = 5 + @project_id = "mock-project-id" + @req.params = + project_id: @project_id + @req.query = + file: @file + line: @line.toString() + column: @column.toString() + @res.send = sinon.stub() + + @CompileManager.syncFromCode = sinon.stub().callsArgWith(4, null, @pdfPositions = ["mock-positions"]) + @CompileController.syncFromCode @req, @res, @next + + it "should find the corresponding location in the PDF", -> + @CompileManager.syncFromCode + .calledWith(@project_id, @file, @line, @column) + .should.equal true + + it "should return the positions", -> + @res.send + .calledWith(JSON.stringify + pdf: @pdfPositions + ) + .should.equal true + + describe "syncFromPdf", -> + beforeEach -> + @page = 5 + @h = 100.23 + @v = 45.67 + @project_id = "mock-project-id" + @req.params = + project_id: @project_id + @req.query = + page: @page.toString() + h: @h.toString() + v: @v.toString() + @res.send = sinon.stub() + + @CompileManager.syncFromPdf = sinon.stub().callsArgWith(4, null, @codePositions = ["mock-positions"]) + @CompileController.syncFromPdf @req, @res, @next + + it "should find the corresponding location in the code", -> + @CompileManager.syncFromPdf + .calledWith(@project_id, @page, @h, @v) + .should.equal true + + it "should return the positions", -> + @res.send + .calledWith(JSON.stringify + code: @codePositions + ) + .should.equal true + diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 6c75835b59..5ec1c451b8 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -4,6 +4,7 @@ require('chai').should() modulePath = require('path').join __dirname, '../../../app/js/CompileManager' tk = require("timekeeper") EventEmitter = require("events").EventEmitter +Path = require "path" describe "CompileManager", -> beforeEach -> @@ -102,3 +103,68 @@ describe "CompileManager", -> .should.equal true @callback.args[0][0].message.should.equal "rm -r #{@Settings.path.compilesDir}/#{@project_id} failed: #{@error}" + + describe "syncing", -> + beforeEach -> + @page = 1 + @h = 42.23 + @v = 87.56 + @width = 100.01 + @height = 234.56 + @line = 5 + @column = 3 + @file_name = "main.tex" + @proc = new EventEmitter() + @proc.stdout = new EventEmitter() + @proc.stderr = new EventEmitter() + @child_process.spawn = sinon.stub().returns(@proc) + @Settings.path.synctexBaseDir = (project_id) => "#{@Settings.path.compilesDir}/#{@project_id}" + + describe "syncFromCode", -> + beforeEach -> + @stdout = "NODE\t#{@page}\t#{@h}\t#{@v}\t#{@width}\t#{@height}\n" + @CompileManager.syncFromCode @project_id, @file_name, @line, @column, @callback + @proc.stdout.emit "data", @stdout + @proc.emit "close", 0 + + it "should execute the synctex binary", -> + bin_path = Path.resolve(__dirname + "/../../../bin/synctex") + synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}/output.pdf" + file_path = "#{@Settings.path.compilesDir}/#{@project_id}/#{@file_name}" + @child_process.spawn + .calledWith(bin_path, ["code", synctex_path, file_path, @line, @column]) + .should.equal true + + it "should call the callback with the parsed output", -> + @callback + .calledWith(null, [{ + page: @page + h: @h + v: @v + height: @height + width: @width + }]) + .should.equal true + + describe "syncFromPdf", -> + beforeEach -> + @stdout = "NODE\t#{@Settings.path.compilesDir}/#{@project_id}/#{@file_name}\t#{@line}\t#{@column}\n" + @CompileManager.syncFromPdf @project_id, @page, @h, @v, @callback + @proc.stdout.emit "data", @stdout + @proc.emit "close", 0 + + it "should execute the synctex binary", -> + bin_path = Path.resolve(__dirname + "/../../../bin/synctex") + synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}/output.pdf" + @child_process.spawn + .calledWith(bin_path, ["pdf", synctex_path, @page, @h, @v]) + .should.equal true + + it "should call the callback with the parsed output", -> + @callback + .calledWith(null, [{ + file: @file_name + line: @line + column: @column + }]) + .should.equal true \ No newline at end of file From 173e0a80c6abf281df1762523b0482944401f9f1 Mon Sep 17 00:00:00 2001 From: James Allen Date: Wed, 9 Apr 2014 12:37:04 +0100 Subject: [PATCH 028/754] Compile binary with install --- services/clsi/Gruntfile.coffee | 2 +- services/clsi/bin/.gitignore | 0 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 services/clsi/bin/.gitignore diff --git a/services/clsi/Gruntfile.coffee b/services/clsi/Gruntfile.coffee index c9be97fd3d..3068c676ec 100644 --- a/services/clsi/Gruntfile.coffee +++ b/services/clsi/Gruntfile.coffee @@ -78,7 +78,7 @@ module.exports = (grunt) -> ], stdio: "inherit" proc.on "close", callback - grunt.registerTask 'compile:app', ['clean:app', 'coffee:app', 'coffee:app_src', 'coffee:smoke_tests'] + grunt.registerTask 'compile:app', ['clean:app', 'coffee:app', 'coffee:app_src', 'coffee:smoke_tests', 'compile:bin'] grunt.registerTask 'run', ['compile:app', 'bunyan', 'execute'] grunt.registerTask 'compile:unit_tests', ['clean:unit_tests', 'coffee:unit_tests'] diff --git a/services/clsi/bin/.gitignore b/services/clsi/bin/.gitignore new file mode 100644 index 0000000000..e69de29bb2 From b53fed12437874787a4d99e4c01b7cc5e802f52a Mon Sep 17 00:00:00 2001 From: James Allen Date: Wed, 9 Apr 2014 12:44:51 +0100 Subject: [PATCH 029/754] Rearrange compile flags --- services/clsi/Gruntfile.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/Gruntfile.coffee b/services/clsi/Gruntfile.coffee index 3068c676ec..90f9be1608 100644 --- a/services/clsi/Gruntfile.coffee +++ b/services/clsi/Gruntfile.coffee @@ -73,8 +73,8 @@ module.exports = (grunt) -> grunt.registerTask 'compile:bin', () -> callback = @async() proc = spawn "cc", [ - "-o", "bin/synctex", "-lz", "-Isrc/synctex", - "src/synctex.c", "src/synctex/synctex_parser.c", "src/synctex/synctex_parser_utils.c" + "-o", "bin/synctex", "-Isrc/synctex", + "src/synctex.c", "src/synctex/synctex_parser.c", "src/synctex/synctex_parser_utils.c", "-lz" ], stdio: "inherit" proc.on "close", callback From 52b22a41c80b88ebe11c90fd6e9d88c7586b3af2 Mon Sep 17 00:00:00 2001 From: James Allen Date: Wed, 9 Apr 2014 15:34:54 +0100 Subject: [PATCH 030/754] Use simpler exec command with a timeout --- .../clsi/app/coffee/CompileManager.coffee | 17 ++++----------- .../unit/coffee/CompileManagerTests.coffee | 21 +++++++------------ 2 files changed, 11 insertions(+), 27 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 0cca3335c1..0a71782ca4 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -75,19 +75,10 @@ module.exports = CompileManager = _runSynctex: (args, callback = (error, stdout) ->) -> bin_path = Path.resolve(__dirname + "/../../bin/synctex") - proc = child_process.spawn bin_path, args - proc.on "error", callback - - stdout = "" - proc.stdout.on "data", (chunk) -> stdout += chunk.toString() - stderr = "" - proc.stderr.on "data", (chunk) -> stderr += chunk.toString() - - proc.on "close", (code) -> - if code == 0 - return callback(null, stdout) - else - return callback(new Error("synctex failed: #{stderr}")) + seconds = 1000 + child_process.execFile bin_path, args, timeout: 10 * seconds, (error, stdout, stderr) -> + return callback(error) if error? + callback(null, stdout) _parseSynctexFromCodeOutput: (output) -> results = [] diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 5ec1c451b8..5d110eb17e 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -114,25 +114,20 @@ describe "CompileManager", -> @line = 5 @column = 3 @file_name = "main.tex" - @proc = new EventEmitter() - @proc.stdout = new EventEmitter() - @proc.stderr = new EventEmitter() - @child_process.spawn = sinon.stub().returns(@proc) + @child_process.execFile = sinon.stub() @Settings.path.synctexBaseDir = (project_id) => "#{@Settings.path.compilesDir}/#{@project_id}" describe "syncFromCode", -> beforeEach -> - @stdout = "NODE\t#{@page}\t#{@h}\t#{@v}\t#{@width}\t#{@height}\n" + @child_process.execFile.callsArgWith(3, null, @stdout = "NODE\t#{@page}\t#{@h}\t#{@v}\t#{@width}\t#{@height}\n", "") @CompileManager.syncFromCode @project_id, @file_name, @line, @column, @callback - @proc.stdout.emit "data", @stdout - @proc.emit "close", 0 it "should execute the synctex binary", -> bin_path = Path.resolve(__dirname + "/../../../bin/synctex") synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}/output.pdf" file_path = "#{@Settings.path.compilesDir}/#{@project_id}/#{@file_name}" - @child_process.spawn - .calledWith(bin_path, ["code", synctex_path, file_path, @line, @column]) + @child_process.execFile + .calledWith(bin_path, ["code", synctex_path, file_path, @line, @column], timeout: 10000) .should.equal true it "should call the callback with the parsed output", -> @@ -148,16 +143,14 @@ describe "CompileManager", -> describe "syncFromPdf", -> beforeEach -> - @stdout = "NODE\t#{@Settings.path.compilesDir}/#{@project_id}/#{@file_name}\t#{@line}\t#{@column}\n" + @child_process.execFile.callsArgWith(3, null, @stdout = "NODE\t#{@Settings.path.compilesDir}/#{@project_id}/#{@file_name}\t#{@line}\t#{@column}\n", "") @CompileManager.syncFromPdf @project_id, @page, @h, @v, @callback - @proc.stdout.emit "data", @stdout - @proc.emit "close", 0 it "should execute the synctex binary", -> bin_path = Path.resolve(__dirname + "/../../../bin/synctex") synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}/output.pdf" - @child_process.spawn - .calledWith(bin_path, ["pdf", synctex_path, @page, @h, @v]) + @child_process.execFile + .calledWith(bin_path, ["pdf", synctex_path, @page, @h, @v], timeout: 10000) .should.equal true it "should call the callback with the parsed output", -> From abbcea2945786a02fc42d9e70edc43aa3b030a43 Mon Sep 17 00:00:00 2001 From: James Allen Date: Wed, 30 Apr 2014 11:31:37 +0100 Subject: [PATCH 031/754] Add commented out docker config --- services/clsi/config/settings.defaults.coffee | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index 18783ec4ed..8142a546b6 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -17,6 +17,17 @@ module.exports = clsiCacheDir: Path.resolve(__dirname + "/../cache") synctexBaseDir: (project_id) -> Path.join(@compilesDir, project_id) + # clsi: + # commandRunner: "docker-runner-sharelatex" + # docker: + # image: "quay.io/sharelatex/texlive-full" + # env: + # PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2013/bin/x86_64-linux/" + # HOME: "/tmp" + # modem: + # socketPath: false + # user: "tex" + internal: clsi: port: 3013 From 3213c562bc07db04b50e348c4d7e69714b2cecaa Mon Sep 17 00:00:00 2001 From: James Allen Date: Fri, 9 May 2014 14:55:37 +0100 Subject: [PATCH 032/754] Add in new metrics --- services/clsi/app.coffee | 6 ++++++ services/clsi/app/coffee/Metrics.coffee | 23 +---------------------- services/clsi/package.json | 1 + 3 files changed, 8 insertions(+), 22 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 5084ffb1ad..7f56d48ab9 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -4,6 +4,10 @@ logger = require "logger-sharelatex" logger.initialize("clsi") smokeTest = require "smoke-test-sharelatex" +Metrics = require "metrics-sharelatex" +Metrics.initialize("clsi") +Metrics.open_sockets.monitor(logger) + ProjectPersistenceManager = require "./app/js/ProjectPersistenceManager" require("./app/js/db").sync() @@ -11,6 +15,8 @@ require("./app/js/db").sync() express = require "express" app = express() +app.use Metrics.http.monitor(logger) + app.post "/project/:project_id/compile", express.bodyParser(), CompileController.compile app.del "/project/:project_id", CompileController.clearCache diff --git a/services/clsi/app/coffee/Metrics.coffee b/services/clsi/app/coffee/Metrics.coffee index 107c5ffc7e..9965b252e4 100644 --- a/services/clsi/app/coffee/Metrics.coffee +++ b/services/clsi/app/coffee/Metrics.coffee @@ -1,23 +1,2 @@ -StatsD = require('lynx') -statsd = new StatsD('localhost', 8125, {on_error:->}) - -buildKey = (key)-> "clsi.#{process.env.NODE_ENV or "testing"}.#{key}" - -module.exports = - set : (key, value, sampleRate = 1)-> - statsd.set buildKey(key), value, sampleRate - - inc : (key, sampleRate = 1)-> - statsd.increment buildKey(key), sampleRate - - Timer : class - constructor :(key, sampleRate = 1)-> - this.start = new Date() - this.key = buildKey(key) - done:-> - timeSpan = new Date - this.start - statsd.timing(this.key, timeSpan, this.sampleRate) - - gauge : (key, value, sampleRate = 1)-> - statsd.gauge key, value, sampleRate +module.exports = require "metrics-sharelatex" diff --git a/services/clsi/package.json b/services/clsi/package.json index 875e6e8566..6eb8e58729 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -12,6 +12,7 @@ "request": "~2.21.0", "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#master", "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#master", + "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#master", "sequelize": "~2.0.0-beta.2", "wrench": "~1.5.4", "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#master", From 5cd66aad5845bccea4bf00fb9cfc8056535679b5 Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 19 May 2014 07:18:57 -0400 Subject: [PATCH 033/754] Return more informative errors --- services/clsi/app.coffee | 5 ++- .../clsi/app/coffee/CompileController.coffee | 11 ++++-- services/clsi/package.json | 5 ++- .../acceptance/coffee/TimeoutTests.coffee | 4 +- .../unit/coffee/CompileControllerTests.coffee | 38 +++++++++++++++++-- 5 files changed, 50 insertions(+), 13 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 7f56d48ab9..ca93c706fe 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -13,12 +13,13 @@ ProjectPersistenceManager = require "./app/js/ProjectPersistenceManager" require("./app/js/db").sync() express = require "express" +bodyParser = require "body-parser" app = express() app.use Metrics.http.monitor(logger) -app.post "/project/:project_id/compile", express.bodyParser(), CompileController.compile -app.del "/project/:project_id", CompileController.clearCache +app.post "/project/:project_id/compile", bodyParser.json(limit: "2mb"), CompileController.compile +app.delete "/project/:project_id", CompileController.clearCache app.get "/project/:project_id/sync/code", CompileController.syncFromCode app.get "/project/:project_id/sync/pdf", CompileController.syncFromPdf diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 090c3f09fc..cddb56b1ca 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -16,8 +16,11 @@ module.exports = CompileController = CompileManager.doCompile request, (error, outputFiles = []) -> if error? logger.error err: error, project_id: request.project_id, "error running compile" - error = error.message or error - status = "failure" + if error.timedout + status = "timedout" + else + status = "error" + code = 500 else status = "failure" for file in outputFiles @@ -25,10 +28,10 @@ module.exports = CompileController = status = "success" timer.done() - res.send JSON.stringify { + res.send (code or 200), { compile: status: status - error: error + error: error?.message or error outputFiles: outputFiles.map (file) -> url: "#{Settings.apis.clsi.url}/project/#{request.project_id}/output/#{file.path}" type: file.type diff --git a/services/clsi/package.json b/services/clsi/package.json index 6eb8e58729..14a0bc9947 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -5,7 +5,6 @@ "author": "James Allen ", "dependencies": { "async": "0.2.9", - "express": "3.3.1", "lynx": "0.0.11", "mkdirp": "0.3.5", "mysql": "2.0.0-alpha7", @@ -16,7 +15,9 @@ "sequelize": "~2.0.0-beta.2", "wrench": "~1.5.4", "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#master", - "sqlite3": "~2.2.0" + "sqlite3": "~2.2.0", + "express": "^4.2.0", + "body-parser": "^1.2.0" }, "devDependencies": { "mocha": "1.10.0", diff --git a/services/clsi/test/acceptance/coffee/TimeoutTests.coffee b/services/clsi/test/acceptance/coffee/TimeoutTests.coffee index dd87e61458..66ac7a427a 100644 --- a/services/clsi/test/acceptance/coffee/TimeoutTests.coffee +++ b/services/clsi/test/acceptance/coffee/TimeoutTests.coffee @@ -22,6 +22,6 @@ describe "Timed out compile", -> it "should return a timeout error", -> @body.compile.error.should.equal "container timed out" - it "should return a failure status", -> - @body.compile.status.should.equal "failure" + it "should return a timedout status", -> + @body.compile.status.should.equal "timedout" diff --git a/services/clsi/test/unit/coffee/CompileControllerTests.coffee b/services/clsi/test/unit/coffee/CompileControllerTests.coffee index 2aefdb544a..d2babf7c2b 100644 --- a/services/clsi/test/unit/coffee/CompileControllerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileControllerTests.coffee @@ -66,7 +66,7 @@ describe "CompileController", -> it "should return the JSON response", -> @res.send - .calledWith(JSON.stringify + .calledWith(200, compile: status: "success" error: null @@ -83,14 +83,46 @@ describe "CompileController", -> it "should return the JSON response with the error", -> @res.send - .calledWith(JSON.stringify + .calledWith(500, compile: - status: "failure" + status: "error" error: @message outputFiles: [] ) .should.equal true + describe "when the request times out", -> + beforeEach -> + @error = new Error(@message = "container timed out") + @error.timedout = true + @CompileManager.doCompile = sinon.stub().callsArgWith(1, @error, null) + @CompileController.compile @req, @res + + it "should return the JSON response with the timeout status", -> + @res.send + .calledWith(200, + compile: + status: "timedout" + error: @message + outputFiles: [] + ) + .should.equal true + + describe "when the request returns no output files", -> + beforeEach -> + @CompileManager.doCompile = sinon.stub().callsArgWith(1, null, []) + @CompileController.compile @req, @res + + it "should return the JSON response with the failure status", -> + @res.send + .calledWith(200, + compile: + error: null + status: "failure" + outputFiles: [] + ) + .should.equal true + describe "syncFromCode", -> beforeEach -> @file = "main.tex" From 9261987bd8f318a6579fd42bb55f2fd2f9479d82 Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 19 May 2014 20:03:05 +0100 Subject: [PATCH 034/754] Increase size limit (arbitrarily... *shrug*) --- services/clsi/app.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index ca93c706fe..72748f129b 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -18,7 +18,7 @@ app = express() app.use Metrics.http.monitor(logger) -app.post "/project/:project_id/compile", bodyParser.json(limit: "2mb"), CompileController.compile +app.post "/project/:project_id/compile", bodyParser.json(limit: "5mb"), CompileController.compile app.delete "/project/:project_id", CompileController.clearCache app.get "/project/:project_id/sync/code", CompileController.syncFromCode From f8819fbbd775e7db321b5f509301e9662e16929a Mon Sep 17 00:00:00 2001 From: James Allen Date: Thu, 22 May 2014 12:18:56 +0100 Subject: [PATCH 035/754] Increase req and res stream timeouts to 3 minutes --- services/clsi/app.coffee | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 72748f129b..a17894edbe 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -18,6 +18,15 @@ app = express() app.use Metrics.http.monitor(logger) +# Compile requests can take longer than the default two +# minutes (including file download time), so bump up the +# timeout a bit. +TIMEOUT = threeMinutes = 3 * 60 * 1000 +app.use (req, res, next) -> + req.setTimeout TIMEOUT + res.setTimeout TIMEOUT + next() + app.post "/project/:project_id/compile", bodyParser.json(limit: "5mb"), CompileController.compile app.delete "/project/:project_id", CompileController.clearCache From bc22a371d0a25ed2e586b577a3c076fc5c7eb5a3 Mon Sep 17 00:00:00 2001 From: James Allen Date: Thu, 29 May 2014 15:49:48 +0100 Subject: [PATCH 036/754] Explicitly look for output.pdf file, not any pdf --- services/clsi/app/coffee/CompileController.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index cddb56b1ca..c73820206a 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -24,7 +24,7 @@ module.exports = CompileController = else status = "failure" for file in outputFiles - if file.type == "pdf" + if file.path?.match(/output\.pdf$/) status = "success" timer.done() From fe6c76e62d02a20b38723b85cf7b145706733d93 Mon Sep 17 00:00:00 2001 From: James Allen Date: Thu, 29 May 2014 16:40:58 +0100 Subject: [PATCH 037/754] Don't normalize path passed to synctex --- services/clsi/app/coffee/CompileManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 0a71782ca4..46e2c0f182 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -58,7 +58,7 @@ module.exports = CompileManager = # might not match the file path on the host. The .synctex.gz file however, will be accessed # wherever it is on the host. base_dir = Settings.path.synctexBaseDir(project_id) - file_path = Path.join(base_dir, file_name) + file_path = base_dir + "/" + file_name synctex_path = Path.join(Settings.path.compilesDir, project_id, "output.pdf") CompileManager._runSynctex ["code", synctex_path, file_path, line, column], (error, stdout) -> return callback(error) if error? From 98efa96f3e49f55cb2c87fae776f6b6f2665626e Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Thu, 5 Jun 2014 15:51:24 +0100 Subject: [PATCH 038/754] added caching of health checks so we can hit them heaviy from multiple places without potential concurency problems --- services/clsi/app.coffee | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index a17894edbe..ea5bfe8242 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -41,7 +41,18 @@ app.get "/project/:project_id/output/*", (req, res, next) -> app.get "/status", (req, res, next) -> res.send "CLSI is alive\n" -app.get "/health_check", smokeTest.run(require.resolve(__dirname + "/test/smoke/js/SmokeTests.js")) +resCacher = + contentType:(@setContentType)-> + send:(@code, @body)-> + +do runSmokeTest = -> + logger.log("running smoke tests") + smokeTest.run(require.resolve(__dirname + "/test/smoke/js/SmokeTests.js"))({}, resCacher) + setTimeout(runSmokeTest, 20 * 1000) + +app.get "/health_check", (req, res)-> + res.contentType(resCacher.setContentType) + res.send resCacher.code, resCacher.body app.use (error, req, res, next) -> logger.error err: error, "server error" From 8d337a26dbf8be1b00b049d7966b82f6c21c6efa Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Thu, 5 Jun 2014 16:13:06 +0100 Subject: [PATCH 039/754] add null checks and defaults for the cached smoke test results --- services/clsi/app.coffee | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index ea5bfe8242..1061760da3 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -45,14 +45,19 @@ resCacher = contentType:(@setContentType)-> send:(@code, @body)-> + #default the server to be down + code:500 + body:{} + setContentType:"application/json" + do runSmokeTest = -> logger.log("running smoke tests") smokeTest.run(require.resolve(__dirname + "/test/smoke/js/SmokeTests.js"))({}, resCacher) setTimeout(runSmokeTest, 20 * 1000) app.get "/health_check", (req, res)-> - res.contentType(resCacher.setContentType) - res.send resCacher.code, resCacher.body + res.contentType(resCacher?.setContentType) + res.send resCacher?.code, resCacher?.body app.use (error, req, res, next) -> logger.error err: error, "server error" From 3e4cfc5ba9ba1521848924dfa5610237ea0a6bc7 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Tue, 10 Jun 2014 14:09:36 +0100 Subject: [PATCH 040/754] added url into error message when downloading from filestore --- services/clsi/app/coffee/UrlFetcher.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/UrlFetcher.coffee b/services/clsi/app/coffee/UrlFetcher.coffee index a89e37e9d2..66c5edf011 100644 --- a/services/clsi/app/coffee/UrlFetcher.coffee +++ b/services/clsi/app/coffee/UrlFetcher.coffee @@ -14,10 +14,10 @@ module.exports = UrlFetcher = if res.statusCode >= 200 and res.statusCode < 300 urlStream.pipe(fileStream) else - callbackOnce(new Error("URL returned non-success status code: #{res.statusCode}")) + callbackOnce(new Error("URL returned non-success status code: #{res.statusCode} #{url}")) urlStream.on "error", (error) -> - callbackOnce(error or new Error("Something went wrong downloading the URL")) + callbackOnce(error or new Error("Something went wrong downloading the URL #{url}")) urlStream.on "end", () -> callbackOnce() From 37a8e37bdafef2a1ad29eb3e8f3ab8474e8fe0bc Mon Sep 17 00:00:00 2001 From: James Allen Date: Wed, 13 Aug 2014 10:35:51 +0000 Subject: [PATCH 041/754] Add updated knitr example output.pdf for TexLive 2014 --- .../fixtures/examples/knitr/output.pdf | Bin 43271 -> 43236 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/services/clsi/test/acceptance/fixtures/examples/knitr/output.pdf b/services/clsi/test/acceptance/fixtures/examples/knitr/output.pdf index 365ce1af85e7946954f23a63defcad9fc5f9797f..133becbe4bc1255aa65ea9005f4062a839c0caae 100644 GIT binary patch delta 30844 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

+af(*Qumk$PtWI5&N)42)FrW&2_lZ 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 delta 30820 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 z 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 SQX)8Bc6K;QN=aoYxc>oZ{$a5I From 10413b876a0f0206f12d6146dd1a0c13d8ca4b37 Mon Sep 17 00:00:00 2001 From: James Allen Date: Tue, 19 Aug 2014 12:11:56 +0100 Subject: [PATCH 042/754] Don't run smoke test by default --- services/clsi/app.coffee | 9 +++++---- services/clsi/config/settings.defaults.coffee | 5 ++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 1061760da3..3e0936279c 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -50,10 +50,11 @@ resCacher = body:{} setContentType:"application/json" -do runSmokeTest = -> - logger.log("running smoke tests") - smokeTest.run(require.resolve(__dirname + "/test/smoke/js/SmokeTests.js"))({}, resCacher) - setTimeout(runSmokeTest, 20 * 1000) +if Settings.smokeTest + do runSmokeTest = -> + logger.log("running smoke tests") + smokeTest.run(require.resolve(__dirname + "/test/smoke/js/SmokeTests.js"))({}, resCacher) + setTimeout(runSmokeTest, 20 * 1000) app.get "/health_check", (req, res)-> res.contentType(resCacher?.setContentType) diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index 8142a546b6..4169f27540 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -11,7 +11,6 @@ module.exports = dialect: "sqlite" storage: Path.resolve(__dirname + "/../db.sqlite") - path: compilesDir: Path.resolve(__dirname + "/../compiles") clsiCacheDir: Path.resolve(__dirname + "/../cache") @@ -36,5 +35,5 @@ module.exports = apis: clsi: url: "http://localhost:3013" - - + + smokeTest: false From 855ec6e4dd801d2d7ba8fd8fd54a461bf1eeea17 Mon Sep 17 00:00:00 2001 From: James Allen Date: Tue, 19 Aug 2014 14:01:01 +0100 Subject: [PATCH 043/754] Lock down module versions --- services/clsi/package.json | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index 14a0bc9947..0f154297b5 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -2,6 +2,10 @@ "name": "node-clsi", "description": "A Node.js implementation of the CLSI LaTeX web-API", "version": "0.0.1-dev", + "repository": { + "type": "git", + "url": "https://github.com/sharelatex/clsi-sharelatex.git" + }, "author": "James Allen ", "dependencies": { "async": "0.2.9", @@ -9,12 +13,12 @@ "mkdirp": "0.3.5", "mysql": "2.0.0-alpha7", "request": "~2.21.0", - "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#master", - "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#master", - "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#master", + "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.0.0", + "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", + "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.0.0", "sequelize": "~2.0.0-beta.2", "wrench": "~1.5.4", - "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#master", + "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v1.0.0", "sqlite3": "~2.2.0", "express": "^4.2.0", "body-parser": "^1.2.0" From ee891cc030246a7928d3c1dfa04b7bbc9dc104c5 Mon Sep 17 00:00:00 2001 From: James Allen Date: Tue, 19 Aug 2014 14:03:12 +0100 Subject: [PATCH 044/754] Release version 0.1.0 --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index 0f154297b5..9dbf7cb804 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -1,7 +1,7 @@ { "name": "node-clsi", "description": "A Node.js implementation of the CLSI LaTeX web-API", - "version": "0.0.1-dev", + "version": "0.1.0", "repository": { "type": "git", "url": "https://github.com/sharelatex/clsi-sharelatex.git" From 9059f62543671dcd8819edd055978b0cabf685c5 Mon Sep 17 00:00:00 2001 From: James Allen Date: Tue, 23 Sep 2014 10:52:01 +0100 Subject: [PATCH 045/754] Lock down sequelize version --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index 9dbf7cb804..31f768bd1a 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -16,7 +16,7 @@ "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.0.0", "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.0.0", - "sequelize": "~2.0.0-beta.2", + "sequelize": "2.0.0-beta.2", "wrench": "~1.5.4", "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v1.0.0", "sqlite3": "~2.2.0", From b74e9b02f078343b66d51663b29674cd8893f55c Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 29 Sep 2014 16:05:44 +0100 Subject: [PATCH 046/754] Bump version to 0.1.1 --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index 31f768bd1a..c2d4a669ac 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -1,7 +1,7 @@ { "name": "node-clsi", "description": "A Node.js implementation of the CLSI LaTeX web-API", - "version": "0.1.0", + "version": "0.1.1", "repository": { "type": "git", "url": "https://github.com/sharelatex/clsi-sharelatex.git" From a698795558f5e4a7efcedcf7ade9926580b072bf Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Fri, 17 Oct 2014 10:14:23 +0100 Subject: [PATCH 047/754] up timeout to 6 mins --- services/clsi/app.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 1061760da3..decbf43f30 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -21,7 +21,7 @@ app.use Metrics.http.monitor(logger) # Compile requests can take longer than the default two # minutes (including file download time), so bump up the # timeout a bit. -TIMEOUT = threeMinutes = 3 * 60 * 1000 +TIMEOUT = 6 * 60 * 1000 app.use (req, res, next) -> req.setTimeout TIMEOUT res.setTimeout TIMEOUT From c37447a2d65fa367aaa8489b7c5ecf6cf0c57679 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Fri, 17 Oct 2014 11:03:08 +0100 Subject: [PATCH 048/754] increase max compile to 4 mins --- services/clsi/app/coffee/RequestParser.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/RequestParser.coffee b/services/clsi/app/coffee/RequestParser.coffee index 66765fb180..d98ca82435 100644 --- a/services/clsi/app/coffee/RequestParser.coffee +++ b/services/clsi/app/coffee/RequestParser.coffee @@ -1,6 +1,6 @@ module.exports = RequestParser = VALID_COMPILERS: ["pdflatex", "latex", "xelatex", "lualatex"] - MAX_TIMEOUT: 60 + MAX_TIMEOUT: 300 parse: (body, callback = (error, data) ->) -> response = {} From 52165261910ef8fe083c6bee15c56fcc1b3b4a09 Mon Sep 17 00:00:00 2001 From: James Allen Date: Tue, 28 Oct 2014 12:07:17 +0000 Subject: [PATCH 049/754] Force mimetype of output files to be safe --- services/clsi/app.coffee | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index c79eb92353..6bc412b59a 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -4,6 +4,8 @@ logger = require "logger-sharelatex" logger.initialize("clsi") smokeTest = require "smoke-test-sharelatex" +Path = require "path" + Metrics = require "metrics-sharelatex" Metrics.initialize("clsi") Metrics.open_sockets.monitor(logger) @@ -33,7 +35,13 @@ app.delete "/project/:project_id", CompileController.clearCache app.get "/project/:project_id/sync/code", CompileController.syncFromCode app.get "/project/:project_id/sync/pdf", CompileController.syncFromPdf -staticServer = express.static(Settings.path.compilesDir) +staticServer = express.static Settings.path.compilesDir, setHeaders: (res, path, stat) -> + if Path.basename(path) == "output.pdf" + res.set("Content-Type", "application/pdf") + else + # Force plain treatment of other file types to prevent hosting of HTTP/JS files + # that could be used in same-origin/XSS attacks. + res.set("Content-Type", "text/plain") app.get "/project/:project_id/output/*", (req, res, next) -> req.url = "/#{req.params.project_id}/#{req.params[0]}" staticServer(req, res, next) From a39c356014938066999978d696955e06955201db Mon Sep 17 00:00:00 2001 From: James Allen Date: Wed, 29 Oct 2014 10:59:32 +0000 Subject: [PATCH 050/754] Update acceptance tests for new knitr, and remove markdown --- .../fixtures/examples/knitr/output.pdf | Bin 43236 -> 43236 bytes .../markdown-included/chapters/chapter1.md | 17 ------------- .../examples/markdown-included/main.tex | 9 ------- .../examples/markdown-included/output.pdf | Bin 34051 -> 0 bytes .../examples/markdown-standalone/main.md | 23 ------------------ .../examples/markdown-standalone/output.pdf | Bin 95563 -> 0 bytes 6 files changed, 49 deletions(-) delete mode 100644 services/clsi/test/acceptance/fixtures/examples/markdown-included/chapters/chapter1.md delete mode 100644 services/clsi/test/acceptance/fixtures/examples/markdown-included/main.tex delete mode 100644 services/clsi/test/acceptance/fixtures/examples/markdown-included/output.pdf delete mode 100644 services/clsi/test/acceptance/fixtures/examples/markdown-standalone/main.md delete mode 100644 services/clsi/test/acceptance/fixtures/examples/markdown-standalone/output.pdf diff --git a/services/clsi/test/acceptance/fixtures/examples/knitr/output.pdf b/services/clsi/test/acceptance/fixtures/examples/knitr/output.pdf index 133becbe4bc1255aa65ea9005f4062a839c0caae..a1f93799e58349967efead8c4583661423ae3f23 100644 GIT binary patch delta 575 zcmaEIk?F}rrU|k2A1eOH>=Ma{D#>1;dp04YDB-}?z?-?BKUWLgYzVbF!XzUS`=hYp z$;TgZ?^irv6xp*rN zR#z%f7F&D2ZqGhNt!q(kMN!}8irK2Qu(w|j1<~9fJpV=<7{EhVS%8R})ot{Q!zW$b=wtD_8!BgRZV zLm9aro6hgw@*_sc__m0D^46)__Z~UwojPMK&;9SW&(9}YZ#|H zp0^MxPv&S&%-FKQ&9XTDX~(-4cDy35GWMKY7_(u|*?;<*&v$p9ICpAhvICc$teNoq zaF471SVQ(*`ZIZ>knm{0kn`E-m`MZSA^zC4)`oa<_OhqzyBe@3ERZS@8Bj-q(fq-}03@yR4qS zm2>ss4XTz(tp>j-4_~lD0G42+0>amy7-UaPf4A<+N*m)iU#Z?6xcn=Wvd{*52fn zo$|a(yyoB1kHwv5a)NDojs5O@6B5^-npkF3b3fnwf9r`W_NzCF2OC|!@i8>+|KmqH z_NbIDRJi5im6V<)vSf*M{1WR=LHp-wOl~oqYy3U5?}gv}<&92<&7OGZMchrhKmF$YqsEiUi&FYlBzvTv-Sb>D zXiGg~!}m)kCT|oH-mJ=Wa)G*mg`u&bfswh1QIsZ^zHfetOJYf?f`*Hgk%6HJLUOa{ y;_EEVmL@Ld1{SWCZjL6FmafJ|j?SirPR>rQZqAlQCWe;gb_zCxluXuNu@wMv9|N@j diff --git a/services/clsi/test/acceptance/fixtures/examples/markdown-included/chapters/chapter1.md b/services/clsi/test/acceptance/fixtures/examples/markdown-included/chapters/chapter1.md deleted file mode 100644 index 920d9be763..0000000000 --- a/services/clsi/test/acceptance/fixtures/examples/markdown-included/chapters/chapter1.md +++ /dev/null @@ -1,17 +0,0 @@ -Section Title -------------- - -* List item one -* List item two - -: Sample grid table. - -+---------------+---------------+--------------------+ -| Fruit | Price | Advantages | -+===============+===============+====================+ -| Bananas | $1.34 | - built-in wrapper | -| | | - bright color | -+---------------+---------------+--------------------+ -| Oranges | $2.10 | - cures scurvy | -| | | - tasty | -+---------------+---------------+--------------------+ diff --git a/services/clsi/test/acceptance/fixtures/examples/markdown-included/main.tex b/services/clsi/test/acceptance/fixtures/examples/markdown-included/main.tex deleted file mode 100644 index bfda9624c2..0000000000 --- a/services/clsi/test/acceptance/fixtures/examples/markdown-included/main.tex +++ /dev/null @@ -1,9 +0,0 @@ -\documentclass{article} -\usepackage{longtable} -\usepackage{booktabs, multicol, multirow} - -\begin{document} - -\input{chapters/chapter1} - -\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/markdown-included/output.pdf b/services/clsi/test/acceptance/fixtures/examples/markdown-included/output.pdf deleted file mode 100644 index 81a1136e80d7998772ba056439ecb7aedd4f2383..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34051 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 NHsDfKb@g}S0sy_uxYYmv diff --git a/services/clsi/test/acceptance/fixtures/examples/markdown-standalone/main.md b/services/clsi/test/acceptance/fixtures/examples/markdown-standalone/main.md deleted file mode 100644 index 036d971ccd..0000000000 --- a/services/clsi/test/acceptance/fixtures/examples/markdown-standalone/main.md +++ /dev/null @@ -1,23 +0,0 @@ -% Title -% Author -% Date - -Chapter title -============= - -Section Title -------------- - -Hello world. Have a nice table: - -: Sample grid table. - -+---------------+---------------+--------------------+ -| Fruit | Price | Advantages | -+===============+===============+====================+ -| Bananas | $1.34 | - built-in wrapper | -| | | - bright color | -+---------------+---------------+--------------------+ -| Oranges | $2.10 | - cures scurvy | -| | | - tasty | -+---------------+---------------+--------------------+ diff --git a/services/clsi/test/acceptance/fixtures/examples/markdown-standalone/output.pdf b/services/clsi/test/acceptance/fixtures/examples/markdown-standalone/output.pdf deleted file mode 100644 index 9991ab82aefe9b86c25eee886e971bbf2e5acb7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 95563 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 From b8335b6b643893050bb2dfacd4eb562745c40d69 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Thu, 27 Nov 2014 16:11:00 +0000 Subject: [PATCH 051/754] respect the status code on the error if it exists --- services/clsi/app.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index c79eb92353..f9b7760a0d 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -62,7 +62,7 @@ app.get "/health_check", (req, res)-> app.use (error, req, res, next) -> logger.error err: error, "server error" - res.send 500 + res.send err?.statusCode || 500 app.listen port = (Settings.internal?.clsi?.port or 3013), host = (Settings.internal?.clsi?.host or "localhost"), (error) -> logger.log "CLSI listening on #{host}:#{port}" From c119d2ba791969a1a4d3fa9e46728774a8acfc44 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Thu, 27 Nov 2014 16:19:01 +0000 Subject: [PATCH 052/754] err != error --- services/clsi/app.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 56fd9bfc75..5f55fdfba7 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -70,7 +70,7 @@ app.get "/health_check", (req, res)-> app.use (error, req, res, next) -> logger.error err: error, "server error" - res.send err?.statusCode || 500 + res.send error?.statusCode || 500 app.listen port = (Settings.internal?.clsi?.port or 3013), host = (Settings.internal?.clsi?.host or "localhost"), (error) -> logger.log "CLSI listening on #{host}:#{port}" From 95373d2b7ea9c90f1966f573a1c4f7c62d43ab19 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Tue, 2 Dec 2014 14:30:13 +0000 Subject: [PATCH 053/754] send a strong etag for the output.pdf file, needed for byte ranges in pdf.js --- services/clsi/app.coffee | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 5f55fdfba7..0d9904dc3e 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -38,10 +38,17 @@ app.get "/project/:project_id/sync/pdf", CompileController.syncFromPdf staticServer = express.static Settings.path.compilesDir, setHeaders: (res, path, stat) -> if Path.basename(path) == "output.pdf" res.set("Content-Type", "application/pdf") + # Calculate an etag in the same way as nginx + # https://github.com/tj/send/issues/65 + etag = (path, stat) -> + '"' + Math.ceil(+stat.mtime / 1000).toString(16) + + '-' + Number(stat.size).toString(16) + '"' + res.set("Etag", etag(path, stat)) else # Force plain treatment of other file types to prevent hosting of HTTP/JS files # that could be used in same-origin/XSS attacks. res.set("Content-Type", "text/plain") + app.get "/project/:project_id/output/*", (req, res, next) -> req.url = "/#{req.params.project_id}/#{req.params[0]}" staticServer(req, res, next) From 49b7cdc854493689d47c2bf99596c9ac319ca442 Mon Sep 17 00:00:00 2001 From: James Allen Date: Thu, 4 Dec 2014 21:37:09 +0000 Subject: [PATCH 054/754] Add in missing error check --- services/clsi/app/coffee/OutputFileFinder.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/OutputFileFinder.coffee b/services/clsi/app/coffee/OutputFileFinder.coffee index c04edbc28f..6df377584d 100644 --- a/services/clsi/app/coffee/OutputFileFinder.coffee +++ b/services/clsi/app/coffee/OutputFileFinder.coffee @@ -9,7 +9,8 @@ module.exports = OutputFileFinder = for resource in resources incomingResources[resource.path] = true - OutputFileFinder._getAllFiles directory, (error, allFiles) -> + OutputFileFinder._getAllFiles directory, (error, allFiles = []) -> + return callback(error) if error? jobs = [] outputFiles = [] for file in allFiles From 2647eb0ec77ee98e348d9a84da8ca516590ed36a Mon Sep 17 00:00:00 2001 From: James Allen Date: Thu, 4 Dec 2014 22:07:37 +0000 Subject: [PATCH 055/754] Check file is not a symlink before returning it --- services/clsi/app.coffee | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 0d9904dc3e..fe9ad532fe 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -5,6 +5,7 @@ logger.initialize("clsi") smokeTest = require "smoke-test-sharelatex" Path = require "path" +fs = require "fs" Metrics = require "metrics-sharelatex" Metrics.initialize("clsi") @@ -49,9 +50,26 @@ staticServer = express.static Settings.path.compilesDir, setHeaders: (res, path, # that could be used in same-origin/XSS attacks. res.set("Content-Type", "text/plain") + + app.get "/project/:project_id/output/*", (req, res, next) -> - req.url = "/#{req.params.project_id}/#{req.params[0]}" - staticServer(req, res, next) + basePath = Path.resolve("#{Settings.path.compilesDir}/#{req.params.project_id}") + path = Path.normalize("#{basePath}/#{req.params[0]}") + if path.slice(0, basePath.length) != basePath + logger.warn path: req.params[0], project_id: req.params.project_id, "trying to leave project directory, aborting" + res.send(404) + return + fs.lstat path, (error, stats) -> + if error? + if error.code == "ENOENT" + error.statusCode = 404 + return next(error) + if stats.isSymbolicLink() + error = new Error("file is a symlink") + error.statusCode = 404 + return next(error) + req.url = "/#{req.params.project_id}/#{req.params[0]}" + staticServer(req, res, next) app.get "/status", (req, res, next) -> res.send "CLSI is alive\n" From 9742aa8fb487824fbb23a62bcd2535ea6d91059e Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Thu, 4 Dec 2014 23:54:22 +0000 Subject: [PATCH 056/754] replaced old symlink logic with tested middlewear based on fs.realpath --- services/clsi/app.coffee | 23 +------ .../coffee/SymlinkCheckerMiddlewear.coffee | 17 ++++++ .../SymlinkCheckerMiddlewearTests.coffee | 60 +++++++++++++++++++ 3 files changed, 80 insertions(+), 20 deletions(-) create mode 100644 services/clsi/app/coffee/SymlinkCheckerMiddlewear.coffee create mode 100644 services/clsi/test/unit/coffee/SymlinkCheckerMiddlewearTests.coffee diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index fe9ad532fe..44d31575cc 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -50,26 +50,9 @@ staticServer = express.static Settings.path.compilesDir, setHeaders: (res, path, # that could be used in same-origin/XSS attacks. res.set("Content-Type", "text/plain") - - -app.get "/project/:project_id/output/*", (req, res, next) -> - basePath = Path.resolve("#{Settings.path.compilesDir}/#{req.params.project_id}") - path = Path.normalize("#{basePath}/#{req.params[0]}") - if path.slice(0, basePath.length) != basePath - logger.warn path: req.params[0], project_id: req.params.project_id, "trying to leave project directory, aborting" - res.send(404) - return - fs.lstat path, (error, stats) -> - if error? - if error.code == "ENOENT" - error.statusCode = 404 - return next(error) - if stats.isSymbolicLink() - error = new Error("file is a symlink") - error.statusCode = 404 - return next(error) - req.url = "/#{req.params.project_id}/#{req.params[0]}" - staticServer(req, res, next) +app.get "/project/:project_id/output/*", require("./app/js/SymlinkCheckerMiddlewear"), (req, res, next) -> + req.url = "/#{req.params.project_id}/#{req.params[0]}" + staticServer(req, res, next) app.get "/status", (req, res, next) -> res.send "CLSI is alive\n" diff --git a/services/clsi/app/coffee/SymlinkCheckerMiddlewear.coffee b/services/clsi/app/coffee/SymlinkCheckerMiddlewear.coffee new file mode 100644 index 0000000000..b7baf1fa77 --- /dev/null +++ b/services/clsi/app/coffee/SymlinkCheckerMiddlewear.coffee @@ -0,0 +1,17 @@ +Path = require("path") +fs = require("fs") +Settings = require("settings-sharelatex") +logger = require("logger-sharelatex") + + +module.exports = (req, res, next)-> + basePath = Path.resolve("#{Settings.path.compilesDir}/#{req.params.project_id}") + requestedFsPath = Path.normalize("#{basePath}/#{req.params[0]}") + fs.realpath requestedFsPath, (err, realFsPath)-> + if err? + return res.send(500) + else if requestedFsPath != realFsPath + logger.warn requestedFsPath:requestedFsPath, realFsPath:realFsPath, path: req.params[0], project_id: req.params.project_id, "trying to access a different file (symlink), aborting" + return res.send(404) + else + return next() diff --git a/services/clsi/test/unit/coffee/SymlinkCheckerMiddlewearTests.coffee b/services/clsi/test/unit/coffee/SymlinkCheckerMiddlewearTests.coffee new file mode 100644 index 0000000000..50bd4271b5 --- /dev/null +++ b/services/clsi/test/unit/coffee/SymlinkCheckerMiddlewearTests.coffee @@ -0,0 +1,60 @@ +should = require('chai').should() +SandboxedModule = require('sandboxed-module') +assert = require('assert') +path = require('path') +sinon = require('sinon') +modulePath = path.join __dirname, "../../../app/js/SymlinkCheckerMiddlewear" +expect = require("chai").expect + +describe "SymlinkCheckerMiddlewear", -> + + beforeEach -> + + @settings = + path: + compilesDir: "/compiles/here" + + @fs = {} + @SymlinkCheckerMiddlewear = SandboxedModule.require modulePath, requires: + "settings-sharelatex":@settings + "logger-sharelatex": + log:-> + warn:-> + "fs":@fs + @req = + params: + project_id:"12345" + + @res = {} + @req.params[0]= "output.pdf" + + + describe "sending a normal file through", -> + beforeEach -> + @fs.realpath = sinon.stub().callsArgWith(1, null, "#{@settings.path.compilesDir}/#{@req.params.project_id}/output.pdf") + + it "should call next", (done)-> + @SymlinkCheckerMiddlewear @req, @res, done + + + describe "with a symlink file", -> + beforeEach -> + @fs.realpath = sinon.stub().callsArgWith(1, null, "/etc/#{@req.params.project_id}/output.pdf") + + it "should send a 404", (done)-> + @res.send = (resCode)-> + resCode.should.equal 404 + done() + @SymlinkCheckerMiddlewear @req, @res + + describe "with an error from fs.realpath", -> + + beforeEach -> + @fs.realpath = sinon.stub().callsArgWith(1, "error") + + it "should send a 500", (done)-> + @res.send = (resCode)-> + resCode.should.equal 500 + done() + @SymlinkCheckerMiddlewear @req, @res + From ffa7919e463455a1ab89e3701618b5627e0f6454 Mon Sep 17 00:00:00 2001 From: James Allen Date: Tue, 9 Dec 2014 11:07:58 +0000 Subject: [PATCH 057/754] Use find -type f to get a list of output files --- services/clsi/Gruntfile.coffee | 1 + .../clsi/app/coffee/OutputFileFinder.coffee | 57 ++++++++----------- .../unit/coffee/OutputFileFinderTests.coffee | 35 ++++++++---- 3 files changed, 51 insertions(+), 42 deletions(-) diff --git a/services/clsi/Gruntfile.coffee b/services/clsi/Gruntfile.coffee index 90f9be1608..09d37346cf 100644 --- a/services/clsi/Gruntfile.coffee +++ b/services/clsi/Gruntfile.coffee @@ -50,6 +50,7 @@ module.exports = (grunt) -> unit: options: reporter: "spec" + grep: grunt.option("grep") src: ["test/unit/js/**/*.js"] acceptance: options: diff --git a/services/clsi/app/coffee/OutputFileFinder.coffee b/services/clsi/app/coffee/OutputFileFinder.coffee index 6df377584d..634b3421c0 100644 --- a/services/clsi/app/coffee/OutputFileFinder.coffee +++ b/services/clsi/app/coffee/OutputFileFinder.coffee @@ -1,7 +1,7 @@ async = require "async" fs = require "fs" Path = require "path" -wrench = require "wrench" +spawn = require("child_process").spawn module.exports = OutputFileFinder = findOutputFiles: (resources, directory, callback = (error, outputFiles) ->) -> @@ -16,44 +16,37 @@ module.exports = OutputFileFinder = for file in allFiles do (file) -> jobs.push (callback) -> - if incomingResources[file.path] + if incomingResources[file] return callback() else - OutputFileFinder._isDirectory Path.join(directory, file.path), (error, directory) -> - return callback(error) if error? - if !directory - outputFiles.push file - callback() + outputFiles.push { + path: file + type: file.match(/\.([^\.]+)$/)?[1] + } + callback() async.series jobs, (error) -> return callback(error) if error? callback null, outputFiles - _isDirectory: (path, callback = (error, directory) ->) -> - fs.stat path, (error, stat) -> - callback error, stat?.isDirectory() - - _getAllFiles: (directory, _callback = (error, outputFiles) ->) -> - callback = (error, outputFiles) -> - _callback(error, outputFiles) + _getAllFiles: (directory, _callback = (error, fileList) ->) -> + callback = (error, fileList) -> + _callback(error, fileList) _callback = () -> - outputFiles = [] - - wrench.readdirRecursive directory, (error, files) => - if error? - if error.code == "ENOENT" - # Directory doesn't exist, which is not a problem - return callback(null, []) - else - return callback(error) - - # readdirRecursive returns multiple times and finishes with a null response - if !files? - return callback(null, outputFiles) - - for file in files - outputFiles.push - path: file - type: file.match(/\.([^\.]+)$/)?[1] + proc = spawn("find", [directory, "-type", "f"]) + stdout = "" + proc.stdout.on "data", (chunk) -> + stdout += chunk.toString() + proc.on "error", callback + proc.on "close", (code) -> + if code != 0 + error = new Error("find returned non-zero exit code: #{code}") + return callback(error) + + fileList = stdout.trim().split("\n") + fileList = fileList.map (file) -> + # Strip leading directory + path = Path.relative(directory, file) + return callback null, fileList diff --git a/services/clsi/test/unit/coffee/OutputFileFinderTests.coffee b/services/clsi/test/unit/coffee/OutputFileFinderTests.coffee index b429bf1a5e..76adca4dc8 100644 --- a/services/clsi/test/unit/coffee/OutputFileFinderTests.coffee +++ b/services/clsi/test/unit/coffee/OutputFileFinderTests.coffee @@ -4,29 +4,25 @@ require('chai').should() modulePath = require('path').join __dirname, '../../../app/js/OutputFileFinder' path = require "path" expect = require("chai").expect +EventEmitter = require("events").EventEmitter describe "OutputFileFinder", -> beforeEach -> @OutputFileFinder = SandboxedModule.require modulePath, requires: "fs": @fs = {} - "wrench": @wrench = {} + "child_process": spawn: @spawn = sinon.stub() @directory = "/test/dir" @callback = sinon.stub() describe "findOutputFiles", -> beforeEach -> @resource_path = "resource/path.tex" - @output_paths = ["output.pdf", "extra", "extra/file.tex"] + @output_paths = ["output.pdf", "extra/file.tex"] + @all_paths = @output_paths.concat [@resource_path] @resources = [ path: @resource_path = "resource/path.tex" ] - @OutputFileFinder._isDirectory = (dirPath, callback = (error, directory) ->) => - callback null, dirPath == path.join(@directory, "extra") - - @wrench.readdirRecursive = (dir, callback) => - callback(null, [@resource_path].concat(@output_paths)) - callback(null, null) - sinon.spy @wrench, "readdirRecursive" + @OutputFileFinder._getAllFiles = sinon.stub().callsArgWith(1, null, @all_paths) @OutputFileFinder.findOutputFiles @resources, @directory, (error, @outputFiles) => it "should only return the output files, not directories or resource paths", -> @@ -37,5 +33,24 @@ describe "OutputFileFinder", -> path: "extra/file.tex", type: "tex" }] - + + describe "_getAllFiles", -> + beforeEach -> + @proc = new EventEmitter() + @proc.stdout = new EventEmitter() + @spawn.returns @proc + @directory = "/base/dir" + @OutputFileFinder._getAllFiles @directory, @callback + + @proc.stdout.emit( + "data", + ["/base/dir/main.tex", "/base/dir/chapters/chapter1.tex"].join("\n") + "\n" + ) + @proc.emit "close", 0 + + it "should call the callback with the relative file paths", -> + @callback.calledWith( + null, + ["main.tex", "chapters/chapter1.tex"] + ).should.equal true From 42af7c4e63e88ade7da621c8c025d8f354a9edbb Mon Sep 17 00:00:00 2001 From: James Allen Date: Tue, 9 Dec 2014 11:16:16 +0000 Subject: [PATCH 058/754] Add in some debugging logging --- services/clsi/app/coffee/OutputFileFinder.coffee | 8 +++++++- .../clsi/test/unit/coffee/OutputFileFinderTests.coffee | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/OutputFileFinder.coffee b/services/clsi/app/coffee/OutputFileFinder.coffee index 634b3421c0..8ef06b6bba 100644 --- a/services/clsi/app/coffee/OutputFileFinder.coffee +++ b/services/clsi/app/coffee/OutputFileFinder.coffee @@ -2,12 +2,15 @@ async = require "async" fs = require "fs" Path = require "path" spawn = require("child_process").spawn +logger = require "logger-sharelatex" module.exports = OutputFileFinder = findOutputFiles: (resources, directory, callback = (error, outputFiles) ->) -> incomingResources = {} for resource in resources incomingResources[resource.path] = true + + logger.log directory: directory, "getting output files" OutputFileFinder._getAllFiles directory, (error, allFiles = []) -> return callback(error) if error? @@ -33,8 +36,11 @@ module.exports = OutputFileFinder = callback = (error, fileList) -> _callback(error, fileList) _callback = () -> + + args = [directory, "-type", "f"] + logger.log args: args, "running find command" - proc = spawn("find", [directory, "-type", "f"]) + proc = spawn("find", args) stdout = "" proc.stdout.on "data", (chunk) -> stdout += chunk.toString() diff --git a/services/clsi/test/unit/coffee/OutputFileFinderTests.coffee b/services/clsi/test/unit/coffee/OutputFileFinderTests.coffee index 76adca4dc8..6acddf6bd8 100644 --- a/services/clsi/test/unit/coffee/OutputFileFinderTests.coffee +++ b/services/clsi/test/unit/coffee/OutputFileFinderTests.coffee @@ -11,6 +11,7 @@ describe "OutputFileFinder", -> @OutputFileFinder = SandboxedModule.require modulePath, requires: "fs": @fs = {} "child_process": spawn: @spawn = sinon.stub() + "logger-sharelatex": { log: sinon.stub() } @directory = "/test/dir" @callback = sinon.stub() From 1313b06fb74628350599e2fce7afc7ad36c52fcf Mon Sep 17 00:00:00 2001 From: James Allen Date: Tue, 9 Dec 2014 11:25:23 +0000 Subject: [PATCH 059/754] Don't return error if directory doesn't exist yet --- .../clsi/app/coffee/OutputFileFinder.coffee | 5 +-- .../unit/coffee/OutputFileFinderTests.coffee | 37 ++++++++++++------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/services/clsi/app/coffee/OutputFileFinder.coffee b/services/clsi/app/coffee/OutputFileFinder.coffee index 8ef06b6bba..fbfd38ed55 100644 --- a/services/clsi/app/coffee/OutputFileFinder.coffee +++ b/services/clsi/app/coffee/OutputFileFinder.coffee @@ -47,9 +47,8 @@ module.exports = OutputFileFinder = proc.on "error", callback proc.on "close", (code) -> if code != 0 - error = new Error("find returned non-zero exit code: #{code}") - return callback(error) - + logger.warn {directory, code}, "find returned error, directory likely doesn't exist" + return callback null, [] fileList = stdout.trim().split("\n") fileList = fileList.map (file) -> # Strip leading directory diff --git a/services/clsi/test/unit/coffee/OutputFileFinderTests.coffee b/services/clsi/test/unit/coffee/OutputFileFinderTests.coffee index 6acddf6bd8..46d8c1fc42 100644 --- a/services/clsi/test/unit/coffee/OutputFileFinderTests.coffee +++ b/services/clsi/test/unit/coffee/OutputFileFinderTests.coffee @@ -11,7 +11,7 @@ describe "OutputFileFinder", -> @OutputFileFinder = SandboxedModule.require modulePath, requires: "fs": @fs = {} "child_process": spawn: @spawn = sinon.stub() - "logger-sharelatex": { log: sinon.stub() } + "logger-sharelatex": { log: sinon.stub(), warn: sinon.stub() } @directory = "/test/dir" @callback = sinon.stub() @@ -43,15 +43,26 @@ describe "OutputFileFinder", -> @directory = "/base/dir" @OutputFileFinder._getAllFiles @directory, @callback - @proc.stdout.emit( - "data", - ["/base/dir/main.tex", "/base/dir/chapters/chapter1.tex"].join("\n") + "\n" - ) - @proc.emit "close", 0 - - it "should call the callback with the relative file paths", -> - @callback.calledWith( - null, - ["main.tex", "chapters/chapter1.tex"] - ).should.equal true - + describe "successfully", -> + beforeEach -> + @proc.stdout.emit( + "data", + ["/base/dir/main.tex", "/base/dir/chapters/chapter1.tex"].join("\n") + "\n" + ) + @proc.emit "close", 0 + + it "should call the callback with the relative file paths", -> + @callback.calledWith( + null, + ["main.tex", "chapters/chapter1.tex"] + ).should.equal true + + describe "when the directory doesn't exist", -> + beforeEach -> + @proc.emit "close", 1 + + it "should call the callback with a blank array", -> + @callback.calledWith( + null, + [] + ).should.equal true From 1f8ddac27d490d4f005cb146767de236032d2c73 Mon Sep 17 00:00:00 2001 From: James Allen Date: Tue, 10 Feb 2015 13:19:42 +0000 Subject: [PATCH 060/754] Release version 0.1.2 --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index c2d4a669ac..1afcfeab8f 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -1,7 +1,7 @@ { "name": "node-clsi", "description": "A Node.js implementation of the CLSI LaTeX web-API", - "version": "0.1.1", + "version": "0.1.2", "repository": { "type": "git", "url": "https://github.com/sharelatex/clsi-sharelatex.git" From 561ce7dc603d5033cd5546fb07d1fa43ad684e5e Mon Sep 17 00:00:00 2001 From: James Allen Date: Wed, 11 Feb 2015 12:03:36 +0000 Subject: [PATCH 061/754] Sanitize rootResourcePath --- services/clsi/app/coffee/RequestParser.coffee | 6 +++++- services/clsi/test/unit/coffee/RequestParserTests.coffee | 9 ++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/RequestParser.coffee b/services/clsi/app/coffee/RequestParser.coffee index d98ca82435..93c843dd4f 100644 --- a/services/clsi/app/coffee/RequestParser.coffee +++ b/services/clsi/app/coffee/RequestParser.coffee @@ -27,10 +27,12 @@ module.exports = RequestParser = response.timeout = response.timeout * 1000 # milliseconds response.resources = (@_parseResource(resource) for resource in (compile.resources or [])) - response.rootResourcePath = @_parseAttribute "rootResourcePath", + + rootResourcePath = @_parseAttribute "rootResourcePath", compile.rootResourcePath default: "main.tex" type: "string" + response.rootResourcePath = RequestParser._sanitizePath(rootResourcePath) catch error return callback error @@ -72,3 +74,5 @@ module.exports = RequestParser = throw "Default not implemented" return attribute + _sanitizePath: (path) -> + path.replace(/[^a-zA-Z0-9_\-;.,\/ ]/g, "") \ No newline at end of file diff --git a/services/clsi/test/unit/coffee/RequestParserTests.coffee b/services/clsi/test/unit/coffee/RequestParserTests.coffee index 35ad6f4e14..8545ff22a2 100644 --- a/services/clsi/test/unit/coffee/RequestParserTests.coffee +++ b/services/clsi/test/unit/coffee/RequestParserTests.coffee @@ -204,6 +204,13 @@ describe "RequestParser", -> @callback.calledWith("rootResourcePath attribute should be a string") .should.equal true - + describe "with a root resource path that needs escaping", -> + beforeEach -> + @validRequest.compile.rootResourcePath = "`rm -rf foo`.tex" + @RequestParser.parse @validRequest, @callback + @data = @callback.args[0][1] + + it "should return the escaped resource", -> + @data.rootResourcePath.should.equal "rm -rf foo.tex" From 80382d5c15c562802be254808f484c07d75857fb Mon Sep 17 00:00:00 2001 From: James Allen Date: Fri, 13 Feb 2015 11:21:35 +0000 Subject: [PATCH 062/754] Allow non-latin characters in the rootResourcePath --- services/clsi/app/coffee/RequestParser.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/RequestParser.coffee b/services/clsi/app/coffee/RequestParser.coffee index 93c843dd4f..d9a2e9bbe4 100644 --- a/services/clsi/app/coffee/RequestParser.coffee +++ b/services/clsi/app/coffee/RequestParser.coffee @@ -75,4 +75,5 @@ module.exports = RequestParser = return attribute _sanitizePath: (path) -> - path.replace(/[^a-zA-Z0-9_\-;.,\/ ]/g, "") \ No newline at end of file + # See http://php.net/manual/en/function.escapeshellcmd.php + path.replace(/[\#\&\;\`\|\*\?\~\<\>\^\(\)\[\]\{\}\$\\\,\x0A\xFF]/g, "") \ No newline at end of file From 4532cd14b00413f12a630a4ab1dbe038a005080e Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Fri, 13 Feb 2015 11:28:43 +0000 Subject: [PATCH 063/754] update sanitizePath regex remove accidental inclusion of , and add null char \x00 --- services/clsi/app/coffee/RequestParser.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/RequestParser.coffee b/services/clsi/app/coffee/RequestParser.coffee index d9a2e9bbe4..53268103a2 100644 --- a/services/clsi/app/coffee/RequestParser.coffee +++ b/services/clsi/app/coffee/RequestParser.coffee @@ -76,4 +76,4 @@ module.exports = RequestParser = _sanitizePath: (path) -> # See http://php.net/manual/en/function.escapeshellcmd.php - path.replace(/[\#\&\;\`\|\*\?\~\<\>\^\(\)\[\]\{\}\$\\\,\x0A\xFF]/g, "") \ No newline at end of file + path.replace(/[\#\&\;\`\|\*\?\~\<\>\^\(\)\[\]\{\}\$\\\x0A\xFF\x00]/g, "") From 4002849e2026bd0452fadea933185ee7caab2032 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Tue, 24 Feb 2015 14:40:05 +0000 Subject: [PATCH 064/754] save output files in a .cache directory --- .../clsi/app/coffee/CompileManager.coffee | 4 +- .../clsi/app/coffee/OutputCacheManager.coffee | 49 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 services/clsi/app/coffee/OutputCacheManager.coffee diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 46e2c0f182..dbdb39fc50 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -1,6 +1,7 @@ ResourceWriter = require "./ResourceWriter" LatexRunner = require "./LatexRunner" OutputFileFinder = require "./OutputFileFinder" +OutputCacheManager = require "./OutputCacheManager" Settings = require("settings-sharelatex") Path = require "path" logger = require "logger-sharelatex" @@ -32,7 +33,8 @@ module.exports = CompileManager = OutputFileFinder.findOutputFiles request.resources, compileDir, (error, outputFiles) -> return callback(error) if error? - callback null, outputFiles + OutputCacheManager.saveOutputFiles outputFiles, compileDir, (error, newOutputFiles) -> + callback null, newOutputFiles clearProject: (project_id, _callback = (error) ->) -> callback = (error) -> diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee new file mode 100644 index 0000000000..0f96b11e9d --- /dev/null +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -0,0 +1,49 @@ +async = require "async" +fs = require "fs" +fse = require "fs-extra" +Path = require "path" +logger = require "logger-sharelatex" +_ = require "underscore" + +module.exports = OutputCacheManager = + CACHE_DIR: '.cache/clsi' + + saveOutputFiles: (outputFiles, target, callback) -> + # make a target/build_id directory and + # copy all the output files into it + buildId = 'build-' + Date.now() + relDir = OutputCacheManager.CACHE_DIR + '/' + buildId + newDir = target + '/' + relDir + OutputCacheManager.expireOutputFiles target + fse.ensureDir newDir, (err) -> + if err? + callback(err, outputFiles) + else + async.mapSeries outputFiles, (file, cb) -> + newFile = _.clone(file) + newFile.path = relDir + '/' + file.path + src = target + '/' + file.path + dst = target + '/' + newFile.path + #console.log 'src', src, 'dst', dst + fs.stat src, (err, stats) -> + if err? + cb(err) + else if stats.isFile() + #console.log 'isFile: copying' + fse.copy src, dst, (err) -> + cb(err, newFile) + else + # other filetype - shouldn't happen + cb(new Error("output file is not a file"), file) + , (err, results) -> + if err? + callback err, outputFiles + else + callback(err, results) + + expireOutputFiles: (target, callback) -> + # look in target for build dirs and delete if > N or age of mod time > T + cacheDir = target + '/' + OutputCacheManager.CACHE_DIR + fs.readdir cacheDir, (err, results) -> + console.log 'CACHEDIR', results + callback(err) if callback? From 5c1d61c955715e91277568822a93c3f00a727a61 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Tue, 24 Feb 2015 14:40:22 +0000 Subject: [PATCH 065/754] skip the cache directory when finding output files --- services/clsi/app/coffee/OutputFileFinder.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/OutputFileFinder.coffee b/services/clsi/app/coffee/OutputFileFinder.coffee index fbfd38ed55..26f07cb735 100644 --- a/services/clsi/app/coffee/OutputFileFinder.coffee +++ b/services/clsi/app/coffee/OutputFileFinder.coffee @@ -37,7 +37,7 @@ module.exports = OutputFileFinder = _callback(error, fileList) _callback = () -> - args = [directory, "-type", "f"] + args = [directory, "-name", ".cache", "-prune", "-o", "-type", "f", "-print"] logger.log args: args, "running find command" proc = spawn("find", args) From 49e1ce552d43a782c290c7bb344af98e96844577 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Tue, 24 Feb 2015 15:48:34 +0000 Subject: [PATCH 066/754] add an optimisation pass for the cached output files --- .../clsi/app/coffee/OutputCacheManager.coffee | 7 ++++- .../app/coffee/OutputFileOptimiser.coffee | 30 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 services/clsi/app/coffee/OutputFileOptimiser.coffee diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee index 0f96b11e9d..479637d848 100644 --- a/services/clsi/app/coffee/OutputCacheManager.coffee +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -5,12 +5,16 @@ Path = require "path" logger = require "logger-sharelatex" _ = require "underscore" +OutputFileOptimiser = require "./OutputFileOptimiser" + module.exports = OutputCacheManager = CACHE_DIR: '.cache/clsi' saveOutputFiles: (outputFiles, target, callback) -> # make a target/build_id directory and # copy all the output files into it + # + # TODO: use Path module buildId = 'build-' + Date.now() relDir = OutputCacheManager.CACHE_DIR + '/' + buildId newDir = target + '/' + relDir @@ -31,7 +35,8 @@ module.exports = OutputCacheManager = else if stats.isFile() #console.log 'isFile: copying' fse.copy src, dst, (err) -> - cb(err, newFile) + OutputFileOptimiser.optimiseFile src, dst, (err, result) -> + cb(err, newFile) else # other filetype - shouldn't happen cb(new Error("output file is not a file"), file) diff --git a/services/clsi/app/coffee/OutputFileOptimiser.coffee b/services/clsi/app/coffee/OutputFileOptimiser.coffee new file mode 100644 index 0000000000..a00dc2053d --- /dev/null +++ b/services/clsi/app/coffee/OutputFileOptimiser.coffee @@ -0,0 +1,30 @@ +fs = require "fs" +Path = require "path" +spawn = require("child_process").spawn +logger = require "logger-sharelatex" + +module.exports = OutputFileOptimiser = + + optimiseFile: (src, dst, callback = (error) ->) -> + if src.match(/\.pdf$/) + OutputFileOptimiser.optimisePDF src, dst, callback + else + callback (null) + + optimisePDF: (src, dst, callback = (error) ->) -> + tmpOutput = dst + '.opt' + args = ["--linearize", src, tmpOutput] + logger.log args: args, "running qpdf command" + + proc = spawn("qpdf", args) + stdout = "" + proc.stdout.on "data", (chunk) -> + stdout += chunk.toString() + proc.on "error", callback + proc.on "close", (code) -> + if code != 0 + logger.warn {directory, code}, "qpdf returned error" + return callback null + fs.rename tmpOutput, dst, (err) -> + # could log an error here + callback null From 9991c38dfc044abed7d93df134912b62893a3ad6 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Tue, 24 Feb 2015 16:09:55 +0000 Subject: [PATCH 067/754] added package dependencies for caching --- services/clsi/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index 1afcfeab8f..58e6745ed1 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -21,7 +21,9 @@ "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v1.0.0", "sqlite3": "~2.2.0", "express": "^4.2.0", - "body-parser": "^1.2.0" + "body-parser": "^1.2.0", + "fs-extra": "^0.16.3", + "underscore": "^1.8.2" }, "devDependencies": { "mocha": "1.10.0", From 5a5ef8baede51e5a40a9042f7292cda0e71b7efa Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Wed, 25 Feb 2015 17:05:19 +0000 Subject: [PATCH 068/754] accept build id parameter when serving static files --- services/clsi/app.coffee | 24 +++++++++++++++++-- .../clsi/app/coffee/CompileController.coffee | 1 + .../clsi/app/coffee/CompileManager.coffee | 2 +- .../clsi/app/coffee/OutputCacheManager.coffee | 7 +++--- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 44d31575cc..dd2bae56bc 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -36,7 +36,24 @@ app.delete "/project/:project_id", CompileController.clearCache app.get "/project/:project_id/sync/code", CompileController.syncFromCode app.get "/project/:project_id/sync/pdf", CompileController.syncFromPdf -staticServer = express.static Settings.path.compilesDir, setHeaders: (res, path, stat) -> +url = require "url" + +staticForbidSymLinks = (root, options) -> + expressStatic = express.static root, options + basePath = Path.resolve(root) + return (req, res, next) -> + path = url.parse(req.url).pathname + requestedFsPath = Path.normalize("#{basePath}/#{path}") + fs.realpath requestedFsPath, (err, realFsPath)-> + if err? + return res.send(500) + else if requestedFsPath != realFsPath + logger.warn requestedFsPath:requestedFsPath, realFsPath:realFsPath, path: req.params[0], project_id: req.params.project_id, "trying to access a different file (symlink), aborting" + return res.send(404) + else + expressStatic(req, res, next) + +staticServer = staticForbidSymLinks Settings.path.compilesDir, setHeaders: (res, path, stat) -> if Path.basename(path) == "output.pdf" res.set("Content-Type", "application/pdf") # Calculate an etag in the same way as nginx @@ -51,7 +68,10 @@ staticServer = express.static Settings.path.compilesDir, setHeaders: (res, path, res.set("Content-Type", "text/plain") app.get "/project/:project_id/output/*", require("./app/js/SymlinkCheckerMiddlewear"), (req, res, next) -> - req.url = "/#{req.params.project_id}/#{req.params[0]}" + if req.query?.build? && req.query.build.match(/^[0-9]+$/) + req.url = "/#{req.params.project_id}/.cache/clsi/#{req.query.build}/#{req.params[0]}" + else + req.url = "/#{req.params.project_id}/#{req.params[0]}" staticServer(req, res, next) app.get "/status", (req, res, next) -> diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index c73820206a..29373c36db 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -35,6 +35,7 @@ module.exports = CompileController = outputFiles: outputFiles.map (file) -> url: "#{Settings.apis.clsi.url}/project/#{request.project_id}/output/#{file.path}" type: file.type + build: file.build } clearCache: (req, res, next = (error) ->) -> diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index dbdb39fc50..ac0c241149 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -33,7 +33,7 @@ module.exports = CompileManager = OutputFileFinder.findOutputFiles request.resources, compileDir, (error, outputFiles) -> return callback(error) if error? - OutputCacheManager.saveOutputFiles outputFiles, compileDir, (error, newOutputFiles) -> + OutputCacheManager.saveOutputFiles outputFiles, compileDir, (error, newOutputFiles) -> callback null, newOutputFiles clearProject: (project_id, _callback = (error) ->) -> diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee index 479637d848..c34a6628ef 100644 --- a/services/clsi/app/coffee/OutputCacheManager.coffee +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -15,7 +15,7 @@ module.exports = OutputCacheManager = # copy all the output files into it # # TODO: use Path module - buildId = 'build-' + Date.now() + buildId = Date.now() relDir = OutputCacheManager.CACHE_DIR + '/' + buildId newDir = target + '/' + relDir OutputCacheManager.expireOutputFiles target @@ -25,9 +25,8 @@ module.exports = OutputCacheManager = else async.mapSeries outputFiles, (file, cb) -> newFile = _.clone(file) - newFile.path = relDir + '/' + file.path src = target + '/' + file.path - dst = target + '/' + newFile.path + dst = target + '/' + relDir + '/' + file.path #console.log 'src', src, 'dst', dst fs.stat src, (err, stats) -> if err? @@ -36,6 +35,8 @@ module.exports = OutputCacheManager = #console.log 'isFile: copying' fse.copy src, dst, (err) -> OutputFileOptimiser.optimiseFile src, dst, (err, result) -> + console.log 'setting buildId on', newFile, 'to', buildId + newFile.build = buildId cb(err, newFile) else # other filetype - shouldn't happen From e78da9d0fd0ea1b908b2243522783119257e8651 Mon Sep 17 00:00:00 2001 From: James Allen Date: Thu, 26 Feb 2015 11:20:56 +0000 Subject: [PATCH 069/754] Release version 0.1.3 --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index 1afcfeab8f..70283c6630 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -1,7 +1,7 @@ { "name": "node-clsi", "description": "A Node.js implementation of the CLSI LaTeX web-API", - "version": "0.1.2", + "version": "0.1.3", "repository": { "type": "git", "url": "https://github.com/sharelatex/clsi-sharelatex.git" From 77b4247f86a0c955f447984112e246ff10203199 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Thu, 26 Feb 2015 15:30:57 +0000 Subject: [PATCH 070/754] fix tests to allow for build parameter --- .../test/unit/coffee/CompileControllerTests.coffee | 3 +++ .../test/unit/coffee/CompileManagerTests.coffee | 14 +++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/services/clsi/test/unit/coffee/CompileControllerTests.coffee b/services/clsi/test/unit/coffee/CompileControllerTests.coffee index d2babf7c2b..bc2d988132 100644 --- a/services/clsi/test/unit/coffee/CompileControllerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileControllerTests.coffee @@ -36,9 +36,11 @@ describe "CompileController", -> @output_files = [{ path: "output.pdf" type: "pdf" + build: 1234 }, { path: "output.log" type: "log" + build: 1234 }] @RequestParser.parse = sinon.stub().callsArgWith(1, null, @request) @ProjectPersistenceManager.markProjectAsJustAccessed = sinon.stub().callsArg(1) @@ -73,6 +75,7 @@ describe "CompileController", -> outputFiles: @output_files.map (file) => url: "#{@Settings.apis.clsi.url}/project/#{@project_id}/output/#{file.path}" type: file.type + build: file.build ) .should.equal true diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 5d110eb17e..8923dea02f 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -12,6 +12,7 @@ describe "CompileManager", -> "./LatexRunner": @LatexRunner = {} "./ResourceWriter": @ResourceWriter = {} "./OutputFileFinder": @OutputFileFinder = {} + "./OutputCacheManager": @OutputCacheManager = {} "settings-sharelatex": @Settings = { path: compilesDir: "/compiles/dir" } "logger-sharelatex": @logger = { log: sinon.stub() } "child_process": @child_process = {} @@ -26,6 +27,15 @@ describe "CompileManager", -> path: "output.pdf" type: "pdf" }] + @build_files = [{ + path: "output.log" + type: "log" + build: 1234 + }, { + path: "output.pdf" + type: "pdf" + build: 1234 + }] @request = resources: @resources = "mock-resources" rootResourcePath: @rootResourcePath = "main.tex" @@ -37,6 +47,7 @@ describe "CompileManager", -> @ResourceWriter.syncResourcesToDisk = sinon.stub().callsArg(3) @LatexRunner.runLatex = sinon.stub().callsArg(2) @OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, @output_files) + @OutputCacheManager.saveOutputFiles = sinon.stub().callsArgWith(2, null, @build_files) @CompileManager.doCompile @request, @callback it "should write the resources to disk", -> @@ -60,7 +71,8 @@ describe "CompileManager", -> .should.equal true it "should return the output files", -> - @callback.calledWith(null, @output_files).should.equal true + console.log 'output_files', @build_files + @callback.calledWith(null, @build_files).should.equal true describe "clearProject", -> describe "succesfully", -> From a877dd36e7c39603a74627050020761070146250 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Thu, 26 Feb 2015 15:32:01 +0000 Subject: [PATCH 071/754] remove debugging code --- services/clsi/app/coffee/OutputCacheManager.coffee | 4 ---- 1 file changed, 4 deletions(-) diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee index c34a6628ef..c869bfdb51 100644 --- a/services/clsi/app/coffee/OutputCacheManager.coffee +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -27,15 +27,12 @@ module.exports = OutputCacheManager = newFile = _.clone(file) src = target + '/' + file.path dst = target + '/' + relDir + '/' + file.path - #console.log 'src', src, 'dst', dst fs.stat src, (err, stats) -> if err? cb(err) else if stats.isFile() - #console.log 'isFile: copying' fse.copy src, dst, (err) -> OutputFileOptimiser.optimiseFile src, dst, (err, result) -> - console.log 'setting buildId on', newFile, 'to', buildId newFile.build = buildId cb(err, newFile) else @@ -51,5 +48,4 @@ module.exports = OutputCacheManager = # look in target for build dirs and delete if > N or age of mod time > T cacheDir = target + '/' + OutputCacheManager.CACHE_DIR fs.readdir cacheDir, (err, results) -> - console.log 'CACHEDIR', results callback(err) if callback? From 5fdd1d4f474bf18c50ec671eabd3aa87db0e2056 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Fri, 27 Feb 2015 13:15:28 +0000 Subject: [PATCH 072/754] cleanup and logging --- .../clsi/app/coffee/OutputCacheManager.coffee | 115 +++++++++++++----- .../app/coffee/OutputFileOptimiser.coffee | 11 +- 2 files changed, 93 insertions(+), 33 deletions(-) diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee index c869bfdb51..f77227a185 100644 --- a/services/clsi/app/coffee/OutputCacheManager.coffee +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -8,44 +8,101 @@ _ = require "underscore" OutputFileOptimiser = require "./OutputFileOptimiser" module.exports = OutputCacheManager = - CACHE_DIR: '.cache/clsi' + CACHE_SUBDIR: '.cache/clsi' + BUILD_REGEX: /^[0-9a-f]+$/ # build id is Date.now() converted to hex + CACHE_LIMIT: 32 # maximum of 32 cache directories + CACHE_AGE: 60*60*1000 # up to one hour old - saveOutputFiles: (outputFiles, target, callback) -> - # make a target/build_id directory and + path: (buildId) -> + # used by static server, given build id return '.cache/clsi/buildId' + return Path.join(OutputCacheManager.CACHE_SUBDIR, buildId) + + saveOutputFiles: (outputFiles, compileDir, callback = (error) ->) -> + # make a compileDir/CACHE_SUBDIR/build_id directory and # copy all the output files into it - # - # TODO: use Path module - buildId = Date.now() - relDir = OutputCacheManager.CACHE_DIR + '/' + buildId - newDir = target + '/' + relDir - OutputCacheManager.expireOutputFiles target - fse.ensureDir newDir, (err) -> + cacheRoot = Path.join(compileDir, OutputCacheManager.CACHE_SUBDIR) + # Put the files into a new cache subdirectory + buildId = Date.now().toString(16) + cacheDir = Path.join(compileDir, OutputCacheManager.CACHE_SUBDIR, buildId) + # let file expiry run in the background + OutputCacheManager.expireOutputFiles cacheRoot, {keep: buildId} + + checkFile = (src, callback) -> + # check if we have a valid file to copy into the cache + fs.stat src, (err, stats) -> + if err? + # some problem reading the file + logger.error err: err, file: src, "stat error for file in cache" + callback(err) + else if not stats.isFile() + # other filetype - reject it + logger.error err: err, src: src, dst: dst, stat: stats, "nonfile output - refusing to copy to cache" + callback(new Error("output file is not a file"), file) + else + # it's a plain file, ok to copy + callback(null) + + copyFile = (src, dst, callback) -> + # copy output file into the cache + fse.copy src, dst, (err) -> + if err? + logger.error err: err, src: src, dst: dst, "copy error for file in cache" + callback(err) + else + # call the optimiser for the file too + OutputFileOptimiser.optimiseFile src, dst, callback + + # make the new cache directory + fse.ensureDir cacheDir, (err) -> if err? + logger.error err: err, directory: cacheDir, "error creating cache directory" callback(err, outputFiles) else + # copy all the output files into the new cache directory async.mapSeries outputFiles, (file, cb) -> newFile = _.clone(file) - src = target + '/' + file.path - dst = target + '/' + relDir + '/' + file.path - fs.stat src, (err, stats) -> - if err? - cb(err) - else if stats.isFile() - fse.copy src, dst, (err) -> - OutputFileOptimiser.optimiseFile src, dst, (err, result) -> - newFile.build = buildId - cb(err, newFile) - else - # other filetype - shouldn't happen - cb(new Error("output file is not a file"), file) + [src, dst] = [Path.join(compileDir, file.path), Path.join(cacheDir, file.path)] + checkFile src, (err) -> + copyFile src, dst, (err) -> + if not err? + newFile.build = buildId # attach a build id if we cached the file + cb(err, newFile) , (err, results) -> if err? - callback err, outputFiles + # pass back the original files if we encountered *any* error + callback(err, outputFiles) else + # pass back the list of new files in the cache callback(err, results) - expireOutputFiles: (target, callback) -> - # look in target for build dirs and delete if > N or age of mod time > T - cacheDir = target + '/' + OutputCacheManager.CACHE_DIR - fs.readdir cacheDir, (err, results) -> - callback(err) if callback? + expireOutputFiles: (cacheRoot, options, callback = (error) ->) -> + # look in compileDir for build dirs and delete if > N or age of mod time > T + fs.readdir cacheRoot, (err, results) -> + if err? + logger.error err: err, project_id: cacheRoot, "error clearing cache" + return callback(err) + + dirs = results.sort().reverse() + currentTime = Date.now() + + isExpired = (dir, index) -> + return false if options?.keep == dir + # remove any directories over the hard limit + return true if index > OutputCacheManager.CACHE_LIMIT + # we can get the build time from the directory name + dirTime = parseInt(dir, 16) + age = currentTime - dirTime + return age > OutputCacheManager.CACHE_AGE + + toRemove = _.filter(dirs, isExpired) + + removeDir = (dir, cb) -> + fse.remove Path.join(cacheRoot, dir), (err, result) -> + logger.log cache: cacheRoot, dir: dir, "removed expired cache dir" + if err? + logger.error err: err, dir: dir, "cache remove error" + cb(err, result) + + async.eachSeries toRemove, (dir, cb) -> + removeDir dir, cb + , callback diff --git a/services/clsi/app/coffee/OutputFileOptimiser.coffee b/services/clsi/app/coffee/OutputFileOptimiser.coffee index a00dc2053d..d1e45d4f8c 100644 --- a/services/clsi/app/coffee/OutputFileOptimiser.coffee +++ b/services/clsi/app/coffee/OutputFileOptimiser.coffee @@ -6,6 +6,8 @@ logger = require "logger-sharelatex" module.exports = OutputFileOptimiser = optimiseFile: (src, dst, callback = (error) ->) -> + # check output file (src) and see if we can optimise it, storing + # the result in the build directory (dst) if src.match(/\.pdf$/) OutputFileOptimiser.optimisePDF src, dst, callback else @@ -19,12 +21,13 @@ module.exports = OutputFileOptimiser = proc = spawn("qpdf", args) stdout = "" proc.stdout.on "data", (chunk) -> - stdout += chunk.toString() - proc.on "error", callback + stdout += chunk.toString() + proc.on "error", callback proc.on "close", (code) -> if code != 0 logger.warn {directory, code}, "qpdf returned error" return callback null fs.rename tmpOutput, dst, (err) -> - # could log an error here - callback null + if err? + logger.warn {tmpOutput, dst}, "failed to rename output of qpdf command" + callback err From a6fb82513f55fded7d0ca10792af19f4a7175579 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Fri, 27 Feb 2015 13:16:01 +0000 Subject: [PATCH 073/754] use OutputCacheManager to construct static path to files --- services/clsi/app.coffee | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index dd2bae56bc..ee2e8387ec 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -12,6 +12,7 @@ Metrics.initialize("clsi") Metrics.open_sockets.monitor(logger) ProjectPersistenceManager = require "./app/js/ProjectPersistenceManager" +OutputCacheManager = require "./app/js/OutputCacheManager" require("./app/js/db").sync() @@ -68,8 +69,8 @@ staticServer = staticForbidSymLinks Settings.path.compilesDir, setHeaders: (res, res.set("Content-Type", "text/plain") app.get "/project/:project_id/output/*", require("./app/js/SymlinkCheckerMiddlewear"), (req, res, next) -> - if req.query?.build? && req.query.build.match(/^[0-9]+$/) - req.url = "/#{req.params.project_id}/.cache/clsi/#{req.query.build}/#{req.params[0]}" + if req.query?.build? && req.query.build.match(OutputCacheManager.BUILD_REGEX) + req.url = "/#{req.params.project_id}/" + OutputCacheManager.path(req.query.build) + "/#{req.params[0]}" else req.url = "/#{req.params.project_id}/#{req.params[0]}" staticServer(req, res, next) From e024fec82d3125ce758bdc13382a10fdaff38678 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Fri, 27 Feb 2015 13:57:57 +0000 Subject: [PATCH 074/754] provide a static server which forbids symlinks prevents mismatch between rootdir of server and rootdir of symlink checking middleware --- services/clsi/app.coffee | 27 ++++++------------- .../clsi/app/coffee/OutputCacheManager.coffee | 8 ++++-- .../coffee/StaticServerForbidSymlinks.coffee | 24 +++++++++++++++++ .../coffee/SymlinkCheckerMiddlewear.coffee | 17 ------------ 4 files changed, 38 insertions(+), 38 deletions(-) create mode 100644 services/clsi/app/coffee/StaticServerForbidSymlinks.coffee delete mode 100644 services/clsi/app/coffee/SymlinkCheckerMiddlewear.coffee diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index ee2e8387ec..9ac1d1dc18 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -37,24 +37,12 @@ app.delete "/project/:project_id", CompileController.clearCache app.get "/project/:project_id/sync/code", CompileController.syncFromCode app.get "/project/:project_id/sync/pdf", CompileController.syncFromPdf -url = require "url" +ForbidSymlinks = require "./app/js/StaticServerForbidSymlinks" -staticForbidSymLinks = (root, options) -> - expressStatic = express.static root, options - basePath = Path.resolve(root) - return (req, res, next) -> - path = url.parse(req.url).pathname - requestedFsPath = Path.normalize("#{basePath}/#{path}") - fs.realpath requestedFsPath, (err, realFsPath)-> - if err? - return res.send(500) - else if requestedFsPath != realFsPath - logger.warn requestedFsPath:requestedFsPath, realFsPath:realFsPath, path: req.params[0], project_id: req.params.project_id, "trying to access a different file (symlink), aborting" - return res.send(404) - else - expressStatic(req, res, next) - -staticServer = staticForbidSymLinks Settings.path.compilesDir, setHeaders: (res, path, stat) -> +# create a static server which does not allow access to any symlinks +# avoids possible mismatch of root directory between middleware check +# and serving the files +staticServer = ForbidSymlinks express.static, Settings.path.compilesDir, setHeaders: (res, path, stat) -> if Path.basename(path) == "output.pdf" res.set("Content-Type", "application/pdf") # Calculate an etag in the same way as nginx @@ -68,9 +56,10 @@ staticServer = staticForbidSymLinks Settings.path.compilesDir, setHeaders: (res, # that could be used in same-origin/XSS attacks. res.set("Content-Type", "text/plain") -app.get "/project/:project_id/output/*", require("./app/js/SymlinkCheckerMiddlewear"), (req, res, next) -> +app.get "/project/:project_id/output/*", (req, res, next) -> if req.query?.build? && req.query.build.match(OutputCacheManager.BUILD_REGEX) - req.url = "/#{req.params.project_id}/" + OutputCacheManager.path(req.query.build) + "/#{req.params[0]}" + # for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) + req.url = "/#{req.params.project_id}/" + OutputCacheManager.path(req.query.build, "/#{req.params[0]}") else req.url = "/#{req.params.project_id}/#{req.params[0]}" staticServer(req, res, next) diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee index f77227a185..50c5837002 100644 --- a/services/clsi/app/coffee/OutputCacheManager.coffee +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -13,9 +13,13 @@ module.exports = OutputCacheManager = CACHE_LIMIT: 32 # maximum of 32 cache directories CACHE_AGE: 60*60*1000 # up to one hour old - path: (buildId) -> + path: (buildId, file) -> # used by static server, given build id return '.cache/clsi/buildId' - return Path.join(OutputCacheManager.CACHE_SUBDIR, buildId) + if buildId.match OutputCacheManager.BUILD_REGEX + return Path.join(OutputCacheManager.CACHE_SUBDIR, buildId, file) + else + # for invalid build id, return top level + return file saveOutputFiles: (outputFiles, compileDir, callback = (error) ->) -> # make a compileDir/CACHE_SUBDIR/build_id directory and diff --git a/services/clsi/app/coffee/StaticServerForbidSymlinks.coffee b/services/clsi/app/coffee/StaticServerForbidSymlinks.coffee new file mode 100644 index 0000000000..2f48190835 --- /dev/null +++ b/services/clsi/app/coffee/StaticServerForbidSymlinks.coffee @@ -0,0 +1,24 @@ +Path = require("path") +fs = require("fs") +Settings = require("settings-sharelatex") +logger = require("logger-sharelatex") +url = require "url" + +module.exports = ForbidSymlinks = (staticFn, root, options) -> + expressStatic = staticFn root, options + basePath = Path.resolve(root) + return (req, res, next) -> + path = url.parse(req.url)?.pathname + requestedFsPath = Path.normalize("#{basePath}/#{path}") + fs.realpath requestedFsPath, (err, realFsPath)-> + if err? + logger.warn err:err, requestedFsPath:requestedFsPath, realFsPath:realFsPath, path: req.params[0], project_id: req.params.project_id, "error checking file access" + if err.code == 'ENOENT' + return res.sendStatus(404) + else + return res.sendStatus(500) + else if requestedFsPath != realFsPath + logger.warn requestedFsPath:requestedFsPath, realFsPath:realFsPath, path: req.params[0], project_id: req.params.project_id, "trying to access a different file (symlink), aborting" + return res.sendStatus(404) + else + expressStatic(req, res, next) diff --git a/services/clsi/app/coffee/SymlinkCheckerMiddlewear.coffee b/services/clsi/app/coffee/SymlinkCheckerMiddlewear.coffee deleted file mode 100644 index b7baf1fa77..0000000000 --- a/services/clsi/app/coffee/SymlinkCheckerMiddlewear.coffee +++ /dev/null @@ -1,17 +0,0 @@ -Path = require("path") -fs = require("fs") -Settings = require("settings-sharelatex") -logger = require("logger-sharelatex") - - -module.exports = (req, res, next)-> - basePath = Path.resolve("#{Settings.path.compilesDir}/#{req.params.project_id}") - requestedFsPath = Path.normalize("#{basePath}/#{req.params[0]}") - fs.realpath requestedFsPath, (err, realFsPath)-> - if err? - return res.send(500) - else if requestedFsPath != realFsPath - logger.warn requestedFsPath:requestedFsPath, realFsPath:realFsPath, path: req.params[0], project_id: req.params.project_id, "trying to access a different file (symlink), aborting" - return res.send(404) - else - return next() From 140090da47495df8327f751d50e397a263822b63 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Fri, 27 Feb 2015 15:38:57 +0000 Subject: [PATCH 075/754] move convert tests from middleware to restricted static server --- .../StaticServerForbidSymlinksTests.coffee | 82 +++++++++++++++++++ .../SymlinkCheckerMiddlewearTests.coffee | 60 -------------- 2 files changed, 82 insertions(+), 60 deletions(-) create mode 100644 services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.coffee delete mode 100644 services/clsi/test/unit/coffee/SymlinkCheckerMiddlewearTests.coffee diff --git a/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.coffee b/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.coffee new file mode 100644 index 0000000000..e17bacefca --- /dev/null +++ b/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.coffee @@ -0,0 +1,82 @@ +should = require('chai').should() +SandboxedModule = require('sandboxed-module') +assert = require('assert') +path = require('path') +sinon = require('sinon') +modulePath = path.join __dirname, "../../../app/js/StaticServerForbidSymlinks" +expect = require("chai").expect + +describe "StaticServerForbidSymlinks", -> + + beforeEach -> + + @settings = + path: + compilesDir: "/compiles/here" + + @fs = {} + @ForbidSymlinks = SandboxedModule.require modulePath, requires: + "settings-sharelatex":@settings + "logger-sharelatex": + log:-> + warn:-> + "fs":@fs + + @dummyStatic = (rootDir, options) -> + return (req, res, next) -> + # console.log "dummyStatic serving file", rootDir, "called with", req.url + # serve it + next() + + @StaticServerForbidSymlinks = @ForbidSymlinks @dummyStatic, @settings.path.compilesDir + @req = + params: + project_id:"12345" + + @res = {} + @req.url = "/12345/output.pdf" + + + describe "sending a normal file through", -> + beforeEach -> + @fs.realpath = sinon.stub().callsArgWith(1, null, "#{@settings.path.compilesDir}/#{@req.params.project_id}/output.pdf") + + it "should call next", (done)-> + @res.sendStatus = (resCode)-> + resCode.should.equal 200 + done() + @StaticServerForbidSymlinks @req, @res, done + + + describe "with a missing file", -> + beforeEach -> + @fs.realpath = sinon.stub().callsArgWith(1, {code: 'ENOENT'}, "#{@settings.path.compilesDir}/#{@req.params.project_id}/unknown.pdf") + + it "should send a 404", (done)-> + @res.sendStatus = (resCode)-> + resCode.should.equal 404 + done() + @StaticServerForbidSymlinks @req, @res + + + describe "with a symlink file", -> + beforeEach -> + @fs.realpath = sinon.stub().callsArgWith(1, null, "/etc/#{@req.params.project_id}/output.pdf") + + it "should send a 404", (done)-> + @res.sendStatus = (resCode)-> + resCode.should.equal 404 + done() + @StaticServerForbidSymlinks @req, @res + + describe "with an error from fs.realpath", -> + + beforeEach -> + @fs.realpath = sinon.stub().callsArgWith(1, "error") + + it "should send a 500", (done)-> + @res.sendStatus = (resCode)-> + resCode.should.equal 500 + done() + @StaticServerForbidSymlinks @req, @res + diff --git a/services/clsi/test/unit/coffee/SymlinkCheckerMiddlewearTests.coffee b/services/clsi/test/unit/coffee/SymlinkCheckerMiddlewearTests.coffee deleted file mode 100644 index 50bd4271b5..0000000000 --- a/services/clsi/test/unit/coffee/SymlinkCheckerMiddlewearTests.coffee +++ /dev/null @@ -1,60 +0,0 @@ -should = require('chai').should() -SandboxedModule = require('sandboxed-module') -assert = require('assert') -path = require('path') -sinon = require('sinon') -modulePath = path.join __dirname, "../../../app/js/SymlinkCheckerMiddlewear" -expect = require("chai").expect - -describe "SymlinkCheckerMiddlewear", -> - - beforeEach -> - - @settings = - path: - compilesDir: "/compiles/here" - - @fs = {} - @SymlinkCheckerMiddlewear = SandboxedModule.require modulePath, requires: - "settings-sharelatex":@settings - "logger-sharelatex": - log:-> - warn:-> - "fs":@fs - @req = - params: - project_id:"12345" - - @res = {} - @req.params[0]= "output.pdf" - - - describe "sending a normal file through", -> - beforeEach -> - @fs.realpath = sinon.stub().callsArgWith(1, null, "#{@settings.path.compilesDir}/#{@req.params.project_id}/output.pdf") - - it "should call next", (done)-> - @SymlinkCheckerMiddlewear @req, @res, done - - - describe "with a symlink file", -> - beforeEach -> - @fs.realpath = sinon.stub().callsArgWith(1, null, "/etc/#{@req.params.project_id}/output.pdf") - - it "should send a 404", (done)-> - @res.send = (resCode)-> - resCode.should.equal 404 - done() - @SymlinkCheckerMiddlewear @req, @res - - describe "with an error from fs.realpath", -> - - beforeEach -> - @fs.realpath = sinon.stub().callsArgWith(1, "error") - - it "should send a 500", (done)-> - @res.send = (resCode)-> - resCode.should.equal 500 - done() - @SymlinkCheckerMiddlewear @req, @res - From 7f38c5e5a351a9ef76e9cf20aad9d42a7e550958 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Fri, 27 Feb 2015 16:07:02 +0000 Subject: [PATCH 076/754] fix double callback for proc.on 'error' and proc.on 'close' --- services/clsi/app/coffee/OutputFileOptimiser.coffee | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/services/clsi/app/coffee/OutputFileOptimiser.coffee b/services/clsi/app/coffee/OutputFileOptimiser.coffee index d1e45d4f8c..d0c091d37b 100644 --- a/services/clsi/app/coffee/OutputFileOptimiser.coffee +++ b/services/clsi/app/coffee/OutputFileOptimiser.coffee @@ -2,6 +2,7 @@ fs = require "fs" Path = require "path" spawn = require("child_process").spawn logger = require "logger-sharelatex" +_ = require "underscore" module.exports = OutputFileOptimiser = @@ -22,12 +23,15 @@ module.exports = OutputFileOptimiser = stdout = "" proc.stdout.on "data", (chunk) -> stdout += chunk.toString() - proc.on "error", callback + callback = _.once(callback) # avoid double call back for error and close event + proc.on "error", (err) -> + logger.warn {err, args}, "qpdf failed" + callback(null) # ignore the error proc.on "close", (code) -> if code != 0 - logger.warn {directory, code}, "qpdf returned error" - return callback null + logger.warn {code, args}, "qpdf returned error" + return callback(null) # ignore the error fs.rename tmpOutput, dst, (err) -> if err? logger.warn {tmpOutput, dst}, "failed to rename output of qpdf command" - callback err + callback(null) # ignore the error From c26de9554db0052e74c493d1d37d14ffd114a798 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Mon, 2 Mar 2015 09:58:20 +0000 Subject: [PATCH 077/754] skip cache directory error when empty --- services/clsi/app/coffee/OutputCacheManager.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee index 50c5837002..af4ce61203 100644 --- a/services/clsi/app/coffee/OutputCacheManager.coffee +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -83,6 +83,7 @@ module.exports = OutputCacheManager = # look in compileDir for build dirs and delete if > N or age of mod time > T fs.readdir cacheRoot, (err, results) -> if err? + return callback(null) if err.code == 'ENOENT' # cache directory is empty logger.error err: err, project_id: cacheRoot, "error clearing cache" return callback(err) From 3a3eda0ccfc7d9db5d384a788a28871d8f41edd4 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Mon, 2 Mar 2015 11:31:48 +0000 Subject: [PATCH 078/754] reduce cache limit for pdfs --- services/clsi/app/coffee/OutputCacheManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee index af4ce61203..66abc4eaa8 100644 --- a/services/clsi/app/coffee/OutputCacheManager.coffee +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -10,7 +10,7 @@ OutputFileOptimiser = require "./OutputFileOptimiser" module.exports = OutputCacheManager = CACHE_SUBDIR: '.cache/clsi' BUILD_REGEX: /^[0-9a-f]+$/ # build id is Date.now() converted to hex - CACHE_LIMIT: 32 # maximum of 32 cache directories + CACHE_LIMIT: 2 # maximum number of cache directories CACHE_AGE: 60*60*1000 # up to one hour old path: (buildId, file) -> From 9ee2706410ca1446e960bc574fd7c0c44ecb8252 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Mon, 16 Mar 2015 15:02:45 +0000 Subject: [PATCH 079/754] add v8 profiler on /profile?time=MS url --- services/clsi/app.coffee | 9 +++++++++ services/clsi/package.json | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 9ac1d1dc18..78fad79b41 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -86,6 +86,15 @@ app.get "/health_check", (req, res)-> res.contentType(resCacher?.setContentType) res.send resCacher?.code, resCacher?.body +profiler = require "v8-profiler" +app.get "/profile", (req, res) -> + time = parseInt(req.query.time || "1000") + profiler.startProfiling("test") + setTimeout () -> + profile = profiler.stopProfiling("test") + res.json(profile) + , time + app.use (error, req, res, next) -> logger.error err: error, "server error" res.send error?.statusCode || 500 diff --git a/services/clsi/package.json b/services/clsi/package.json index 678d97123a..97bd5ae9db 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -23,7 +23,8 @@ "express": "^4.2.0", "body-parser": "^1.2.0", "fs-extra": "^0.16.3", - "underscore": "^1.8.2" + "underscore": "^1.8.2", + "v8-profiler": "^5.2.4" }, "devDependencies": { "mocha": "1.10.0", From 59c25fc703d0c092c9c3f0dfc0875754a9d92566 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Mon, 16 Mar 2015 16:47:14 +0000 Subject: [PATCH 080/754] remove unnecessary call to async.series in OutputFileFinder callback was previously async but is now synchronous, so high stack usage. --- .../clsi/app/coffee/OutputFileFinder.coffee | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/services/clsi/app/coffee/OutputFileFinder.coffee b/services/clsi/app/coffee/OutputFileFinder.coffee index 26f07cb735..dcec5f5df6 100644 --- a/services/clsi/app/coffee/OutputFileFinder.coffee +++ b/services/clsi/app/coffee/OutputFileFinder.coffee @@ -17,20 +17,12 @@ module.exports = OutputFileFinder = jobs = [] outputFiles = [] for file in allFiles - do (file) -> - jobs.push (callback) -> - if incomingResources[file] - return callback() - else - outputFiles.push { - path: file - type: file.match(/\.([^\.]+)$/)?[1] - } - callback() - - async.series jobs, (error) -> - return callback(error) if error? - callback null, outputFiles + if !incomingResources[file] + outputFiles.push { + path: file + type: file.match(/\.([^\.]+)$/)?[1] + } + callback null, outputFiles _getAllFiles: (directory, _callback = (error, fileList) ->) -> callback = (error, fileList) -> From 79e71cdf92567ed31178bf3adfb282daa7e81c18 Mon Sep 17 00:00:00 2001 From: James Allen Date: Fri, 20 Mar 2015 15:25:02 +0000 Subject: [PATCH 081/754] Release version 0.1.4 --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index 97bd5ae9db..859a834f15 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -1,7 +1,7 @@ { "name": "node-clsi", "description": "A Node.js implementation of the CLSI LaTeX web-API", - "version": "0.1.3", + "version": "0.1.4", "repository": { "type": "git", "url": "https://github.com/sharelatex/clsi-sharelatex.git" From 134058ccc0b56f4c7a89365b865d4a6f095b2c7e Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Thu, 9 Apr 2015 14:40:02 +0100 Subject: [PATCH 082/754] add heapdump support for memory profiling --- services/clsi/app.coffee | 4 ++++ services/clsi/package.json | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 78fad79b41..464ff3900f 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -95,6 +95,10 @@ app.get "/profile", (req, res) -> res.json(profile) , time +app.get "/heapdump", (req, res)-> + require('heapdump').writeSnapshot '/tmp/' + Date.now() + '.clsi.heapsnapshot', (err, filename)-> + res.send filename + app.use (error, req, res, next) -> logger.error err: error, "server error" res.send error?.statusCode || 500 diff --git a/services/clsi/package.json b/services/clsi/package.json index 859a834f15..8bf9035673 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -24,7 +24,8 @@ "body-parser": "^1.2.0", "fs-extra": "^0.16.3", "underscore": "^1.8.2", - "v8-profiler": "^5.2.4" + "v8-profiler": "^5.2.4", + "heapdump": "^0.3.5" }, "devDependencies": { "mocha": "1.10.0", From 56b77c300c6ebd17dab3c7abdc53983ebc5b22df Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Wed, 29 Apr 2015 15:54:08 +0100 Subject: [PATCH 083/754] log errors when copying files from cache --- services/clsi/app/coffee/UrlCache.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/clsi/app/coffee/UrlCache.coffee b/services/clsi/app/coffee/UrlCache.coffee index 1836a4e73c..153e133e2e 100644 --- a/services/clsi/app/coffee/UrlCache.coffee +++ b/services/clsi/app/coffee/UrlCache.coffee @@ -62,6 +62,8 @@ module.exports = UrlCache = _copyFile: (from, to, _callback = (error) ->) -> callbackOnce = (error) -> + if error? + logger.error err: error, from:from, to:to, "error copying file from cache" _callback(error) _callback = () -> writeStream = fs.createWriteStream(to) From 72918fb41be43d3dc96a42dc6c1386dff1ccee87 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Wed, 29 Apr 2015 15:54:38 +0100 Subject: [PATCH 084/754] invalidate the cache if there is an error copying a file --- services/clsi/app/coffee/UrlCache.coffee | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/UrlCache.coffee b/services/clsi/app/coffee/UrlCache.coffee index 153e133e2e..941bc9defd 100644 --- a/services/clsi/app/coffee/UrlCache.coffee +++ b/services/clsi/app/coffee/UrlCache.coffee @@ -10,7 +10,12 @@ module.exports = UrlCache = downloadUrlToFile: (project_id, url, destPath, lastModified, callback = (error) ->) -> UrlCache._ensureUrlIsInCache project_id, url, lastModified, (error, pathToCachedUrl) => return callback(error) if error? - UrlCache._copyFile(pathToCachedUrl, destPath, callback) + UrlCache._copyFile pathToCachedUrl, destPath, (error) -> + if error? + UrlCache._clearUrlDetails project_id, url, () -> + callback(error) + else + callback(error) clearProject: (project_id, callback = (error) ->) -> UrlCache._findAllUrlsInProject project_id, (error, urls) -> From bb9944e6181750260251e56ca56b9d66a65d27e8 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Wed, 29 Apr 2015 15:55:58 +0100 Subject: [PATCH 085/754] log errors when downloading files and clean up failed downloads --- services/clsi/app/coffee/UrlFetcher.coffee | 26 ++++++++++++++++--- .../test/unit/coffee/UrlFetcherTests.coffee | 4 ++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/services/clsi/app/coffee/UrlFetcher.coffee b/services/clsi/app/coffee/UrlFetcher.coffee index 66c5edf011..5082a1592e 100644 --- a/services/clsi/app/coffee/UrlFetcher.coffee +++ b/services/clsi/app/coffee/UrlFetcher.coffee @@ -1,15 +1,31 @@ request = require("request").defaults(jar: false) fs = require("fs") +logger = require "logger-sharelatex" module.exports = UrlFetcher = pipeUrlToFile: (url, filePath, _callback = (error) ->) -> callbackOnce = (error) -> - _callback(error) - _callback = () -> + cleanUp error, (error) -> + _callback(error) + _callback = () -> + + cleanUp = (error, callback) -> + if error? + logger.log filePath: filePath, "deleting file from cache due to error" + fs.unlink filePath, (err) -> + if err? + logger.err err: err, filePath: filePath, "error deleting file from cache" + callback(error) + else + callback() - urlStream = request.get(url) fileStream = fs.createWriteStream(filePath) + fileStream.on 'error', (error) -> + logger.error err: error, url:url, filePath: filePath, "error writing file into cache" + callbackOnce(error) + logger.log url:url, filePath: filePath, "downloading url to cache" + urlStream = request.get(url) urlStream.on "response", (res) -> if res.statusCode >= 200 and res.statusCode < 300 urlStream.pipe(fileStream) @@ -17,7 +33,11 @@ module.exports = UrlFetcher = callbackOnce(new Error("URL returned non-success status code: #{res.statusCode} #{url}")) urlStream.on "error", (error) -> + logger.error err: error, url:url, filePath: filePath, "error downloading url" callbackOnce(error or new Error("Something went wrong downloading the URL #{url}")) urlStream.on "end", () -> + # FIXME: what if we get an error writing the file? Maybe we + # should be using the fileStream end event as the point of + # callback. callbackOnce() diff --git a/services/clsi/test/unit/coffee/UrlFetcherTests.coffee b/services/clsi/test/unit/coffee/UrlFetcherTests.coffee index 3e6dc926c7..7d38aa667b 100644 --- a/services/clsi/test/unit/coffee/UrlFetcherTests.coffee +++ b/services/clsi/test/unit/coffee/UrlFetcherTests.coffee @@ -11,6 +11,7 @@ describe "UrlFetcher", -> @UrlFetcher = SandboxedModule.require modulePath, requires: request: defaults: @defaults = sinon.stub().returns(@request = {}) fs: @fs = {} + "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } it "should turn off the cookie jar in request", -> @defaults.calledWith(jar: false) @@ -21,7 +22,8 @@ describe "UrlFetcher", -> @path = "/path/to/file/on/disk" @request.get = sinon.stub().returns(@urlStream = new EventEmitter) @urlStream.pipe = sinon.stub() - @fs.createWriteStream = sinon.stub().returns(@fileStream = "write-stream-stub") + @fs.createWriteStream = sinon.stub().returns(@fileStream = { on: () -> }) + @fs.unlink = (file, callback) -> callback() @UrlFetcher.pipeUrlToFile(@url, @path, @callback) it "should request the URL", -> From 76cd52af70007b7db41556d32638cf05c1b662ea Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Wed, 29 Apr 2015 15:58:28 +0100 Subject: [PATCH 086/754] prevent leak of urlStream on failed downloads --- services/clsi/app/coffee/UrlFetcher.coffee | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/services/clsi/app/coffee/UrlFetcher.coffee b/services/clsi/app/coffee/UrlFetcher.coffee index 5082a1592e..e14285d56e 100644 --- a/services/clsi/app/coffee/UrlFetcher.coffee +++ b/services/clsi/app/coffee/UrlFetcher.coffee @@ -30,6 +30,16 @@ module.exports = UrlFetcher = if res.statusCode >= 200 and res.statusCode < 300 urlStream.pipe(fileStream) else + logger.error statusCode: res.statusCode, url:url, filePath: filePath, "unexpected status code downloading url to cache" + # https://nodejs.org/api/http.html#http_class_http_clientrequest + # If you add a 'response' event handler, then you must consume + # the data from the response object, either by calling + # response.read() whenever there is a 'readable' event, or by + # adding a 'data' handler, or by calling the .resume() + # method. Until the data is consumed, the 'end' event will not + # fire. Also, until the data is read it will consume memory + # that can eventually lead to a 'process out of memory' error. + urlStream.on 'data', () -> # discard the data callbackOnce(new Error("URL returned non-success status code: #{res.statusCode} #{url}")) urlStream.on "error", (error) -> From 6a347a0ebee8c2d23b9deca0f404a94c15e2f9ee Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Wed, 29 Apr 2015 16:04:32 +0100 Subject: [PATCH 087/754] remove debugging from tests --- services/clsi/test/unit/coffee/CompileManagerTests.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 8923dea02f..d5644b6bb9 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -71,7 +71,6 @@ describe "CompileManager", -> .should.equal true it "should return the output files", -> - console.log 'output_files', @build_files @callback.calledWith(null, @build_files).should.equal true describe "clearProject", -> From 91d0f0f2f882b14e25f7ad2f3a7876ffefd059e4 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Thu, 30 Apr 2015 15:03:41 +0100 Subject: [PATCH 088/754] make startup message consistent --- services/clsi/app.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 464ff3900f..42c5c062dd 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -104,7 +104,7 @@ app.use (error, req, res, next) -> res.send error?.statusCode || 500 app.listen port = (Settings.internal?.clsi?.port or 3013), host = (Settings.internal?.clsi?.host or "localhost"), (error) -> - logger.log "CLSI listening on #{host}:#{port}" + logger.info "CLSI starting up, listening on #{host}:#{port}" setInterval () -> ProjectPersistenceManager.clearExpiredProjects() From b08349e07cd9bb091d50ece028683f70b2ea6325 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Tue, 5 May 2015 09:47:17 +0100 Subject: [PATCH 089/754] disable sequelize logging by default prevent any leaking of objects to console during debugging --- services/clsi/app/coffee/db.coffee | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/db.coffee b/services/clsi/app/coffee/db.coffee index 5438fc594b..203915efa3 100644 --- a/services/clsi/app/coffee/db.coffee +++ b/services/clsi/app/coffee/db.coffee @@ -1,11 +1,14 @@ Sequelize = require("sequelize") Settings = require("settings-sharelatex") +_ = require("underscore") + +options = _.extend {logging:false}, Settings.mysql.clsi sequelize = new Sequelize( Settings.mysql.clsi.database, Settings.mysql.clsi.username, Settings.mysql.clsi.password, - Settings.mysql.clsi + options ) module.exports = From f7d6c9de3150d90492df9d67fdbd644a5f9eb3d3 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Tue, 5 May 2015 09:51:45 +0100 Subject: [PATCH 090/754] avoid leak when calling chai.should() repeatedly in smoke test --- services/clsi/test/smoke/coffee/SmokeTests.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/test/smoke/coffee/SmokeTests.coffee b/services/clsi/test/smoke/coffee/SmokeTests.coffee index f560b0cf2d..48c50d6681 100644 --- a/services/clsi/test/smoke/coffee/SmokeTests.coffee +++ b/services/clsi/test/smoke/coffee/SmokeTests.coffee @@ -1,5 +1,5 @@ chai = require("chai") -chai.should() +chai.should() unless Object.prototype.should? expect = chai.expect request = require "request" Settings = require "settings-sharelatex" From 23e3b279c1505cc617b8052ded48c9f8a702fee9 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Tue, 5 May 2015 10:54:59 +0100 Subject: [PATCH 091/754] use the latest versions of metrics and smoketest modules --- services/clsi/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index 8bf9035673..3d02800b2c 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -15,10 +15,10 @@ "request": "~2.21.0", "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.0.0", "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", - "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.0.0", + "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git", "sequelize": "2.0.0-beta.2", "wrench": "~1.5.4", - "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v1.0.0", + "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git", "sqlite3": "~2.2.0", "express": "^4.2.0", "body-parser": "^1.2.0", From 604c31ae84c892dc446f60cc477182e0549f1415 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Mon, 11 May 2015 12:10:13 +0100 Subject: [PATCH 092/754] additional validation of requests --- .../coffee/StaticServerForbidSymlinks.coffee | 19 +++++- .../StaticServerForbidSymlinksTests.coffee | 65 +++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/StaticServerForbidSymlinks.coffee b/services/clsi/app/coffee/StaticServerForbidSymlinks.coffee index 2f48190835..348eccab0a 100644 --- a/services/clsi/app/coffee/StaticServerForbidSymlinks.coffee +++ b/services/clsi/app/coffee/StaticServerForbidSymlinks.coffee @@ -9,7 +9,24 @@ module.exports = ForbidSymlinks = (staticFn, root, options) -> basePath = Path.resolve(root) return (req, res, next) -> path = url.parse(req.url)?.pathname - requestedFsPath = Path.normalize("#{basePath}/#{path}") + # check that the path is of the form /project_id/path/to/file + if result = path.match(/^\/?(\w+)\/(.*)/) + project_id = result[1] + file = result[2] + else + logger.warn path: path, "unrecognized file request" + return res.sendStatus(404) + # check that the file does not use a relative path + for dir in file.split('/') + if dir == '..' + logger.warn path: path, "attempt to use a relative path" + return res.sendStatus(404) + # check that the requested path is normalized + requestedFsPath = "#{basePath}/#{project_id}/#{file}" + if requestedFsPath != Path.normalize(requestedFsPath) + logger.error path: requestedFsPath, "requestedFsPath is not normalized" + return res.sendStatus(404) + # check that the requested path is not a symlink fs.realpath requestedFsPath, (err, realFsPath)-> if err? logger.warn err:err, requestedFsPath:requestedFsPath, realFsPath:realFsPath, path: req.params[0], project_id: req.params.project_id, "error checking file access" diff --git a/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.coffee b/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.coffee index e17bacefca..e6b7f5f925 100644 --- a/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.coffee +++ b/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.coffee @@ -20,6 +20,7 @@ describe "StaticServerForbidSymlinks", -> "logger-sharelatex": log:-> warn:-> + error:-> "fs":@fs @dummyStatic = (rootDir, options) -> @@ -69,6 +70,70 @@ describe "StaticServerForbidSymlinks", -> done() @StaticServerForbidSymlinks @req, @res + + describe "with a relative file", -> + beforeEach -> + @req.url = "/12345/../67890/output.pdf" + + it "should send a 404", (done)-> + @res.sendStatus = (resCode)-> + resCode.should.equal 404 + done() + @StaticServerForbidSymlinks @req, @res + + + describe "with a unnormalized file containing .", -> + beforeEach -> + @req.url = "/12345/foo/./output.pdf" + + it "should send a 404", (done)-> + @res.sendStatus = (resCode)-> + resCode.should.equal 404 + done() + @StaticServerForbidSymlinks @req, @res + + + describe "with a file containing an empty path", -> + beforeEach -> + @req.url = "/12345/foo//output.pdf" + + it "should send a 404", (done)-> + @res.sendStatus = (resCode)-> + resCode.should.equal 404 + done() + @StaticServerForbidSymlinks @req, @res + + describe "with a non-project file", -> + beforeEach -> + @req.url = "/.foo/output.pdf" + + it "should send a 404", (done)-> + @res.sendStatus = (resCode)-> + resCode.should.equal 404 + done() + @StaticServerForbidSymlinks @req, @res + + describe "with a file outside the compiledir", -> + beforeEach -> + @req.url = "/../bar/output.pdf" + + it "should send a 404", (done)-> + @res.sendStatus = (resCode)-> + resCode.should.equal 404 + done() + @StaticServerForbidSymlinks @req, @res + + + describe "with a file with no leading /", -> + beforeEach -> + @req.url = "./../bar/output.pdf" + + it "should send a 404", (done)-> + @res.sendStatus = (resCode)-> + resCode.should.equal 404 + done() + @StaticServerForbidSymlinks @req, @res + describe "with an error from fs.realpath", -> beforeEach -> From 7dfdad02f56b1f6dc76c717e21fcdd651560c81a Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Tue, 12 May 2015 15:17:18 +0100 Subject: [PATCH 093/754] change regex checking file request ensure other files can not be accessed --- .../clsi/app/coffee/StaticServerForbidSymlinks.coffee | 4 ++-- .../coffee/StaticServerForbidSymlinksTests.coffee | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/StaticServerForbidSymlinks.coffee b/services/clsi/app/coffee/StaticServerForbidSymlinks.coffee index 348eccab0a..83ca4ca703 100644 --- a/services/clsi/app/coffee/StaticServerForbidSymlinks.coffee +++ b/services/clsi/app/coffee/StaticServerForbidSymlinks.coffee @@ -9,8 +9,8 @@ module.exports = ForbidSymlinks = (staticFn, root, options) -> basePath = Path.resolve(root) return (req, res, next) -> path = url.parse(req.url)?.pathname - # check that the path is of the form /project_id/path/to/file - if result = path.match(/^\/?(\w+)\/(.*)/) + # check that the path is of the form /project_id_or_name/path/to/file.log + if result = path.match(/^\/?([a-zA-Z0-9_-]+)\/(.*)/) project_id = result[1] file = result[2] else diff --git a/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.coffee b/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.coffee index e6b7f5f925..4a87d64207 100644 --- a/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.coffee +++ b/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.coffee @@ -134,6 +134,17 @@ describe "StaticServerForbidSymlinks", -> done() @StaticServerForbidSymlinks @req, @res + describe "with a github style path", -> + beforeEach -> + @req.url = "/henryoswald-latex_example/output/output.log" + @fs.realpath = sinon.stub().callsArgWith(1, null, "#{@settings.path.compilesDir}/henryoswald-latex_example/output/output.log") + + it "should call next", (done)-> + @res.sendStatus = (resCode)-> + resCode.should.equal 200 + done() + @StaticServerForbidSymlinks @req, @res, done + describe "with an error from fs.realpath", -> beforeEach -> From 56f1ffa067dee7a3145cd83e9e8f3ef8ac5b45db Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Wed, 13 May 2015 16:59:51 +0100 Subject: [PATCH 094/754] only run qpdf for the main output.pdf file was previously matching any pdf file, which caused it to run for embedded pdf figures produced during the mklatex run --- services/clsi/app/coffee/OutputFileOptimiser.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/OutputFileOptimiser.coffee b/services/clsi/app/coffee/OutputFileOptimiser.coffee index d0c091d37b..f337a7a953 100644 --- a/services/clsi/app/coffee/OutputFileOptimiser.coffee +++ b/services/clsi/app/coffee/OutputFileOptimiser.coffee @@ -9,7 +9,7 @@ module.exports = OutputFileOptimiser = optimiseFile: (src, dst, callback = (error) ->) -> # check output file (src) and see if we can optimise it, storing # the result in the build directory (dst) - if src.match(/\.pdf$/) + if src.match(/\/output\.pdf$/) OutputFileOptimiser.optimisePDF src, dst, callback else callback (null) From aa32cbc1ee27eabfb5dcc6f2789c955e9daeb328 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Fri, 15 May 2015 14:17:40 +0100 Subject: [PATCH 095/754] clean up stream handling for file copy --- services/clsi/app/coffee/UrlCache.coffee | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/UrlCache.coffee b/services/clsi/app/coffee/UrlCache.coffee index 941bc9defd..be6960c20a 100644 --- a/services/clsi/app/coffee/UrlCache.coffee +++ b/services/clsi/app/coffee/UrlCache.coffee @@ -75,8 +75,9 @@ module.exports = UrlCache = readStream = fs.createReadStream(from) writeStream.on "error", callbackOnce readStream.on "error", callbackOnce - writeStream.on "close", () -> callbackOnce() - readStream.pipe(writeStream) + writeStream.on "close", callbackOnce + writeStream.on "open", () -> + readStream.pipe(writeStream) _clearUrlFromCache: (project_id, url, callback = (error) ->) -> UrlCache._clearUrlDetails project_id, url, (error) -> From 608b1dd65781744c48bdd14eb95aa4d4328a692e Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Tue, 12 May 2015 11:40:29 +0100 Subject: [PATCH 096/754] replace deprecated send(code,body) calls --- services/clsi/app.coffee | 4 ++-- services/clsi/app/coffee/CompileController.coffee | 4 ++-- .../test/unit/coffee/CompileControllerTests.coffee | 13 +++++++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 42c5c062dd..9083bb4bf3 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -84,7 +84,7 @@ if Settings.smokeTest app.get "/health_check", (req, res)-> res.contentType(resCacher?.setContentType) - res.send resCacher?.code, resCacher?.body + res.status(resCacher?.code).send(resCacher?.body) profiler = require "v8-profiler" app.get "/profile", (req, res) -> @@ -101,7 +101,7 @@ app.get "/heapdump", (req, res)-> app.use (error, req, res, next) -> logger.error err: error, "server error" - res.send error?.statusCode || 500 + res.sendStatus(error?.statusCode || 500) app.listen port = (Settings.internal?.clsi?.port or 3013), host = (Settings.internal?.clsi?.host or "localhost"), (error) -> logger.info "CLSI starting up, listening on #{host}:#{port}" diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 29373c36db..71368be737 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -28,7 +28,7 @@ module.exports = CompileController = status = "success" timer.done() - res.send (code or 200), { + res.status(code or 200).send { compile: status: status error: error?.message or error @@ -41,7 +41,7 @@ module.exports = CompileController = clearCache: (req, res, next = (error) ->) -> ProjectPersistenceManager.clearProject req.params.project_id, (error) -> return next(error) if error? - res.send 204 # No content + res.sendStatus(204) # No content syncFromCode: (req, res, next = (error) ->) -> file = req.query.file diff --git a/services/clsi/test/unit/coffee/CompileControllerTests.coffee b/services/clsi/test/unit/coffee/CompileControllerTests.coffee index bc2d988132..3298f472e6 100644 --- a/services/clsi/test/unit/coffee/CompileControllerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileControllerTests.coffee @@ -44,6 +44,7 @@ describe "CompileController", -> }] @RequestParser.parse = sinon.stub().callsArgWith(1, null, @request) @ProjectPersistenceManager.markProjectAsJustAccessed = sinon.stub().callsArg(1) + @res.status = sinon.stub().returnsThis() @res.send = sinon.stub() describe "successfully", -> @@ -67,8 +68,9 @@ describe "CompileController", -> .should.equal true it "should return the JSON response", -> + @res.status.calledWith(200).should.equal true @res.send - .calledWith(200, + .calledWith( compile: status: "success" error: null @@ -85,8 +87,9 @@ describe "CompileController", -> @CompileController.compile @req, @res it "should return the JSON response with the error", -> + @res.status.calledWith(500).should.equal true @res.send - .calledWith(500, + .calledWith( compile: status: "error" error: @message @@ -102,8 +105,9 @@ describe "CompileController", -> @CompileController.compile @req, @res it "should return the JSON response with the timeout status", -> + @res.status.calledWith(200).should.equal true @res.send - .calledWith(200, + .calledWith( compile: status: "timedout" error: @message @@ -117,8 +121,9 @@ describe "CompileController", -> @CompileController.compile @req, @res it "should return the JSON response with the failure status", -> + @res.status.calledWith(200).should.equal true @res.send - .calledWith(200, + .calledWith( compile: error: null status: "failure" From 66fc2715dce3af34c44bf8e580081b43be2cba40 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Fri, 15 May 2015 14:07:15 +0100 Subject: [PATCH 097/754] clean up error handling in UrlFetcher --- services/clsi/app/coffee/UrlFetcher.coffee | 68 +++++++++++-------- .../test/unit/coffee/UrlFetcherTests.coffee | 16 +++-- 2 files changed, 49 insertions(+), 35 deletions(-) diff --git a/services/clsi/app/coffee/UrlFetcher.coffee b/services/clsi/app/coffee/UrlFetcher.coffee index e14285d56e..9675da9b25 100644 --- a/services/clsi/app/coffee/UrlFetcher.coffee +++ b/services/clsi/app/coffee/UrlFetcher.coffee @@ -2,33 +2,53 @@ request = require("request").defaults(jar: false) fs = require("fs") logger = require "logger-sharelatex" +oneMinute = 60 * 1000 + module.exports = UrlFetcher = pipeUrlToFile: (url, filePath, _callback = (error) ->) -> callbackOnce = (error) -> - cleanUp error, (error) -> - _callback(error) - _callback = () -> + clearTimeout timeoutHandler if timeoutHandler? + _callback(error) + _callback = () -> - cleanUp = (error, callback) -> - if error? - logger.log filePath: filePath, "deleting file from cache due to error" - fs.unlink filePath, (err) -> - if err? - logger.err err: err, filePath: filePath, "error deleting file from cache" - callback(error) - else - callback() + timeoutHandler = setTimeout () -> + timeoutHandler = null + logger.error url:url, filePath: filePath, "Timed out downloading file to cache" + callbackOnce(new Error("Timed out downloading file to cache #{url}")) + # FIXME: maybe need to close fileStream here + , 3 * oneMinute - fileStream = fs.createWriteStream(filePath) - fileStream.on 'error', (error) -> - logger.error err: error, url:url, filePath: filePath, "error writing file into cache" - callbackOnce(error) + logger.log url:url, filePath: filePath, "started downloading url to cache" + urlStream = request.get({url: url, timeout: oneMinute}) + urlStream.pause() + + urlStream.on "error", (error) -> + logger.error err: error, url:url, filePath: filePath, "error downloading url" + callbackOnce(error or new Error("Something went wrong downloading the URL #{url}")) + + urlStream.on "end", () -> + logger.log url:url, filePath: filePath, "finished downloading file into cache" - logger.log url:url, filePath: filePath, "downloading url to cache" - urlStream = request.get(url) urlStream.on "response", (res) -> if res.statusCode >= 200 and res.statusCode < 300 + fileStream = fs.createWriteStream(filePath) + + fileStream.on 'error', (error) -> + logger.error err: error, url:url, filePath: filePath, "error writing file into cache" + fs.unlink filePath, (err) -> + if err? + logger.err err: err, filePath: filePath, "error deleting file from cache" + callbackOnce(error) + + fileStream.on 'finish', () -> + logger.log url:url, filePath: filePath, "finished writing file into cache" + callbackOnce() + + fileStream.on 'pipe', () -> + logger.log url:url, filePath: filePath, "piping into filestream" + urlStream.pipe(fileStream) + urlStream.resume() else logger.error statusCode: res.statusCode, url:url, filePath: filePath, "unexpected status code downloading url to cache" # https://nodejs.org/api/http.html#http_class_http_clientrequest @@ -39,15 +59,5 @@ module.exports = UrlFetcher = # method. Until the data is consumed, the 'end' event will not # fire. Also, until the data is read it will consume memory # that can eventually lead to a 'process out of memory' error. - urlStream.on 'data', () -> # discard the data + urlStream.resume() # discard the data callbackOnce(new Error("URL returned non-success status code: #{res.statusCode} #{url}")) - - urlStream.on "error", (error) -> - logger.error err: error, url:url, filePath: filePath, "error downloading url" - callbackOnce(error or new Error("Something went wrong downloading the URL #{url}")) - - urlStream.on "end", () -> - # FIXME: what if we get an error writing the file? Maybe we - # should be using the fileStream end event as the point of - # callback. - callbackOnce() diff --git a/services/clsi/test/unit/coffee/UrlFetcherTests.coffee b/services/clsi/test/unit/coffee/UrlFetcherTests.coffee index 7d38aa667b..dd709ddeaf 100644 --- a/services/clsi/test/unit/coffee/UrlFetcherTests.coffee +++ b/services/clsi/test/unit/coffee/UrlFetcherTests.coffee @@ -22,25 +22,29 @@ describe "UrlFetcher", -> @path = "/path/to/file/on/disk" @request.get = sinon.stub().returns(@urlStream = new EventEmitter) @urlStream.pipe = sinon.stub() - @fs.createWriteStream = sinon.stub().returns(@fileStream = { on: () -> }) + @urlStream.pause = sinon.stub() + @urlStream.resume = sinon.stub() + @fs.createWriteStream = sinon.stub().returns(@fileStream = new EventEmitter) @fs.unlink = (file, callback) -> callback() @UrlFetcher.pipeUrlToFile(@url, @path, @callback) it "should request the URL", -> @request.get - .calledWith(@url) + .calledWith(sinon.match {"url": @url}) .should.equal true - it "should open the file for writing", -> - @fs.createWriteStream - .calledWith(@path) - .should.equal true describe "successfully", -> beforeEach -> @res = statusCode: 200 @urlStream.emit "response", @res @urlStream.emit "end" + @fileStream.emit "finish" + + it "should open the file for writing", -> + @fs.createWriteStream + .calledWith(@path) + .should.equal true it "should pipe the URL to the file", -> @urlStream.pipe From 6ebcd9e8fd6a807226725f4f530d083996f05767 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Fri, 15 May 2015 14:05:17 +0100 Subject: [PATCH 098/754] add indexes to db --- services/clsi/app/coffee/db.coffee | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/db.coffee b/services/clsi/app/coffee/db.coffee index 203915efa3..a72f61e8d0 100644 --- a/services/clsi/app/coffee/db.coffee +++ b/services/clsi/app/coffee/db.coffee @@ -16,11 +16,20 @@ module.exports = url: Sequelize.STRING project_id: Sequelize.STRING lastModified: Sequelize.DATE + }, { + indexes: [ + {fields: ['url', 'project_id']}, + {fields: ['project_id']} + ] }) Project: sequelize.define("Project", { - project_id: Sequelize.STRING + project_id: {type: Sequelize.STRING, primaryKey: true} lastAccessed: Sequelize.DATE + }, { + indexes: [ + {fields: ['lastAccessed']} + ] }) sync: () -> sequelize.sync() From e379068fa344bb03e60e59cd3c512f54122dd520 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Fri, 15 May 2015 14:05:28 +0100 Subject: [PATCH 099/754] upgrade sequelize and mysql --- .../coffee/ProjectPersistenceManager.coffee | 20 +++++++++---------- services/clsi/app/coffee/UrlCache.coffee | 16 +++++++-------- services/clsi/package.json | 4 ++-- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.coffee b/services/clsi/app/coffee/ProjectPersistenceManager.coffee index 8b37947317..2e23d46cec 100644 --- a/services/clsi/app/coffee/ProjectPersistenceManager.coffee +++ b/services/clsi/app/coffee/ProjectPersistenceManager.coffee @@ -8,11 +8,11 @@ module.exports = ProjectPersistenceManager = EXPIRY_TIMEOUT: oneDay = 24 * 60 * 60 * 1000 #ms markProjectAsJustAccessed: (project_id, callback = (error) ->) -> - db.Project.findOrCreate(project_id: project_id) - .success( - (project) -> + db.Project.findOrCreate(where: {project_id: project_id}) + .spread( + (project, created) -> project.updateAttributes(lastAccessed: new Date()) - .success(() -> callback()) + .then(() -> callback()) .error callback ) .error callback @@ -41,14 +41,12 @@ module.exports = ProjectPersistenceManager = callback() _clearProjectFromDatabase: (project_id, callback = (error) ->) -> - db.Project.destroy(project_id: project_id) - .success(() -> callback()) + db.Project.destroy(where: {project_id: project_id}) + .then(() -> callback()) .error callback _findExpiredProjectIds: (callback = (error, project_ids) ->) -> db.Project.findAll(where: ["lastAccessed < ?", new Date(Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT)]) - .success( - (projects) -> - callback null, projects.map((project) -> project.project_id) - ) - .error callback + .then((projects) -> + callback null, projects.map((project) -> project.project_id) + ).error callback diff --git a/services/clsi/app/coffee/UrlCache.coffee b/services/clsi/app/coffee/UrlCache.coffee index be6960c20a..535a70570c 100644 --- a/services/clsi/app/coffee/UrlCache.coffee +++ b/services/clsi/app/coffee/UrlCache.coffee @@ -91,27 +91,27 @@ module.exports = UrlCache = _findUrlDetails: (project_id, url, callback = (error, urlDetails) ->) -> db.UrlCache.find(where: { url: url, project_id: project_id }) - .success((urlDetails) -> callback null, urlDetails) + .then((urlDetails) -> callback null, urlDetails) .error callback _updateOrCreateUrlDetails: (project_id, url, lastModified, callback = (error) ->) -> - db.UrlCache.findOrCreate(url: url, project_id: project_id) - .success( - (urlDetails) -> + db.UrlCache.findOrCreate(where: {url: url, project_id: project_id}) + .spread( + (urlDetails, created) -> urlDetails.updateAttributes(lastModified: lastModified) - .success(() -> callback()) + .then(() -> callback()) .error(callback) ) .error callback _clearUrlDetails: (project_id, url, callback = (error) ->) -> - db.UrlCache.destroy(url: url, project_id: project_id) - .success(() -> callback null) + db.UrlCache.destroy(where: {url: url, project_id: project_id}) + .then(() -> callback null) .error callback _findAllUrlsInProject: (project_id, callback = (error, urls) ->) -> db.UrlCache.findAll(where: { project_id: project_id }) - .success( + .then( (urlEntries) -> callback null, urlEntries.map((entry) -> entry.url) ) diff --git a/services/clsi/package.json b/services/clsi/package.json index 3d02800b2c..fc75cede50 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -11,12 +11,12 @@ "async": "0.2.9", "lynx": "0.0.11", "mkdirp": "0.3.5", - "mysql": "2.0.0-alpha7", + "mysql": "2.6.2", "request": "~2.21.0", "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.0.0", "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git", - "sequelize": "2.0.0-beta.2", + "sequelize": "^2.1.3", "wrench": "~1.5.4", "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git", "sqlite3": "~2.2.0", From 44e1dc8c0c0b0ed3fb39a00af8e4b0696b565cd0 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Thu, 21 May 2015 11:33:13 +0100 Subject: [PATCH 100/754] added comments --- services/clsi/app/coffee/UrlFetcher.coffee | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/UrlFetcher.coffee b/services/clsi/app/coffee/UrlFetcher.coffee index 9675da9b25..201306c01d 100644 --- a/services/clsi/app/coffee/UrlFetcher.coffee +++ b/services/clsi/app/coffee/UrlFetcher.coffee @@ -20,8 +20,9 @@ module.exports = UrlFetcher = logger.log url:url, filePath: filePath, "started downloading url to cache" urlStream = request.get({url: url, timeout: oneMinute}) - urlStream.pause() + urlStream.pause() # stop data flowing until we are ready + # attach handlers before setting up pipes urlStream.on "error", (error) -> logger.error err: error, url:url, filePath: filePath, "error downloading url" callbackOnce(error or new Error("Something went wrong downloading the URL #{url}")) @@ -33,6 +34,7 @@ module.exports = UrlFetcher = if res.statusCode >= 200 and res.statusCode < 300 fileStream = fs.createWriteStream(filePath) + # attach handlers before setting up pipes fileStream.on 'error', (error) -> logger.error err: error, url:url, filePath: filePath, "error writing file into cache" fs.unlink filePath, (err) -> @@ -48,7 +50,7 @@ module.exports = UrlFetcher = logger.log url:url, filePath: filePath, "piping into filestream" urlStream.pipe(fileStream) - urlStream.resume() + urlStream.resume() # now we are ready to handle the data else logger.error statusCode: res.statusCode, url:url, filePath: filePath, "unexpected status code downloading url to cache" # https://nodejs.org/api/http.html#http_class_http_clientrequest From 59e87a8729b9a2e19f3b3b416e940d18ce41b26b Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Mon, 8 Jun 2015 18:35:24 -0300 Subject: [PATCH 101/754] initial version of texcount --- services/clsi/app.coffee | 1 + .../clsi/app/coffee/CompileController.coffee | 10 ++++ .../clsi/app/coffee/CompileManager.coffee | 50 ++++++++++++++++++- .../acceptance/coffee/SynctexTests.coffee | 17 +++++++ .../acceptance/coffee/helpers/Client.coffee | 9 ++++ 5 files changed, 86 insertions(+), 1 deletion(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 9083bb4bf3..8e451a7650 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -36,6 +36,7 @@ app.delete "/project/:project_id", CompileController.clearCache app.get "/project/:project_id/sync/code", CompileController.syncFromCode app.get "/project/:project_id/sync/pdf", CompileController.syncFromPdf +app.get "/project/:project_id/wordcount", CompileController.wordcount ForbidSymlinks = require "./app/js/StaticServerForbidSymlinks" diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 71368be737..ce107f8eab 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -66,3 +66,13 @@ module.exports = CompileController = res.send JSON.stringify { code: codePositions } + + wordcount: (req, res, next = (error) ->) -> + file = req.query.file + project_id = req.params.project_id + + CompileManager.wordcount project_id, file, (error, result) -> + return next(error) if error? + res.send JSON.stringify { + texcount: result + } diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index ac0c241149..5bbd3ab2e1 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -107,4 +107,52 @@ module.exports = CompileManager = line: parseInt(line, 10) column: parseInt(column, 10) } - return results \ No newline at end of file + return results + + _parseWordcountFromOutput: (output) -> + results = { + encode: "" + textWords: 0 + headWords: 0 + outside: 0 + headers: 0 + elements: 0 + mathInline: 0 + mathDisplay: 0 + } + for line in output.split("\n") + [data, info] = line.split(":") + if data.indexOf("Encoding") > -1 + results['encode'] = info.trim() + if data.indexOf("in text") > -1 + results['textWords'] = parseInt(info, 10) + if data.indexOf("in head") > -1 + results['headWords'] = parseInt(info, 10) + if data.indexOf("outside") > -1 + results['outside'] = parseInt(info, 10) + if data.indexOf("of head") > -1 + results['headers'] = parseInt(info, 10) + if data.indexOf("float") > -1 + results['elements'] = parseInt(info, 10) + if data.indexOf("inlines") > -1 + results['mathInline'] = parseInt(info, 10) + if data.indexOf("displayed") > -1 + results['mathDisplay'] = parseInt(info, 10) + + return results + + wordcount: (project_id, file_name, callback = (error, pdfPositions) ->) -> + base_dir = Settings.path.synctexBaseDir(project_id) + file_path = base_dir + "/" + file_name + logger.log project_id: project_id, file_name: file_name, "try wordcount" + CompileManager._runWordcount [file_path], (error, stdout) -> + return callback(error) if error? + logger.log project_id: project_id, file_name: file_name, stdout: stdout, "wordcount output" + callback null, CompileManager._parseWordcountFromOutput(stdout) + + _runWordcount: (args, callback = (error, stdout) ->) -> + bin_path = Path.resolve("texcount") + seconds = 1000 + child_process.execFile "texcount", args, timeout: 10 * seconds, (error, stdout, stderr) -> + return callback(error) if error? + callback(null, stdout) \ No newline at end of file diff --git a/services/clsi/test/acceptance/coffee/SynctexTests.coffee b/services/clsi/test/acceptance/coffee/SynctexTests.coffee index 330eaff22e..8979b983e5 100644 --- a/services/clsi/test/acceptance/coffee/SynctexTests.coffee +++ b/services/clsi/test/acceptance/coffee/SynctexTests.coffee @@ -36,3 +36,20 @@ describe "Syncing", -> ) done() + describe "wordcount file", -> + it "should return wordcount info", (done) -> + Client.wordcount @project_id, "main.tex", (error, result) -> + throw error if error? + expect(result).to.deep.equal( + texcount: { + encode: "ascii" + textWords: 2 + headWords: 0 + outside: 0 + headers: 0 + elements: 0 + mathInline: 0 + mathDisplay: 0 + } + ) + done() diff --git a/services/clsi/test/acceptance/coffee/helpers/Client.coffee b/services/clsi/test/acceptance/coffee/helpers/Client.coffee index 7025b79cce..3e1a5b86d0 100644 --- a/services/clsi/test/acceptance/coffee/helpers/Client.coffee +++ b/services/clsi/test/acceptance/coffee/helpers/Client.coffee @@ -90,3 +90,12 @@ module.exports = Client = @compile project_id, req, callback + wordcount: (project_id, file, callback = (error, pdfPositions) ->) -> + request.get { + url: "#{@host}/project/#{project_id}/wordcount" + qs: { + file: file + } + }, (error, response, body) -> + return callback(error) if error? + callback null, JSON.parse(body) From 32c0a89ae1fb798a73dfdcbfc079fa3c31d9e29d Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Mon, 8 Jun 2015 19:27:47 -0300 Subject: [PATCH 102/754] add unit test --- .../clsi/app/coffee/CompileManager.coffee | 2 -- .../unit/coffee/CompileControllerTests.coffee | 24 ++++++++++++++ .../unit/coffee/CompileManagerTests.coffee | 31 ++++++++++++++++++- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 5bbd3ab2e1..f689ae8833 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -144,14 +144,12 @@ module.exports = CompileManager = wordcount: (project_id, file_name, callback = (error, pdfPositions) ->) -> base_dir = Settings.path.synctexBaseDir(project_id) file_path = base_dir + "/" + file_name - logger.log project_id: project_id, file_name: file_name, "try wordcount" CompileManager._runWordcount [file_path], (error, stdout) -> return callback(error) if error? logger.log project_id: project_id, file_name: file_name, stdout: stdout, "wordcount output" callback null, CompileManager._parseWordcountFromOutput(stdout) _runWordcount: (args, callback = (error, stdout) ->) -> - bin_path = Path.resolve("texcount") seconds = 1000 child_process.execFile "texcount", args, timeout: 10 * seconds, (error, stdout, stderr) -> return callback(error) if error? diff --git a/services/clsi/test/unit/coffee/CompileControllerTests.coffee b/services/clsi/test/unit/coffee/CompileControllerTests.coffee index 3298f472e6..e0307d20c9 100644 --- a/services/clsi/test/unit/coffee/CompileControllerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileControllerTests.coffee @@ -189,3 +189,27 @@ describe "CompileController", -> ) .should.equal true + describe "wordcount", -> + beforeEach -> + @file = "main.tex" + @project_id = "mock-project-id" + @req.params = + project_id: @project_id + @req.query = + file: @file + @res.send = sinon.stub() + + @CompileManager.wordcount = sinon.stub().callsArgWith(2, null, @texcount = ["mock-texcount"]) + @CompileController.wordcount @req, @res, @next + + it "should return the word count of a file", -> + @CompileManager.wordcount + .calledWith(@project_id, @file) + .should.equal true + + it "should return the texcount info", -> + @res.send + .calledWith(JSON.stringify + texcount: @texcount + ) + .should.equal true diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index d5644b6bb9..eae49627c1 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -171,4 +171,33 @@ describe "CompileManager", -> line: @line column: @column }]) - .should.equal true \ No newline at end of file + .should.equal true + + describe "wordcount", -> + beforeEach -> + @file_name = "main.tex" + @child_process.execFile = sinon.stub() + @child_process.execFile.callsArgWith(3, null, @stdout = "Encoding: ascii\nWords in text: 2", "") + @Settings.path.synctexBaseDir = (project_id) => "#{@Settings.path.compilesDir}/#{@project_id}" + @CompileManager.wordcount @project_id, @file_name, @callback + + it "should execute the texcount binary", -> + file_path = "#{@Settings.path.compilesDir}/#{@project_id}/#{@file_name}" + @child_process.execFile + .calledWith("texcount", [file_path], timeout: 10000) + .should.equal true + + it "should call the callback with the parsed output", -> + @callback + .calledWith(null, { + encode: "ascii" + textWords: 2 + headWords: 0 + outside: 0 + headers: 0 + elements: 0 + mathInline: 0 + mathDisplay: 0 + }) + .should.equal true + From e5f60081c969f7f36e26ac161df3da7d0818b0ad Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Fri, 12 Jun 2015 17:11:03 +0100 Subject: [PATCH 103/754] added some load tests in --- services/clsi/test/load/coffee/bulk.tex | 234 ++++++++++++++++++ .../clsi/test/load/coffee/loadTest.coffee | 71 ++++++ 2 files changed, 305 insertions(+) create mode 100644 services/clsi/test/load/coffee/bulk.tex create mode 100644 services/clsi/test/load/coffee/loadTest.coffee diff --git a/services/clsi/test/load/coffee/bulk.tex b/services/clsi/test/load/coffee/bulk.tex new file mode 100644 index 0000000000..67c4772c14 --- /dev/null +++ b/services/clsi/test/load/coffee/bulk.tex @@ -0,0 +1,234 @@ + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. In tincidunt mattis sapien at tempor. Mauris ac tristique erat. Praesent interdum ipsum sem, ac fermentum urna imperdiet in. Nulla tincidunt purus vitae ipsum sagittis tincidunt. Aenean id nisi ullamcorper, ultrices mi vel, iaculis nunc. Sed vel varius metus, ac eleifend mauris. Donec sed orci fringilla, fermentum nulla vehicula, sodales purus. + +Maecenas nulla quam, congue vitae pellentesque sed, bibendum eu felis. Vestibulum congue gravida diam, in venenatis nisl lacinia id. Nullam eget purus ac enim dignissim consectetur vel at dolor. Integer rhoncus nisl eu odio luctus, at placerat dolor congue. Fusce sodales molestie sem eget scelerisque. Sed eros tellus, tempor eu commodo nec, maximus imperdiet eros. Aliquam vulputate ligula non bibendum tempus. In commodo eros ante, ultrices condimentum purus finibus ut. Suspendisse at eleifend mauris, vitae tincidunt sapien. Curabitur orci ipsum, aliquet a cursus efficitur, lacinia ac ex. Integer lacinia bibendum dui ut ullamcorper. Curabitur in ultricies tellus, quis ullamcorper sem. Praesent sodales dui odio. Ut lacinia aliquet eros, ut maximus nisi. Donec sit amet dui a neque interdum dapibus. + +Ut vulputate sem in lectus porttitor ullamcorper. Nulla ut urna vitae tellus posuere aliquam vitae in odio. Praesent placerat laoreet viverra. Curabitur lacinia est lectus, eget euismod nisi viverra eget. Aliquam facilisis lectus ut tincidunt mollis. Donec ut rhoncus lorem. Vivamus ultricies venenatis congue. Etiam non risus quis leo sodales lacinia. Phasellus commodo feugiat sem quis dignissim. Nunc augue dui, bibendum sed leo vitae, malesuada vulputate sem. + +Quisque nec semper nulla. Etiam dictum blandit interdum. Morbi leo leo, scelerisque vel enim vel, egestas volutpat ligula. Maecenas ac elementum lacus. Duis molestie nunc id metus iaculis, in hendrerit massa egestas. Praesent feugiat tempor dui, sit amet ultrices dui elementum id. Suspendisse cursus accumsan diam, non imperdiet diam dapibus facilisis. Praesent blandit urna felis, eget sodales nisi dictum non. Cras finibus quis augue a venenatis. In pretium condimentum arcu, at vehicula ex gravida ut. Etiam congue urna ipsum, mattis interdum neque cursus bibendum. + +Morbi felis orci, ultricies eget magna gravida, blandit condimentum erat. Curabitur convallis quam eros, eu porta diam ornare vitae. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed eleifend convallis massa, eget tristique dolor iaculis sed. Mauris id nunc erat. Donec semper rhoncus libero sit amet rhoncus. Suspendisse cursus suscipit augue quis fermentum. Sed in maximus erat. + +Ut ultrices massa vitae lectus dictum fermentum. Cras vitae risus metus. Curabitur eleifend hendrerit dolor sit amet rutrum. Pellentesque pellentesque dolor ut felis vehicula pharetra. Nam id ante eget turpis vehicula interdum in vitae odio. Nullam nec orci interdum, commodo massa et, rutrum purus. Aenean vitae porta sem. Nam in lacinia turpis. Duis dui ligula, molestie quis sagittis sit amet, faucibus ac leo. Curabitur sit amet porta ligula. Integer et sollicitudin velit. Donec magna justo, ultricies eu nunc ut, rutrum aliquam orci. Sed in dignissim sem. Proin rutrum velit urna, eu tincidunt ipsum fermentum non. Morbi id cursus nisl. + +Curabitur sed gravida ex, posuere laoreet orci. Morbi ac lacus quis tortor faucibus feugiat. Etiam fringilla lacinia libero. Duis varius sem vel lorem euismod luctus. Fusce tincidunt quis sem in ullamcorper. Ut luctus massa aliquam hendrerit finibus. Ut venenatis, neque eu hendrerit finibus, nisl tortor venenatis eros, in imperdiet leo est quis erat. Fusce luctus posuere massa, ut fermentum sapien blandit ut. Maecenas feugiat consequat lorem, eget sagittis elit vestibulum sit amet. Vivamus molestie ante ut turpis laoreet facilisis vitae eu diam. Integer a tempor tortor. In hac habitasse platea dictumst. Quisque arcu est, blandit eu justo sed, posuere congue nisi. Aliquam magna augue, convallis ac scelerisque vel, cursus eget dui. Nam rutrum auctor odio, vel sagittis ipsum gravida vel. + +Etiam elementum placerat egestas. Morbi nec mi posuere, congue ligula eu, sagittis turpis. Fusce urna nisi, dapibus in pretium et, lobortis eu arcu. Curabitur ornare urna mauris, vitae varius nulla posuere in. Integer faucibus euismod dui, a venenatis massa vehicula sit amet. Donec fringilla tellus vitae ligula pretium mattis. Aliquam aliquet quam augue, a luctus orci euismod sed. Morbi tincidunt tincidunt nulla, eget elementum turpis congue id. Suspendisse pellentesque nulla leo, fermentum ultrices massa sollicitudin vel. Morbi vel nisl consectetur, pulvinar sapien a, accumsan diam. Morbi posuere auctor nibh, nec maximus ante tincidunt ac. Etiam ut erat consectetur, molestie est sit amet, pharetra nulla. Quisque varius vestibulum ex, eget feugiat enim molestie ac. Nulla quis imperdiet risus. + +Nullam nec tempor arcu. Duis fringilla mi at magna dignissim, quis feugiat turpis lacinia. Nunc sed aliquet ipsum. Curabitur at dolor in dui posuere ornare a ut ex. Ut congue neque quis justo iaculis, ut accumsan odio condimentum. Donec sed tempus diam. Phasellus tincidunt malesuada dui, nec gravida justo volutpat vel. Praesent mi purus, sagittis in imperdiet sed, sodales eu turpis. Nullam rutrum non lacus ac imperdiet. Ut ultrices lorem at facilisis feugiat. Morbi eros enim, tristique at nisl ut, venenatis porttitor ligula. Nullam sed diam at nibh tristique consectetur. Phasellus iaculis justo nisi, ut interdum ante rutrum sit amet. Pellentesque finibus felis blandit metus pulvinar lacinia. + +Aliquam erat volutpat. Nulla eu tortor sit amet tellus bibendum tristique eget consequat metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut in aliquet augue. Phasellus laoreet nulla convallis finibus vehicula. Fusce et urna dui. Duis vel porta nunc. Nunc condimentum, justo at efficitur dignissim, lorem diam elementum ex, at dictum lectus sapien ac neque. Aliquam lacinia et ipsum lacinia efficitur. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus convallis urna orci, et dictum sapien porta sit amet. Maecenas euismod dolor mattis sapien vestibulum pulvinar. + +Vestibulum eget posuere purus, et viverra est. Nullam egestas massa et finibus semper. Vestibulum egestas porta ante eget maximus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Pellentesque bibendum magna et fermentum consequat. Duis non arcu quis justo dignissim tempus at id diam. Praesent condimentum vel leo ac efficitur. Phasellus sollicitudin ipsum ut consectetur euismod. Proin diam eros, placerat sed dui ac, porttitor pellentesque nibh. Curabitur fermentum volutpat enim, in ullamcorper ipsum euismod et. Nunc a justo tortor. Phasellus libero nunc, consectetur ut dolor non, volutpat condimentum metus. + +Ut tincidunt est sem, eu venenatis lectus pretium pretium. Vivamus venenatis, erat nec sollicitudin semper, justo nulla euismod dui, quis tempor libero lectus sit amet neque. Sed in iaculis ipsum. Quisque ultricies sed mi a consequat. Sed tincidunt ante ut turpis vehicula, sed fringilla ligula efficitur. Cras eget suscipit sapien. Ut sed malesuada est, ut tempor leo. Mauris dignissim turpis quis turpis placerat cursus. Vivamus dictum dui sed blandit aliquet. + +Ut cursus, nulla eget ultricies tempor, magna enim aliquam libero, eget tempus mauris mauris ut elit. Nulla a mi quam. Integer ullamcorper ex et enim ornare efficitur. Vivamus tellus orci, pharetra in suscipit ac, ultrices sit amet sapien. Pellentesque pretium mauris vel orci accumsan, a hendrerit lectus sagittis. Mauris id nisi commodo, eleifend arcu in, vestibulum metus. Fusce vulputate gravida tincidunt. Nulla cursus non tortor ut tincidunt. Phasellus vel nisi tempus, fringilla lectus sed, ultricies erat. Ut gravida, enim id facilisis consequat, est nisi scelerisque magna, eget pharetra elit mi elementum ligula. Morbi hendrerit tortor eget velit rhoncus, consequat porta nisl aliquet. Nam diam turpis, ullamcorper vitae nisi eu, ultrices hendrerit magna. Vivamus eget pretium elit. Vivamus vitae odio sit amet libero hendrerit imperdiet. + +Aenean pharetra ex eget lectus sodales placerat. Fusce quis orci vel est suscipit venenatis. Curabitur maximus, sem in tincidunt imperdiet, nisl lorem venenatis mauris, eget facilisis lectus mauris a eros. Nam luctus sem ac diam ultrices, eget vulputate tortor efficitur. Nunc fermentum condimentum lacus id faucibus. Nunc ut tellus pretium, mattis eros vitae, scelerisque felis. Aenean ligula nulla, vulputate id eros id, vestibulum vulputate odio. Nunc in elit id augue porttitor auctor sed vitae lacus. Integer enim orci, auctor at magna eget, viverra tempus risus. Nulla suscipit metus tortor, ultricies vestibulum odio euismod at. Etiam consequat diam ac leo dignissim vulputate. Donec lectus lorem, finibus sed purus ac, eleifend condimentum ipsum. + +Fusce ornare metus vel dui scelerisque vehicula. Proin dictum sapien nec auctor congue. Nunc id erat sed velit facilisis tincidunt. In convallis eu diam id aliquam. Suspendisse eu nisl ante. Sed sit amet arcu non erat sagittis vehicula. Quisque pellentesque at lectus quis maximus. Nam mollis nulla interdum lobortis egestas. Fusce eu tellus eget libero pretium venenatis quis tristique justo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Proin tempor suscipit enim, eget lacinia augue malesuada sit amet. Ut ornare massa in magna pulvinar sagittis. Etiam non risus mi. Aenean aliquam dui et risus egestas aliquet. + +Aenean semper dui risus. Aenean consequat id elit a finibus. Sed vitae est sed arcu interdum maximus interdum in leo. Donec justo lorem, dictum sed placerat sit amet, eleifend in justo. Integer efficitur metus id interdum fringilla. Morbi et dui vitae libero consectetur fermentum quis sed quam. Sed interdum aliquam lorem, at blandit lectus fermentum a. Aliquam ac mollis felis, ut vulputate massa. Praesent convallis cursus eleifend. Donec non sem auctor, efficitur nisi ac, egestas libero. Nullam turpis lacus, dignissim eget pellentesque sed, fermentum ut ipsum. Vestibulum a posuere lacus, vitae rutrum neque. In hac habitasse platea dictumst. Sed vel maximus sem. Etiam dapibus risus et consectetur auctor. Phasellus vestibulum posuere sagittis. + +Aliquam nec libero at velit rhoncus pretium. Curabitur tristique blandit orci id vestibulum. Praesent in tempus arcu. Vivamus in felis tellus. Nunc ac fermentum massa. Cras nisi mi, sollicitudin eu maximus vitae, sodales gravida lorem. Vivamus mollis metus id lectus rhoncus consequat. In dui tellus, vulputate sit amet purus vel, volutpat ornare turpis. Fusce vitae massa non ligula lobortis rhoncus eget id sapien. Sed nec tempus lectus. Proin tempor risus ipsum, fermentum suscipit felis cursus sit amet. + +Maecenas ut dignissim ante, vitae ornare lorem. Fusce nec convallis eros, sed finibus urna. Proin ut finibus dolor. In non nunc sed dui aliquam suscipit. Etiam semper varius ex, sed venenatis sem gravida in. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus tempus aliquet placerat. Nam odio mauris, pharetra ac felis vel, ornare cursus nisl. Phasellus elit risus, finibus id ornare ut, scelerisque sed nisi. Curabitur aliquet, magna in finibus congue, dui libero auctor dui, ut fermentum metus enim vitae ex. Duis at elementum tellus. + +Suspendisse laoreet luctus sem sit amet tempor. Vestibulum non lorem fringilla, maximus nisl vel, pulvinar enim. Suspendisse egestas elit et sem sagittis rhoncus. Morbi nulla augue, semper euismod ultricies quis, maximus et lorem. Nulla nec posuere justo. Ut blandit nisl vitae turpis varius finibus. Donec porttitor eros neque, id mollis neque tempus et. Maecenas a massa placerat, laoreet nisl vel, venenatis diam. + +Phasellus at leo vel nisi aliquet placerat. Vestibulum luctus erat quis velit laoreet auctor. Aenean ultricies nulla tristique metus commodo, id fermentum justo tristique. Nullam ut tincidunt libero. Suspendisse volutpat, lacus ac congue ultricies, metus mi imperdiet magna, in maximus turpis ex eget leo. Sed lorem nibh, vestibulum id sodales ac, sagittis at elit. Curabitur purus nunc, sodales eget vehicula vitae, bibendum gravida diam. Nullam dignissim consequat pharetra. Nullam a diam consectetur, mollis odio sed, blandit lectus. Vestibulum eu velit id massa varius sagittis. Quisque tempor ante ac mauris rhoncus molestie. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. + +Ut sit amet euismod mi. Nulla facilisis est posuere, feugiat est et, dictum nulla. Proin eleifend ultricies nunc. Sed commodo justo nisi, id suscipit massa malesuada ut. Donec aliquam nibh tellus, vitae gravida lectus ultricies quis. Nam pulvinar lobortis erat sit amet convallis. Sed quis magna facilisis, tincidunt dui non, hendrerit nunc. Morbi egestas, risus fringilla fermentum porttitor, nunc velit viverra mi, non sodales augue arcu ac sapien. Duis blandit urna at nisl pellentesque semper. Nulla et malesuada nulla. Aenean tristique tortor odio, sit amet luctus odio aliquam id. Phasellus facilisis lorem vitae velit aliquam imperdiet. Cras faucibus dolor eget neque fringilla, ut mattis ex hendrerit. Integer molestie porttitor sagittis. + +Pellentesque diam quam, auctor eget tristique eget, molestie sit amet est. Pellentesque a eros non dui gravida volutpat. Donec molestie blandit nunc ac interdum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse lobortis, neque non aliquet convallis, lectus ex venenatis ex, quis malesuada massa erat non dolor. In tristique, enim eu ultrices ultricies, lectus ligula pretium orci, commodo cursus ante est vel odio. Sed quis accumsan purus. Nam fringilla ex ut urna vestibulum, et feugiat diam ultrices. Vivamus tempus felis ac quam blandit convallis. + +Vestibulum eros erat, volutpat in est at, blandit pharetra sapien. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aenean quis enim orci. Aliquam imperdiet vel arcu ac sagittis. Mauris vitae augue sed metus commodo ornare. Nulla malesuada tellus nisl, eu vestibulum ante mollis a. Sed sagittis euismod nunc, sit amet hendrerit tortor condimentum eu. + +Praesent lacinia massa eget mi auctor placerat. Fusce porttitor augue lectus, a cursus ante dictum vel. Vestibulum ultrices vel mauris in fermentum. Nunc tincidunt non magna sed pharetra. Donec porttitor rutrum arcu, vitae tincidunt lacus suscipit ac. Aliquam lorem mauris, pulvinar non dignissim sed, pulvinar vitae dui. Donec id neque eu velit imperdiet lacinia nec eu magna. Ut a purus sit amet nulla venenatis vulputate. Integer vulputate est sem, iaculis porttitor mi mattis et. Phasellus condimentum ipsum eget tellus viverra, a tincidunt nunc feugiat. Praesent posuere aliquam ex et faucibus. Nullam pretium felis id mauris luctus, a luctus eros sodales. + +Mauris et condimentum velit. Praesent id dignissim odio. Phasellus nisl velit, molestie sed nisi et, sollicitudin tempor nisi. Pellentesque lacus eros, ultricies non leo sit amet, porttitor ullamcorper ipsum. Vestibulum maximus lorem ac justo tempus imperdiet. Suspendisse rhoncus, mi in commodo tempus, orci turpis feugiat dui, nec facilisis arcu diam ut mauris. Vestibulum risus ligula, ornare non cursus vel, pellentesque non augue. Morbi eu gravida arcu. Nunc sed fermentum lacus. Nulla id quam aliquet, aliquet lacus in, rutrum metus. Duis tristique sodales risus vel interdum. Integer rhoncus nibh eget semper malesuada. Nunc sit amet ante diam. Fusce tincidunt aliquam ex, at lobortis tellus porttitor non. Vestibulum tincidunt iaculis dui vel scelerisque. + +Aliquam sagittis mauris eget massa accumsan viverra. Pellentesque luctus sit amet augue ac scelerisque. Praesent imperdiet nisi dolor, sed malesuada est commodo at. Aenean vel leo eget felis tincidunt interdum. Fusce orci mauris, egestas eget lectus et, finibus consectetur urna. Donec ut dapibus elit, eu lacinia neque. Ut et accumsan nulla. Sed ullamcorper ligula purus, eu dapibus nunc auctor vel. Ut convallis consectetur dapibus. Curabitur eget porttitor felis. Maecenas pretium ac leo vitae volutpat. Donec in augue sit amet lorem efficitur dignissim. + +Praesent iaculis tristique rutrum. Pellentesque id odio vel purus bibendum sodales suscipit id odio. Nullam ac velit imperdiet, imperdiet nisi sed, malesuada ipsum. Quisque varius dictum efficitur. Phasellus efficitur varius imperdiet. Aenean facilisis libero non augue porttitor, nec interdum felis imperdiet. Etiam et libero id elit commodo tincidunt. Nullam rutrum odio id rutrum tristique. Cras vehicula aliquet risus ac elementum. Duis nisl urna, commodo eget ante et, vehicula tempus lacus. + +Mauris eu sapien sed erat auctor volutpat vel vel tortor. Aenean in commodo felis. Donec a dui a urna varius aliquet quis at nisi. Pellentesque et urna lacinia, commodo arcu at, laoreet lectus. Aliquam sodales, massa in convallis aliquam, dui orci eleifend arcu, a gravida mauris magna sed arcu. Ut ac lectus in risus feugiat lobortis. Nulla quis est eget dui pharetra ultricies eget at risus. Phasellus sagittis molestie ligula, eget egestas orci volutpat vitae. + +Fusce nec finibus ligula, sed volutpat tortor. Sed placerat quam fringilla augue pharetra dictum. Proin ornare mi erat, eget sollicitudin ligula venenatis vitae. Aliquam semper sagittis urna rutrum pharetra. Vivamus lacinia mattis erat, vitae ultrices arcu. Maecenas id lacus eget justo imperdiet vehicula commodo a leo. Quisque vitae eros interdum, posuere ex ornare, tincidunt lectus. + +Vestibulum hendrerit sed libero et bibendum. Sed ornare eu massa ut vestibulum. Curabitur imperdiet odio felis, at ullamcorper eros rhoncus nec. Cras commodo nisl eu augue iaculis posuere. Aliquam massa tortor, consectetur quis dui in, mollis dictum tellus. Fusce porttitor dapibus arcu. Fusce finibus pretium porttitor. + +Proin dapibus viverra nisi. Cras ullamcorper purus et consequat fermentum. Duis imperdiet in dui in imperdiet. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam quis enim at ipsum ultricies auctor scelerisque nec nulla. Vivamus ut efficitur enim. Quisque dictum quam ac dui iaculis efficitur. Morbi at nulla convallis, condimentum tellus sit amet, dapibus nunc. Morbi metus felis, commodo sit amet justo id, finibus sagittis lorem. Nam nisi diam, fermentum dapibus varius in, convallis eu leo. Phasellus ut nunc orci. Sed tincidunt mauris in ante consequat, id bibendum libero volutpat. Aliquam a dictum libero. Etiam massa odio, congue ut lorem tincidunt, elementum egestas ligula. + +Ut semper arcu a lectus interdum euismod. Curabitur nec ultrices neque. In eget sapien nulla. Pellentesque pellentesque faucibus urna id placerat. Aenean condimentum posuere interdum. Etiam vel tristique lorem, in dapibus urna. Vestibulum facilisis lobortis metus ac egestas. Vestibulum ultrices aliquet dui id efficitur. Sed a velit sed erat ultrices sodales suscipit a tortor. Nam mattis rhoncus augue et viverra. Praesent volutpat gravida enim quis sodales. + +Nam placerat nisl a ullamcorper pharetra. Sed eu eros egestas, suscipit ante id, efficitur mi. Curabitur accumsan gravida pellentesque. Vestibulum urna risus, condimentum vel libero in, porta pharetra nisi. Duis eu feugiat neque, quis condimentum dolor. Suspendisse et elementum urna. Vivamus malesuada nisi eget blandit faucibus. Duis eu lorem ac est ultrices placerat nec nec elit. Nunc sed sagittis ligula. Vivamus gravida suscipit tellus nec euismod. + +Ut posuere porta diam, vitae euismod erat egestas vitae. Aenean imperdiet quis quam eget dictum. Cras vulputate elit eu nibh scelerisque, vitae consectetur nisi malesuada. Praesent iaculis, neque nec tempor elementum, est mi egestas urna, nec commodo neque lacus vel mi. In a orci eu metus elementum tincidunt nec id tortor. Aenean augue augue, vulputate a porta quis, bibendum finibus augue. Nam condimentum ante ac congue ultrices. Praesent eu nisi eu enim accumsan scelerisque et id augue. Cras gravida dictum suscipit. Nulla tristique tempor lacus non eleifend. Curabitur sodales est in arcu accumsan, vel dignissim nunc blandit. Aenean sodales sodales lectus volutpat commodo. Maecenas venenatis accumsan nibh, sit amet semper risus ultrices non. + +In blandit iaculis dolor sit amet convallis. Aliquam quis nisl sit amet augue semper vehicula. Sed aliquam vel ex vel condimentum. Nunc diam massa, mattis ac felis vel, cursus tincidunt ligula. Aliquam erat volutpat. Quisque faucibus in metus in tempus. Ut pharetra congue tellus. Vivamus est libero, fringilla vel elit ac, rhoncus fermentum arcu. Praesent tortor diam, mattis in varius commodo, lacinia accumsan neque. Integer nec luctus nibh. Duis tincidunt velit nisi, id porttitor turpis posuere in. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam finibus tortor lectus. Curabitur condimentum orci eget urna sollicitudin vehicula. + +Donec sagittis mi lacus, quis rutrum sapien scelerisque sed. In quis interdum velit. Nulla eget tincidunt enim. Fusce viverra, sem pharetra ultricies laoreet, magna erat ornare lectus, a viverra mauris magna id mi. Quisque vitae pretium velit. Integer venenatis vel sapien non varius. Praesent eros neque, posuere sit amet posuere ut, posuere a sem. Vestibulum porttitor interdum posuere. Nam viverra felis dolor, eget ultrices lacus tincidunt a. Suspendisse elementum rhoncus tristique. Nam vehicula, odio eu porta ullamcorper, neque nunc pretium neque, ac vehicula mauris eros ac turpis. Aliquam augue nisl, pharetra non mauris id, finibus egestas massa. + +Aliquam rhoncus tortor a nunc vulputate gravida. Phasellus aliquam lorem ipsum, a suscipit orci euismod ac. Curabitur fringilla orci in ante aliquam venenatis. Ut nec sollicitudin orci. Morbi consectetur massa nec lacus vestibulum commodo. Donec quis erat at nibh scelerisque interdum. Donec sed velit molestie purus volutpat tempus. Aenean consequat, massa vitae mollis eleifend, felis ante convallis ex, quis egestas libero nisi interdum dui. Maecenas aliquet nisi quis est dapibus posuere. + +Phasellus lectus ex, finibus non orci et, suscipit fermentum orci. Vestibulum sed ligula non arcu facilisis feugiat. Praesent pellentesque eros quis eleifend tempus. In hac habitasse platea dictumst. Nulla accumsan suscipit risus, nec dignissim purus sollicitudin quis. Vestibulum vestibulum ligula non massa congue commodo. Aliquam velit ante, facilisis et aliquet non, imperdiet nec velit. Nunc vel elit felis. + +Sed sed ex ut dui cursus consectetur. Phasellus laoreet velit lacinia dui placerat tincidunt. Nullam ornare sagittis quam ac pretium. Donec imperdiet velit quis ipsum placerat, vitae lacinia felis sagittis. Aenean vitae dui fermentum, laoreet lacus egestas, faucibus libero. Maecenas blandit blandit mi, et mattis lectus placerat sollicitudin. Aliquam at semper nulla. + +Sed scelerisque lacus felis, et commodo libero tincidunt ac. Ut vel elit vel ex luctus lacinia ut et nisi. Sed ac tristique nisl. Suspendisse efficitur varius purus, sit amet gravida orci sagittis lacinia. Proin non placerat urna. Duis vehicula faucibus est vitae vehicula. Praesent vehicula tempor eros, in aliquet nisl vehicula in. Phasellus in nibh commodo, tempor magna in, convallis metus. Vivamus velit risus, scelerisque quis dolor in, finibus rhoncus erat. Vivamus ipsum libero, tempus non magna eget, condimentum tempus elit. + +Sed eu feugiat neque. In velit ex, suscipit in semper blandit, malesuada in orci. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec sem odio, elementum at turpis in, aliquet posuere augue. Etiam accumsan libero lorem, tempor cursus purus fringilla in. Vestibulum id diam consectetur, interdum dui vitae, accumsan tellus. Ut eu viverra nisi. Duis odio nisl, consectetur id volutpat eu, interdum a tortor. In et ipsum interdum, fringilla urna nec, congue lectus. Aliquam eu sodales neque. Vivamus et tincidunt dolor. Sed porttitor rhoncus rutrum. Nulla facilisi. + +Vivamus dapibus ipsum vitae libero ullamcorper, quis ullamcorper tortor porttitor. Phasellus elementum sapien ac felis sagittis, non finibus massa faucibus. Curabitur id enim neque. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean velit lectus, tempus placerat pretium ultricies, mollis sit amet nibh. Praesent tincidunt turpis purus, vitae malesuada sapien eleifend at. Pellentesque velit mauris, volutpat auctor pharetra at, laoreet vel mi. Duis a ornare leo, nec malesuada ante. Donec a felis nec ex varius rutrum at a libero. + +Etiam blandit nulla et lorem viverra, vitae suscipit mi luctus. Etiam enim nisl, dignissim eget lectus a, molestie hendrerit leo. Cras placerat leo nec blandit aliquet. Suspendisse id cursus metus. Aliquam a lobortis lectus, eget consequat erat. Praesent congue nulla vitae convallis pulvinar. Donec sed dui tellus. Aenean vehicula neque malesuada mi malesuada, sed lobortis nisl porttitor. Sed eu felis lacinia, fringilla nibh ac, laoreet ex. Vestibulum nibh ex, sagittis eu bibendum et, laoreet ut lectus. Proin ac augue tellus. Nulla tristique metus ut sem egestas sodales. In lorem sapien, tempor sit amet semper a, dignissim a dolor. + +Mauris finibus justo ut pretium vestibulum. Morbi euismod faucibus fringilla. Curabitur vitae dictum ipsum. Curabitur nec nulla fringilla, laoreet ligula eu, convallis magna. Proin in accumsan sem. Morbi pretium venenatis sem, vitae fringilla leo vestibulum et. Maecenas justo ligula, iaculis a finibus nec, aliquam tempor ipsum. Donec cursus nisi vel purus pulvinar, non interdum nulla semper. In eu ullamcorper odio. Sed ac augue ut urna pulvinar rhoncus. Integer maximus ultrices nisl, nec volutpat tellus facilisis eu. Fusce dictum, leo iaculis egestas consectetur, enim ligula aliquam nunc, sed condimentum neque dui eget nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; + +Fusce vitae orci eu purus vehicula viverra. Vivamus mollis orci sed euismod sagittis. Duis dui sapien, ullamcorper in gravida nec, imperdiet sed purus. Cras ligula nulla, consectetur a urna a, luctus ultricies augue. Aliquam tincidunt, lectus eget auctor venenatis, elit tortor malesuada mauris, sed iaculis lectus libero et lectus. Aenean dictum imperdiet tortor, ac aliquet magna rhoncus sed. Mauris facilisis velit suscipit ligula tristique ullamcorper. Praesent leo mauris, rhoncus eu sodales a, lobortis nec nibh. + +Cras in libero felis. Donec luctus nunc id imperdiet consectetur. Nam ultrices suscipit mi, eu pretium urna luctus eget. Phasellus eu lacinia augue. Proin eu est condimentum ligula volutpat semper. Sed luctus, dolor quis bibendum venenatis, neque nibh condimentum felis, vitae cursus libero velit vitae lorem. Donec ultricies ullamcorper ipsum. Maecenas maximus accumsan blandit. + +Mauris aliquet, ex non facilisis tristique, nibh elit efficitur quam, et gravida sapien leo sed diam. Suspendisse malesuada odio vel lorem dignissim, eu accumsan ante egestas. Vivamus blandit erat sed fringilla euismod. Etiam nec mauris a sem finibus dapibus. Quisque hendrerit eros nec mattis ultricies. Vestibulum blandit nulla a eleifend sollicitudin. Fusce hendrerit, nunc ut cursus fermentum, arcu odio laoreet turpis, a tincidunt purus massa nec sem. Nam id tellus et eros vehicula fermentum. Nullam imperdiet rhoncus lectus, at vestibulum nunc semper luctus. Sed a massa sed urna posuere congue in sed augue. + +Nullam condimentum eget tortor in lobortis. Maecenas ac cursus tellus. Nunc mollis lorem risus, sed tincidunt sapien ullamcorper quis. In nec diam quis ligula euismod feugiat vitae eget dui. In pulvinar, arcu in molestie sodales, augue elit aliquam elit, vel dignissim quam mi maximus quam. Sed condimentum, nibh ut finibus faucibus, diam leo ultrices dolor, quis cursus nunc dolor non urna. Aliquam suscipit, magna vitae gravida porta, sem orci mattis arcu, nec fringilla dolor nunc in purus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nunc egestas cursus magna in ultrices. Maecenas quis laoreet ex, eu vehicula metus. Donec sed congue sem, in vulputate diam. Pellentesque molestie nulla ipsum, nec dignissim enim ultricies eget. Morbi vehicula odio ut justo tempus blandit. Sed nec condimentum elit. Morbi ut facilisis mauris. Aliquam luctus odio sed ante aliquam, eget venenatis risus luctus. + +Integer laoreet odio a tellus tincidunt auctor sed id dolor. Praesent quis velit quis nunc dignissim iaculis non non lectus. Praesent blandit ligula urna, semper molestie lectus dignissim sed. Suspendisse bibendum, leo sed placerat tincidunt, sapien dui molestie dui, elementum dignissim nisl nisi et nulla. Ut feugiat felis id malesuada hendrerit. Pellentesque ut nisi et ipsum laoreet tempus vel non eros. Cras ut ante mi. Fusce sed maximus lacus. Etiam hendrerit, odio in maximus tincidunt, felis dolor malesuada justo, quis porttitor odio ipsum vitae eros. Vestibulum risus ante, iaculis sodales accumsan eget, tempor quis neque. + +Vestibulum eget elit vestibulum, imperdiet ex ut, cursus metus. Proin at interdum leo. Vivamus a nisl tristique, varius nisl dignissim, auctor leo. Donec arcu felis, condimentum vel pharetra vitae, fringilla at dolor. Integer elementum viverra tortor, a ullamcorper nunc bibendum in. Vivamus et arcu sit amet nulla maximus condimentum. Vestibulum in nisi ut nulla sollicitudin gravida. Aliquam nulla ipsum, venenatis eu fermentum id, sodales vel diam. Suspendisse metus mi, facilisis ornare est et, interdum pretium odio. Morbi eget nunc orci. + +Mauris neque dolor, imperdiet non dolor ut, suscipit lacinia mi. Donec dolor mauris, viverra in purus aliquet, tincidunt volutpat mi. Proin at dapibus dolor, vel egestas eros. Nulla mattis dictum iaculis. In pulvinar dui sem, eu tincidunt ligula sodales eget. Proin consectetur augue a libero suscipit rutrum blandit id eros. Pellentesque lorem erat, porta at felis id, congue malesuada urna. Quisque fringilla ut odio sed porta. Quisque congue lorem nec augue luctus varius. Nullam nec metus fermentum lacus egestas pharetra a volutpat lectus. Fusce euismod eros sit amet nisi semper imperdiet. Donec a viverra libero, vel ultrices felis. Aliquam vitae ante quis elit posuere ultricies. Mauris velit purus, tincidunt sit amet velit sit amet, sollicitudin pharetra odio. + +Donec semper eleifend aliquet. Vestibulum fringilla augue non arcu tristique pellentesque. Duis viverra, eros vitae dignissim lobortis, mauris lorem ultricies tellus, non cursus diam tellus vitae ipsum. Ut et arcu turpis. Fusce eget neque cursus, posuere augue interdum, fringilla libero. Donec commodo velit finibus urna pellentesque blandit at eu turpis. Proin et viverra tellus, a pharetra sapien. Ut a odio fringilla, viverra elit in, dictum tortor. Morbi est diam, sagittis sed pulvinar sit amet, dictum at lorem. Phasellus a condimentum massa, sit amet vestibulum purus. Suspendisse quis pharetra tortor. Nunc tempus magna vitae ligula luctus laoreet. Integer eleifend varius commodo. In hac habitasse platea dictumst. Cras eget metus sapien. Nulla facilisi. + +Cras euismod mauris tortor, a dapibus ligula gravida fermentum. Duis ultricies fermentum faucibus. Sed interdum, lacus vel mollis tempus, enim tellus ultrices nisi, in sollicitudin enim purus non nulla. Sed eget quam massa. In hac habitasse platea dictumst. Aenean at ante metus. Sed eleifend luctus ipsum nec lacinia. Vestibulum facilisis sodales dui, nec molestie neque tempus in. Curabitur consectetur tortor eget ipsum eleifend varius. Aenean finibus nulla at velit luctus, sed finibus ipsum semper. Vivamus turpis nisi, vulputate in pellentesque ultrices, rhoncus id augue. Quisque efficitur semper ligula, sed dictum turpis porta vitae. Aliquam malesuada est ac leo fermentum, et porttitor erat sagittis. + +Morbi felis odio, tristique quis tempor at, convallis commodo lectus. Integer tincidunt lacus dolor, id molestie ante luctus non. Fusce nec quam in quam euismod malesuada. In consectetur magna ut fermentum volutpat. Phasellus malesuada risus nunc, non pellentesque mauris aliquet quis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent eget mi metus. Nunc in risus eget lacus gravida tristique a in nisi. Cras consequat aliquam quam vitae pulvinar. Curabitur commodo purus ligula, et ornare ipsum aliquet at. Sed tempor sed enim ut convallis. Mauris cursus magna non diam dapibus euismod. Nullam ac nisl est. Maecenas aliquet quam erat, ac imperdiet elit fermentum at. + +Sed urna arcu, convallis et malesuada sit amet, iaculis quis felis. Fusce pellentesque tincidunt lacus, quis aliquam enim dictum vitae. Suspendisse potenti. Donec ut tincidunt est, eget iaculis leo. Curabitur auctor pharetra augue, sed egestas ante varius id. Etiam sollicitudin et mauris vitae ullamcorper. Maecenas mollis vulputate viverra. Etiam efficitur, metus quis cursus elementum, felis arcu congue dui, et volutpat augue tellus a dolor. Duis rhoncus molestie tincidunt. Nunc finibus tortor ut nunc vehicula, ac vestibulum velit tristique. Donec in eros ut erat tempor tincidunt. + +Pellentesque cursus leo non nisl posuere, ac tincidunt lorem tempus. Praesent ut erat dictum, tincidunt elit ut, varius risus. Sed hendrerit id elit ut vestibulum. Suspendisse consequat metus sit amet neque dictum, sed feugiat risus egestas. Aliquam lobortis nisl elit, eget posuere ligula aliquam eget. Nullam lobortis a nunc vel malesuada. Praesent venenatis nisl sit amet libero suscipit, ut placerat sapien egestas. Cras condimentum justo sit amet massa sollicitudin, ac ultricies metus dignissim. Morbi mauris nunc, varius a ornare sit amet, pretium ut ex. Etiam sollicitudin, risus ut viverra euismod, magna mauris mattis tortor, eget cursus massa odio eu ipsum. Mauris tempus nunc mattis lectus varius cursus. Curabitur nisi erat, vulputate rutrum scelerisque vitae, convallis non lorem. Suspendisse purus nulla, aliquet eget hendrerit dignissim, malesuada nec orci. + +In sagittis elit id augue iaculis euismod. Maecenas consequat odio sit amet massa elementum, eget fermentum velit varius. Aliquam ac tellus ac ex ullamcorper tincidunt eget eget diam. Quisque diam tortor, vehicula ac sollicitudin vitae, sollicitudin efficitur ligula. Nullam ut rutrum quam. Phasellus ornare posuere felis, sed vehicula ipsum blandit quis. Etiam a purus eu tortor interdum rutrum. Quisque sed tincidunt magna. Morbi sodales mi vitae sem cursus, sed venenatis augue porttitor. Nam posuere enim dictum hendrerit bibendum. Ut facilisis, dolor sed vestibulum ornare, tellus elit suscipit leo, et euismod arcu neque at tortor. Suspendisse pulvinar neque vel porttitor vestibulum. + +Suspendisse in metus ut nibh euismod sodales. Sed tempor eget dolor at semper. Suspendisse at urna lacus. Donec quis velit sed elit ultricies vestibulum quis nec ipsum. Duis at augue et turpis gravida rhoncus quis in est. Fusce sit amet malesuada quam. Integer nec augue non nisl consequat scelerisque eu et velit. Sed vitae enim felis. + +In a sem accumsan, iaculis nulla vitae, ultricies turpis. Nulla luctus, ligula a gravida dapibus, mauris mauris rutrum erat, et lobortis libero libero sed nibh. In quam diam, dapibus vitae diam in, interdum accumsan ligula. Phasellus ac diam mollis, laoreet sapien ut, vehicula quam. Donec cursus elit tortor, vel mattis odio ornare ut. Quisque et justo a purus aliquam laoreet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla non euismod metus. Integer venenatis eu nisl tempus consectetur. + +Phasellus dictum elit vel velit rhoncus, porttitor tempor mauris scelerisque. Quisque nec fringilla erat. Sed consectetur in eros ac maximus. In nec lorem sapien. Pellentesque aliquet bibendum mi, at pulvinar justo mattis nec. Proin justo lorem, tempus nec elit lobortis, interdum pretium nisl. Pellentesque euismod, massa a consectetur dignissim, risus purus dictum risus, in molestie dolor elit in turpis. Cras vitae dapibus augue. + +Proin enim diam, semper ac dapibus eget, vulputate id ligula. Proin lectus diam, pharetra sed turpis non, varius pharetra eros. Quisque eget rhoncus enim. Integer velit ante, molestie eget convallis vitae, laoreet eget massa. Etiam at sem nec urna accumsan convallis. Nam a diam luctus, scelerisque nisi id, pulvinar quam. Aliquam convallis maximus aliquet. Aliquam at diam nec tellus pretium euismod. Cras aliquam justo nec quam scelerisque vulputate. Etiam dictum eleifend elit elementum consequat. Donec semper tempus ultrices. Pellentesque bibendum vitae dolor vel scelerisque. Aenean lacinia hendrerit dolor non congue. + +Ut congue orci turpis, sit amet ultricies orci luctus in. Ut felis odio, vestibulum non convallis sit amet, congue vitae mauris. Nullam blandit enim vel lorem laoreet, at gravida est sollicitudin. Aenean posuere dignissim ex, id varius arcu iaculis id. Vestibulum id nulla eget magna pulvinar rutrum. Suspendisse pulvinar blandit mauris, vel pharetra turpis finibus a. Quisque ac ligula arcu. Praesent semper nulla sed ultrices scelerisque. Quisque id erat eget odio dictum euismod. Donec sit amet nunc purus. Quisque nulla dui, sollicitudin non odio sit amet, sagittis interdum urna. Nunc feugiat, lacus non commodo volutpat, tellus lorem fermentum risus, eget dapibus urna massa a elit. + +Sed id tellus augue. Donec quis fringilla lacus. Integer suscipit faucibus eleifend. Donec lobortis odio ut felis cursus rutrum. Morbi augue erat, rutrum eu nisl sed, tincidunt porta enim. Nulla consequat malesuada tellus. Pellentesque facilisis vel nibh et pretium. Morbi volutpat ante sed leo tincidunt, egestas bibendum dui auctor. Morbi mattis feugiat maximus. Donec a sagittis ante, non euismod metus. Morbi commodo neque viverra pretium fermentum. + +In sodales, nisl quis vulputate luctus, sapien est fringilla elit, sed vestibulum urna libero ac ante. Suspendisse potenti. Duis eget sagittis elit. Mauris sapien ligula, egestas at auctor eu, efficitur at nisi. Proin elementum, erat nec tincidunt laoreet, elit risus pellentesque sapien, in malesuada enim ligula id magna. Proin scelerisque augue lorem, et hendrerit ante fringilla vel. Quisque in faucibus nunc, sit amet convallis diam. Sed fermentum tristique fringilla. In condimentum purus ornare tristique dapibus. In malesuada nunc lorem, vel imperdiet erat pellentesque id. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Quisque feugiat faucibus nulla, quis faucibus velit lobortis in. Donec augue sem, scelerisque vitae tortor ac, aliquet fermentum nulla. Fusce convallis non metus in ultrices. Cras justo arcu, tristique vel libero sed, fermentum ullamcorper justo. Mauris libero erat, elementum nec malesuada ac, commodo eget ante. + +Duis laoreet diam non orci volutpat rhoncus. Sed bibendum dolor quam, eget sagittis enim tincidunt at. Mauris at varius sem, id luctus augue. Sed venenatis pulvinar viverra. Curabitur enim nisi, mollis at fermentum ac, rhoncus iaculis mi. Ut dictum urna velit, a rhoncus risus tempus ut. Cras tristique scelerisque dignissim. Donec ex felis, dictum at eleifend at, posuere bibendum quam. Donec luctus aliquet velit, id fringilla sem tincidunt sed. Quisque cursus imperdiet diam, ut facilisis augue convallis et. Aliquam hendrerit consectetur neque, vitae ultricies nulla aliquam ut. Donec at justo ut ipsum aliquam bibendum in id ante. Aenean fermentum eros vel turpis tristique egestas. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent vitae dui felis. + +Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam faucibus leo id mi blandit, posuere maximus felis malesuada. Phasellus et porttitor magna. Curabitur leo ipsum, malesuada at lobortis finibus, facilisis id purus. Suspendisse potenti. Suspendisse at iaculis metus. Nam pharetra leo quis ex aliquam fermentum. Sed quis metus faucibus, varius nunc id, condimentum est. Nam lacinia quis velit a iaculis. Nullam accumsan mattis neque vitae posuere. Vivamus sem neque, ultrices sed molestie at, gravida ut est. Nunc a tellus viverra felis pulvinar fermentum vitae nec mi. Nulla et hendrerit magna, sed bibendum mauris. Cras eget diam eu augue convallis porttitor eget sit amet tortor. Cras arcu tortor, vulputate vitae erat non, rutrum rhoncus urna. Donec blandit non erat sit amet gravida. + +Sed feugiat in nibh et sagittis. Quisque in maximus mi, eu elementum neque. In hac habitasse platea dictumst. Pellentesque ultricies consectetur urna vitae imperdiet. Nullam velit lectus, laoreet ut sem eu, commodo fringilla ipsum. Vivamus placerat vulputate ipsum nec viverra. Aenean vel venenatis augue, vitae pharetra felis. + +Pellentesque rutrum urna orci, a condimentum mi ultrices quis. Nunc facilisis velit nec velit eleifend vestibulum et vel erat. Fusce consequat ex ut lacus elementum lacinia. Nulla a sapien ut ex dignissim pulvinar sed vel ex. Aenean porta diam sit amet pellentesque dignissim. Vestibulum mollis convallis auctor. Etiam lacinia eros non nulla blandit tristique. In hac habitasse platea dictumst. Vestibulum dapibus iaculis consectetur. Morbi ex odio, posuere at sollicitudin mattis, efficitur pharetra sapien. Etiam placerat nec quam vitae fringilla. Donec sodales bibendum odio, eget pharetra erat efficitur id. Nullam ultricies dui odio, sit amet tincidunt eros vestibulum eu. Donec semper libero in lacus elementum maximus. + +Curabitur commodo ex nec sapien fermentum suscipit. Donec vel erat placerat, convallis dui non, mattis mauris. Donec placerat dui augue, et dignissim justo feugiat id. Phasellus nec justo ex. Phasellus eget lobortis orci. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cras in scelerisque neque, non pretium purus. Aenean rutrum libero at fermentum pharetra. Nam elementum felis nec dapibus dignissim. Quisque ultricies ipsum a odio pellentesque mollis a maximus leo. + +Sed id urna hendrerit, convallis nisl quis, tristique felis. Sed eget consequat nisl. In vel tincidunt erat. Vivamus molestie rhoncus libero, non tincidunt lacus elementum non. Aenean faucibus lacinia nisi ac lobortis. Suspendisse iaculis augue nibh, id varius tortor ornare ac. Duis vitae congue nibh. Praesent at tortor et justo aliquam fringilla vitae id orci. Mauris ultricies velit condimentum lectus volutpat, vitae tincidunt odio fermentum. Curabitur luctus convallis libero eget pellentesque. Vivamus a tempus ipsum. + +In et justo vel nisi convallis tempor ac vitae purus. Cras efficitur ac orci sit amet aliquet. Vestibulum vel libero egestas, pharetra nisl a, ultrices erat. Nunc id turpis a erat aliquet hendrerit. Suspendisse lobortis est sed quam consectetur condimentum. Proin fermentum purus nec risus dignissim lacinia. Quisque eu libero eu nibh dictum congue id at odio. Nulla posuere justo a mollis scelerisque. Nunc luctus, augue congue volutpat tincidunt, orci nulla euismod elit, eget mollis arcu augue eget elit. Aliquam rhoncus nisl at quam dictum viverra. + +Pellentesque sit amet elit condimentum, suscipit sapien sed, dapibus turpis. Proin felis nunc, aliquet in vulputate a, lobortis et ex. Donec ac magna vulputate, tincidunt mauris eget, ultrices urna. Duis venenatis commodo massa, eget rutrum enim consectetur vel. Aenean vel erat hendrerit, tincidunt dui ut, elementum est. Vestibulum in ipsum fringilla, gravida nunc ac, sagittis dolor. Aenean pulvinar ornare diam eget ultricies. + +Cras ut luctus mauris, sed sodales orci. Quisque vitae ullamcorper metus. Ut vel justo ligula. Aenean sit amet tellus tortor. Mauris eu diam et mauris vestibulum vehicula. Donec finibus, turpis vel blandit pretium, sem quam sagittis purus, in sagittis nibh leo id augue. Duis venenatis mollis pretium. Praesent pretium bibendum eros. In non ipsum cursus, tristique orci vel, elementum dolor. Nulla egestas leo in feugiat dignissim. Integer fringilla odio ut aliquam accumsan. + +Sed risus est, tristique ut ex quis, aliquam malesuada lorem. Maecenas hendrerit eros ultricies venenatis aliquam. Vestibulum ut laoreet lectus. Integer purus neque, porttitor sed tristique congue, vestibulum et ligula. Aliquam fringilla, eros et mattis vulputate, tellus urna auctor velit, ac pharetra ligula mauris ut tortor. Donec tristique nunc metus, vitae vulputate nulla iaculis vitae. Donec iaculis dapibus dolor, eget rhoncus dui. Ut feugiat sed enim tristique efficitur. Curabitur leo risus, vehicula ac ligula id, vestibulum eleifend diam. Aliquam erat volutpat. In ipsum diam, volutpat at diam non, finibus lobortis eros. Curabitur id diam mi. Proin purus urna, auctor et diam nec, aliquam interdum lectus. Ut eget mollis tortor. Quisque elementum porta ultrices. + +Nullam aliquet augue velit, sed suscipit erat eleifend non. Vivamus nisl felis, blandit sit amet neque id, malesuada tincidunt mi. Phasellus a mauris metus. Cras tempor, arcu tincidunt fermentum viverra, tortor lacus tincidunt erat, vel tristique dolor justo vitae eros. Aliquam erat volutpat. Cras aliquet nunc et dignissim sagittis. Fusce vel nisi mi. + +Nunc tempus purus non magna tincidunt, eget dignissim justo posuere. Aenean mattis lacinia risus vel luctus. Suspendisse ac rhoncus massa, id finibus dui. Pellentesque nulla turpis, iaculis vitae mauris non, hendrerit tempus erat. Integer venenatis, dui in rutrum porttitor, purus risus commodo nisl, a fermentum nisi nunc eu neque. Quisque euismod est nec mi facilisis, ut varius leo congue. Integer sed arcu ultrices, volutpat diam at, elementum turpis. Quisque et accumsan orci. Duis consequat sollicitudin tortor in ultrices. Vestibulum porta fringilla auctor. Maecenas maximus eros at erat vestibulum mattis. Praesent fringilla pellentesque quam, vel ullamcorper nisi sollicitudin non. Curabitur fermentum fermentum ligula sed viverra. + +In hac habitasse platea dictumst. Quisque et convallis est, quis posuere felis. In congue, elit nec venenatis hendrerit, eros sapien dictum erat, non vehicula nibh felis ac sem. Nam sed semper massa. Proin id accumsan lorem. Mauris ultrices leo et velit euismod facilisis. Nulla facilisi. Morbi ultrices, mauris id ullamcorper sodales, ex neque eleifend tellus, sed luctus neque orci a dui. Curabitur ut eros metus. Morbi rhoncus odio eget lacinia blandit. Aenean lobortis consequat imperdiet. Proin tempus vehicula massa, nec posuere ex. Phasellus convallis, felis ac lacinia luctus, purus nunc imperdiet ante, eu hendrerit nibh diam eu lacus. + +Vivamus ullamcorper molestie turpis, et euismod lorem semper ac. Proin ornare, purus at ullamcorper euismod, lacus odio gravida nisl, vitae pretium mi erat a sapien. Duis ultrices libero turpis, sit amet varius sapien tempus in. Integer eget dignissim est. Aenean eget nulla nec libero faucibus tempus. Etiam in pellentesque risus. Nunc sed luctus lacus. Duis tristique nulla non enim consequat, congue vestibulum nisl interdum. Proin faucibus, eros non accumsan rutrum, ipsum justo fermentum augue, tempor ornare est metus sit amet tellus. Duis malesuada vel justo at finibus. Nullam sit amet enim scelerisque, dapibus velit ut, iaculis lectus. Nunc elementum erat nibh, eget finibus dolor porta in. Fusce varius tellus mattis tellus commodo pellentesque. Cras viverra gravida ligula, quis hendrerit ex posuere vitae. Sed quis tempor felis, ultrices faucibus velit. + +Quisque porttitor mi vitae metus dapibus, eu tincidunt turpis pharetra. Fusce dolor nulla, vulputate a ligula sed, suscipit hendrerit sem. Integer id nunc vitae erat dictum tempor. Morbi leo dui, tincidunt sed dapibus non, vestibulum sit amet magna. Proin et mauris tellus. Nam volutpat orci eu eros imperdiet congue. Suspendisse nec dictum magna. Nunc consectetur varius augue a ultricies. Proin nec lacus eget massa mattis ornare eget id ligula. Sed laoreet ante nec efficitur lacinia. + +Integer maximus laoreet tellus, eget aliquam mauris luctus ut. Sed condimentum lectus et mi eleifend egestas. Integer convallis tempus sem laoreet consequat. Suspendisse gravida commodo purus eu consequat. Pellentesque sed mi efficitur, tristique felis et, tempus lacus. Nullam in dictum est. Aenean libero libero, ullamcorper a cursus eget, mattis vitae diam. Aliquam at dolor turpis. + +Ut mauris justo, accumsan molestie eros vitae, interdum ornare lorem. Mauris ut consectetur nulla, cursus vulputate lectus. Donec commodo urna velit, nec lacinia elit accumsan sit amet. In et augue vel leo rutrum vulputate at vel magna. Phasellus in tempus erat, quis elementum mauris. Vestibulum tincidunt facilisis ante, ut volutpat eros rhoncus vitae. Aliquam erat volutpat. Proin sit amet lacus turpis. Etiam maximus sodales libero, ut vestibulum dui lacinia sit amet. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec scelerisque, turpis et auctor dictum, dolor odio sodales ligula, a convallis dolor erat a augue. Sed felis nisi, tristique sit amet dolor sit amet, imperdiet auctor mauris. Aenean vitae ex molestie, suscipit lorem fermentum, laoreet nisi. + +Suspendisse pellentesque facilisis erat, sed faucibus sapien facilisis et. Proin pharetra augue ut lorem viverra pulvinar. Fusce quam neque, egestas sed est non, rutrum porta justo. Duis fermentum tincidunt ipsum non pharetra. Suspendisse potenti. Ut volutpat magna quis erat vehicula posuere. Aliquam consequat consequat gravida. Etiam dictum gravida semper. In vitae maximus felis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. + +Aenean pretium, nisl ac dapibus lobortis, sem justo fermentum magna, in ultricies ipsum felis vitae ex. Pellentesque dictum eget purus sit amet commodo. Nunc dapibus, leo eget bibendum luctus, leo nibh eleifend lectus, eget imperdiet felis sem id nunc. Nullam egestas justo commodo, blandit ante at, placerat dolor. Nunc malesuada nulla et nisi maximus, quis molestie diam blandit. Quisque elementum lacus purus, quis varius lectus sodales finibus. Donec sodales nunc non nunc tincidunt, eu rutrum arcu pretium. Etiam risus odio, consequat eget quam eu, dignissim iaculis erat. Aliquam eu porttitor urna. Donec molestie, diam quis tempus maximus, leo mauris pretium mauris, ut lobortis est ipsum a mauris. Nam dignissim congue leo, id mattis sem efficitur eget. Proin risus lorem, fringilla vel varius ac, hendrerit vel neque. Vestibulum auctor est fermentum, congue purus at, semper dolor. Maecenas nec nunc at dolor blandit suscipit. Donec eu nisi aliquam, eleifend sem a, ultrices ex. + +In at augue risus. Nam sed justo in quam porta maximus. Praesent elementum tellus sed dui gravida hendrerit. Cras ultricies pulvinar lobortis. Maecenas tortor augue, auctor ut eleifend eget, egestas at lectus. Aliquam erat volutpat. In in egestas tellus. + +Quisque volutpat placerat vulputate. Nulla aliquam consectetur ex a vulputate. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla volutpat leo elit, id blandit erat vehicula eget. Pellentesque molestie, lacus ac facilisis fermentum, turpis enim faucibus felis, sit amet rhoncus libero dui at mauris. Proin vel placerat risus. Nullam eleifend orci eget eros tempor, aliquet semper diam malesuada. + +In convallis gravida laoreet. Praesent scelerisque mollis massa, non lobortis sapien elementum id. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Etiam dignissim varius eros vitae molestie. Aenean molestie nisl ac bibendum laoreet. Sed placerat pellentesque augue, sit amet commodo turpis condimentum vitae. Duis a purus placerat, tempor nulla ac, rutrum ipsum. Donec lectus nisi, laoreet quis diam eget, finibus scelerisque ante. Aliquam erat volutpat. Integer convallis justo vel nibh vehicula molestie. Donec ut mi eget est pulvinar euismod. In id ligula ut enim venenatis fermentum. In metus leo, rutrum ac mollis tincidunt, semper quis lacus. Sed lobortis, augue in condimentum suscipit, augue velit tempor leo, eu mollis turpis lectus auctor orci. + +Nullam efficitur erat ut dolor tristique condimentum. Suspendisse pretium ex ut bibendum ullamcorper. Phasellus dapibus enim sed tellus condimentum, sit amet mattis tellus cursus. Phasellus venenatis augue in lacus suscipit, vel hendrerit felis vehicula. Cras consectetur cursus lorem vulputate cursus. Vestibulum nec auctor sem. Nam ultricies, mi a ullamcorper eleifend, enim magna elementum erat, a accumsan turpis nunc et tortor. Donec venenatis maximus lacus vitae sodales. Donec dapibus rutrum porta. Nulla facilisi. Vivamus aliquam ex vitae sem consequat blandit. Nullam ultricies, ante ac rutrum efficitur, magna ligula maximus turpis, ut porta est velit et magna. Vestibulum mauris massa, posuere nec convallis in, maximus a nulla. Mauris vitae lorem sed nulla aliquam tempor. Fusce vel fringilla metus, ac aliquet metus. + +Ut ac mattis augue. Fusce cursus at quam ut vehicula. In laoreet cursus urna eu fermentum. Suspendisse est mauris, gravida interdum urna a, ullamcorper hendrerit arcu. Donec dapibus blandit massa nec vulputate. Nam a lacus pretium, imperdiet risus ac, aliquam nunc. Nulla facilisi. Vivamus quam libero, vestibulum sed tellus eget, ornare gravida dolor. Sed placerat nulla in velit imperdiet mattis. Sed at arcu eleifend, scelerisque urna non, porta massa. In volutpat commodo quam et sollicitudin. + +Donec in magna ullamcorper, auctor ex malesuada, tincidunt dui. Etiam enim risus, cursus sit amet ante ut, blandit tincidunt purus. Etiam rutrum dolor nulla, vel feugiat quam convallis sit amet. In finibus mi tortor, non hendrerit purus dictum at. Vivamus condimentum elementum neque et maximus. Cras auctor iaculis metus, at vulputate justo rhoncus eu. Aliquam laoreet mi euismod, lacinia est nec, euismod augue. In viverra tincidunt dolor vitae porttitor. Proin congue, mi eu laoreet congue, libero nunc porttitor tellus, non dictum magna erat sed purus. In nec luctus ligula, vel gravida urna. Sed lacinia mollis justo at hendrerit. Pellentesque gravida laoreet risus non auctor. Praesent ac sollicitudin eros. Vestibulum non viverra magna, sodales tristique ipsum. + +Etiam et lacinia eros, ut scelerisque turpis. Sed elit tortor, varius in nibh at, tempor euismod massa. Sed dapibus purus nec felis venenatis, nec rhoncus eros sagittis. Nullam elit orci, facilisis nec nunc sed, lobortis sagittis metus. Proin aliquam pharetra sagittis. Etiam ultrices nulla quis posuere elementum. Sed ultrices justo justo, eu tempor turpis rutrum vel. Pellentesque at cursus tellus. Vestibulum pulvinar tellus eget felis posuere bibendum. Etiam nec orci eleifend, gravida ipsum mollis, facilisis erat. In efficitur ac metus vel aliquam. Quisque arcu est, malesuada ut ligula ac, consectetur rutrum ex. Quisque varius viverra gravida. Suspendisse id leo quis felis imperdiet fringilla. Aenean ac accumsan urna. Fusce sapien mauris, varius pellentesque porta lobortis, tempus scelerisque metus. + +Vivamus tempus interdum felis, quis gravida ipsum auctor at. Cras sed ligula eu mauris semper pellentesque. Donec ut nibh at odio tempus euismod. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aliquam ultrices odio ut faucibus mollis. Curabitur tincidunt accumsan luctus. Suspendisse tincidunt magna mi, et euismod nulla feugiat quis. Fusce finibus ac velit id fermentum. Aliquam venenatis egestas aliquam. Nulla diam libero, consectetur eu enim ac, dictum tempor lectus. Ut id tempus augue. Nulla facilisis, massa sit amet ultricies ultrices, eros lacus eleifend ex, vitae facilisis velit urna non leo. Ut libero ligula, venenatis ac odio id, posuere hendrerit eros. Cras eget sapien at mauris iaculis tincidunt. Nullam ut neque nisi. Aliquam hendrerit, magna non pellentesque iaculis, nulla libero molestie augue, non vehicula tortor sapien porttitor eros. + +Sed eget lectus nec enim porta gravida. Vestibulum id tincidunt nunc. Quisque scelerisque condimentum ipsum, eu accumsan orci facilisis non. Duis venenatis, est et mattis finibus, turpis urna rhoncus urna, gravida ultrices ipsum neque vitae erat. Vivamus massa enim, tristique vulputate faucibus a, luctus non dui. Vestibulum non risus lorem. Suspendisse eu orci accumsan, molestie enim nec, convallis augue. + +Nullam sed arcu turpis. Quisque ut elementum velit. Maecenas vitae sem vel eros vehicula hendrerit. Aenean suscipit convallis justo. Morbi in consectetur diam. Donec et efficitur justo, vestibulum convallis turpis. Proin sit amet enim id enim tempor tempus. Nam est augue, consectetur vitae ligula ut, tristique consequat nibh. + +Ut pharetra tortor auctor risus posuere, nec hendrerit nibh rhoncus. Aliquam tincidunt aliquam felis, at dignissim justo fermentum quis. Aenean malesuada, eros a ornare varius, sapien ex mollis nunc, vitae bibendum augue lectus vel mi. Nulla ultricies dui sed tellus sodales, at iaculis urna elementum. Sed et lacinia urna. Integer commodo nulla non quam rhoncus, bibendum tristique massa finibus. In posuere, lectus id tristique tristique, risus mauris consectetur erat, fringilla mattis metus eros in velit. Praesent est eros, tempor vel vulputate ut, luctus ut libero. Phasellus a urna porttitor, aliquam enim non, varius ipsum. Cras non ante ut ante egestas condimentum. Praesent finibus eleifend eros, tempor feugiat neque semper eget. Duis ullamcorper condimentum aliquet. Interdum et malesuada fames ac ante ipsum primis in faucibus. + +Curabitur eget orci aliquet, pulvinar enim quis, hendrerit lectus. Suspendisse sed dictum lorem, nec porttitor ligula. Aliquam sit amet ligula sed lorem consectetur rhoncus ut a mauris. Quisque a ipsum sit amet augue mollis lobortis id nec risus. Phasellus vulputate justo in eros sodales vulputate. Fusce leo magna, condimentum quis vehicula id, malesuada at justo. Donec fringilla tortor in ullamcorper viverra. Vestibulum efficitur, quam ac consectetur dignissim, metus libero tincidunt libero, quis ultrices nisi mi eu erat. Nulla pharetra iaculis ullamcorper. Donec sit amet tortor congue risus elementum venenatis. Maecenas nisl nunc, imperdiet nec mattis sit amet, dignissim eget sapien. Nullam tristique turpis eu ante tempor, nec suscipit tortor sodales. Quisque cursus a orci quis molestie. Sed sit amet venenatis leo. Maecenas felis lacus, accumsan at accumsan non, ornare quis dolor. + +Aliquam vel enim eros. Curabitur sit amet risus ligula. Aliquam eget iaculis lacus, vitae efficitur nisl. Vestibulum convallis, risus at lacinia tempus, libero tortor rhoncus augue, id fringilla ipsum massa eget lorem. Maecenas justo leo, dignissim vitae finibus a, vehicula quis eros. Vivamus vel nisl porttitor, aliquam sapien et, semper risus. Praesent at sem tellus. Fusce sit amet fringilla elit. Nullam lorem sapien, vulputate eget lacus interdum, fermentum rutrum neque. Nulla scelerisque massa a felis cursus euismod. + +Mauris a mi posuere, eleifend velit in, luctus nisi. Donec mattis lacus velit, non laoreet odio posuere ac. Nulla efficitur fringilla orci a porta. Vestibulum est magna, fermentum id tempor eget, volutpat vel magna. Nunc non aliquam urna, ut congue urna. Aliquam purus nunc, pretium eget vehicula sed, vehicula sed sem. Quisque pellentesque velit sit amet orci dapibus tristique. Cras fringilla velit id ultrices scelerisque. Praesent porta egestas mauris, vitae accumsan quam. Aliquam molestie, magna sit amet maximus feugiat, arcu mauris ultricies lorem, id aliquam turpis arcu ac mauris. Etiam eu scelerisque neque. Donec sed quam vel est dictum convallis quis posuere tortor. Sed sit amet tortor eros. Sed odio purus, egestas at lacinia sed, consectetur id diam. Quisque tincidunt, ante eget mattis cursus, felis ante venenatis leo, in eleifend erat diam eu eros. + +Sed ac nunc mauris. Sed hendrerit ligula efficitur facilisis tincidunt. Morbi ut ornare lorem, sed facilisis ex. Aenean aliquam tristique mi, ac tristique metus rutrum eget. Curabitur id leo id massa commodo sagittis in at elit. Morbi viverra bibendum ligula vitae tristique. Etiam at est interdum, euismod nibh nec, condimentum arcu. Etiam orci ex, sagittis et tincidunt quis, finibus eu sapien. Sed urna lorem, suscipit a gravida vitae, sollicitudin vitae dolor. Cras ut imperdiet massa. Fusce ornare iaculis ipsum a cursus. Pellentesque vulputate, justo vel tincidunt egestas, lacus odio convallis odio, eu porttitor felis ipsum vitae libero. + +Phasellus eget nulla eget est convallis ornare ac non enim. Integer tincidunt massa eu tincidunt euismod. Proin at nulla in dui malesuada venenatis vitae at ligula. Curabitur dapibus mauris vitae turpis euismod, et pharetra quam molestie. Nulla id faucibus tortor. Pellentesque ultrices, turpis vel lobortis fermentum, sapien diam rhoncus sapien, quis tristique turpis lorem a mi. Nam bibendum, sem eget congue interdum, lacus orci convallis elit, ac porttitor lorem erat et orci. Integer elementum tortor et nisl posuere consectetur. Sed malesuada leo ac urna lacinia, malesuada luctus mauris faucibus. Duis consequat posuere lobortis. Cras interdum lacinia lacus. Cras ipsum sapien, porttitor eget pellentesque ut, aliquam ut magna. Integer luctus velit et elementum malesuada. Maecenas tempus mauris quis mollis posuere. + +Maecenas consequat urna elit, eget fringilla felis laoreet ac. In congue ullamcorper odio, sed malesuada turpis luctus sed. Nullam sodales interdum elit ac dignissim. Pellentesque placerat mollis velit, vulputate tristique neque aliquet vitae. Phasellus tempor viverra est, vitae vehicula justo imperdiet nec. Fusce dictum lacinia urna eget rhoncus. Nam imperdiet nisl orci, sed ultricies orci laoreet vel. In in pulvinar eros. Ut eu rhoncus eros. Suspendisse eu dui viverra, lacinia erat vitae, mattis metus. Aliquam vitae posuere sapien. Quisque eu lorem quis mi egestas varius. Aliquam erat volutpat. + +Duis eu arcu lobortis, vehicula orci ac, imperdiet dui. Etiam venenatis nisi quam, quis dapibus erat sagittis non. Suspendisse lacinia blandit interdum. Vivamus vitae sollicitudin leo. Vivamus sit amet commodo tellus, ac sagittis augue. Vivamus mi orci, ultricies ac nulla at, pharetra maximus diam. Nullam rhoncus volutpat magna eu auctor. + +Etiam commodo enim a leo pellentesque, in elementum odio lobortis. Vestibulum lobortis lobortis malesuada. Sed imperdiet ullamcorper viverra. Cras facilisis malesuada purus a consequat. Ut auctor neque mi, in scelerisque nibh ornare eu. In non dui in enim pretium ullamcorper non rutrum urna. Donec dictum porta orci sed malesuada. Morbi ac placerat felis. Aliquam lacus sem, ullamcorper sed laoreet ut, imperdiet non libero. Nunc non metus id justo accumsan ultricies. Donec in lacinia eros. Morbi non nunc diam. Aenean nisl massa, vestibulum vel fringilla vel, placerat eu leo. Sed feugiat malesuada ultricies. + +Nulla tristique massa eu tortor feugiat auctor. In viverra eu ex quis auctor. Praesent ac sapien orci. Proin sit amet orci posuere, ultricies orci in, fermentum justo. Vivamus tempus, ligula et aliquam egestas, enim orci pulvinar dui, in ultricies mauris turpis eu nisi. Pellentesque nec dui in ipsum laoreet convallis. Vestibulum a mi ornare, elementum ex sed, rhoncus neque. + +Nullam sed orci id sem tincidunt suscipit. Cras ac leo at magna facilisis blandit. Aliquam vitae tristique nisi. Nulla vestibulum felis pretium lectus commodo, id eleifend risus eleifend. Mauris bibendum facilisis est vitae scelerisque. Cras interdum dapibus ligula, nec porta lacus imperdiet at. Cras rhoncus bibendum lorem, congue mollis libero dignissim eget. Sed gravida, mauris sit amet sodales posuere, tellus felis imperdiet arcu, quis iaculis orci orci vitae ipsum. Curabitur id orci odio. Aliquam vitae rutrum lacus. + +Maecenas tristique est felis, eget posuere lorem dapibus non. Maecenas eu interdum lectus. Nullam placerat sit amet quam non hendrerit. Nulla facilisis ornare mollis. Cras sed metus facilisis, mattis dui in, auctor est. Morbi vehicula venenatis est, vitae egestas felis tincidunt vitae. Donec iaculis massa id justo ornare rhoncus. Nulla tempor felis ex, eu consectetur justo dictum sed. Integer eget laoreet nibh. Duis vitae pellentesque tellus, id blandit justo. Cras quis mollis eros. Quisque rhoncus dignissim enim at sollicitudin. Vestibulum sit amet diam sed quam sodales finibus. Nulla maximus orci sit amet porttitor vestibulum. Nam consequat urna at varius vulputate. + +Suspendisse laoreet luctus mauris. Aenean mollis felis urna, ac pretium nunc rutrum non. Aenean semper, risus vitae iaculis eleifend, odio ligula tempus nunc, sit amet suscipit nibh lorem quis ipsum. Nam et placerat sapien. In luctus accumsan risus, id pulvinar elit venenatis eu. Phasellus elementum leo urna, a dapibus neque facilisis eget. Sed sit amet tempor tortor. Fusce iaculis, ipsum nec faucibus scelerisque, felis tortor condimentum purus, in fringilla ex est ac nisl. Nunc et interdum diam. Donec venenatis, dolor quis dignissim euismod, libero ante elementum libero, vitae laoreet purus velit sit amet ipsum. Quisque urna tellus, imperdiet eget gravida a, fringilla commodo diam. + +In posuere nisi dictum tortor elementum iaculis. In dignissim diam sit amet volutpat elementum. Curabitur ultricies mi libero, sit amet feugiat massa viverra sed. Vivamus justo nibh, commodo nec elementum eu, suscipit vel elit. Fusce ac cursus libero, et volutpat augue. In tempus ultricies libero, ac rutrum mauris. Morbi vestibulum tellus eu dui dignissim euismod. In hac habitasse platea dictumst. + +Nam rhoncus hendrerit ex et mattis. Sed varius, arcu quis placerat viverra, ex lorem ultrices arcu, nec fringilla ipsum metus ut ligula. Sed in luctus mi. Pellentesque sed eros nisl. Praesent rutrum magna metus, vel efficitur eros lobortis efficitur. Mauris vestibulum urna at ligula lobortis sollicitudin. Aenean rhoncus auctor leo vel interdum. Cras sollicitudin massa leo, et eleifend metus scelerisque sit amet. Praesent dapibus euismod libero a fermentum. + +Curabitur cursus lacus sit amet est feugiat euismod. Nulla accumsan risus congue lorem facilisis scelerisque. In sed lectus elementum, porttitor nisl vulputate, pulvinar mi. Maecenas eu mollis odio. In faucibus sagittis magna, vitae mollis nisi iaculis non. Aenean dictum lectus ac arcu vehicula fringilla. Curabitur accumsan efficitur libero, eget consequat magna ultrices vel. Donec fermentum vel orci eget finibus. Etiam in massa ante. Mauris gravida enim lacus, sit amet accumsan massa suscipit a. Mauris id bibendum ex, et convallis nisl. Morbi luctus, orci in malesuada finibus, neque turpis convallis justo, id gravida sem purus eget turpis. Fusce eu laoreet justo. + +Nulla facilisi. Nulla varius risus quam, sit amet aliquam felis lacinia a. Ut sapien felis, tincidunt in pellentesque sit amet, vehicula id elit. Morbi a congue nunc. Sed justo neque, rutrum tincidunt hendrerit in, luctus at mauris. Suspendisse porttitor ex vitae felis luctus, et bibendum eros consequat. Maecenas vitae lectus eget est volutpat fermentum ac ac elit. In hac habitasse platea dictumst. Aenean luctus ex nec orci euismod aliquam. Integer a dolor elementum, mollis magna vitae, porta dui. In ac erat posuere, facilisis lacus eget, venenatis velit. Nulla ut sapien tincidunt, ultricies lectus aliquet, varius odio. Curabitur viverra congue ipsum, non finibus enim lobortis vitae. + +Duis vestibulum maximus est, sollicitudin dapibus sapien accumsan sit amet. Cras luctus, massa malesuada ornare imperdiet, dolor lorem blandit est, a blandit erat quam vel tortor. Vestibulum id semper ipsum. Ut nec ante eget velit fringilla sagittis. Duis sit amet lobortis nisi. Aenean interdum dui ut metus suscipit, a pretium tortor ultrices. Nullam tincidunt bibendum nisl, vitae tincidunt urna tincidunt tempus. Donec vitae porta sem. Phasellus venenatis egestas ligula, quis volutpat ipsum lacinia et. Pellentesque placerat ipsum elit, a feugiat libero scelerisque fringilla. Suspendisse ullamcorper congue nisi ut fringilla. Aliquam quis suscipit orci. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam at nulla volutpat, viverra sem ac, blandit erat. Quisque gravida quam eu magna hendrerit, quis varius diam scelerisque. Suspendisse nisi enim, dignissim sit amet tristique ut, tempus sodales nibh. + +Nulla ac eleifend leo. Quisque laoreet finibus mattis. Nam est nulla, maximus sed luctus non, volutpat quis ipsum. Praesent et quam dolor. Nulla facilisi. Aliquam enim orci, volutpat vitae mattis pellentesque, fringilla ut eros. Fusce semper arcu et sapien rutrum laoreet. Maecenas vel imperdiet lectus. Mauris et pulvinar justo. Mauris sed luctus diam. Fusce a odio eget ante consequat volutpat. Nullam at lorem ut dui ornare aliquam nec non justo. Mauris turpis eros, blandit eget elementum ullamcorper, molestie vel quam. Aenean pretium interdum ligula ac fringilla. Aenean et felis lorem. Ut id lectus quis risus finibus condimentum. + +Curabitur aliquet quis justo sed posuere. Donec eu libero eget mi ullamcorper placerat. Etiam massa mi, lobortis eget fermentum in, facilisis vel lectus. In hac habitasse platea dictumst. Nam laoreet sodales metus, nec finibus nulla volutpat bibendum. Aenean ut vulputate dolor, vitae venenatis est. Fusce ipsum libero, laoreet sit amet justo at, auctor tristique arcu. Praesent pellentesque efficitur velit non accumsan. Proin fermentum tempus ante, at eleifend lectus fringilla sit amet. Nunc et diam ac velit tristique malesuada a id mauris. Sed euismod turpis lacus, a maximus dolor semper in. Nulla mauris tortor, dignissim vel mauris sed, efficitur ultrices nulla. Donec sed molestie libero. Quisque nec congue eros, ut consequat lectus. Suspendisse vitae tortor sapien. Sed vitae pellentesque dolor. + +Suspendisse dictum velit metus, vel mollis erat imperdiet ut. Mauris et sapien eleifend, malesuada ante vehicula, ornare tellus. Integer condimentum mattis risus, nec luctus lacus convallis a. Phasellus bibendum consequat nisi, ut consequat dui bibendum a. Sed venenatis lobortis turpis, a venenatis ex sodales eu. Duis sit amet sem dui. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla vitae tellus a ex tristique mattis. Quisque viverra vitae turpis accumsan imperdiet. Cras nunc erat, commodo et malesuada at, vulputate in lorem. Fusce tempor venenatis dui consequat auctor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse potenti. + +Fusce et ligula sem. In condimentum dolor metus, eu luctus justo ultrices nec. Aenean vitae ligula non erat tincidunt blandit. Pellentesque luctus tellus ante, mollis cursus arcu posuere a. Ut sed lacus suscipit, dapibus quam non, elementum purus. Nullam sed venenatis tortor, id luctus lacus. Suspendisse orci felis, porta et sagittis at, posuere vitae lectus. + +Cras in ligula ut mi viverra efficitur at sed erat. Donec congue suscipit orci, eu volutpat augue ultricies non. Maecenas imperdiet tincidunt commodo. Cras quis vulputate urna, et malesuada lorem. Donec id convallis nibh, non congue lacus. Curabitur et scelerisque nisl. Maecenas vestibulum elit ipsum, in posuere tortor placerat sit amet. Nullam nec ex eget libero mattis commodo a at leo. Vestibulum aliquet, eros quis facilisis aliquam, mi arcu aliquet nisl, in tempus massa sapien at tortor. + +Maecenas et dolor sed sapien lacinia fringilla eget et nibh. Aenean viverra urna sit amet lobortis vestibulum. Aliquam vehicula rutrum magna ut aliquam. Maecenas pharetra volutpat porttitor. In id ultricies sapien, a accumsan lectus. Fusce in elit a ex auctor rutrum sit amet ac lorem. Mauris eu mi a nisl vehicula mattis. Donec dictum velit nec libero bibendum, in volutpat metus viverra. Vivamus eget sollicitudin nunc, ac vestibulum erat. Sed dolor risus, semper nec lorem vitae, vehicula molestie purus. Quisque ac lectus iaculis, ultrices leo sit amet, mattis erat. Curabitur lorem mauris, vestibulum vel risus eu, molestie facilisis elit. Pellentesque habitant morbi tristique senectus et netus et volutpat. diff --git a/services/clsi/test/load/coffee/loadTest.coffee b/services/clsi/test/load/coffee/loadTest.coffee new file mode 100644 index 0000000000..26a23fba50 --- /dev/null +++ b/services/clsi/test/load/coffee/loadTest.coffee @@ -0,0 +1,71 @@ +request = require "request" +Settings = require "settings-sharelatex" +async = require("async") +fs = require("fs") +_ = require("underscore") +concurentCompiles = 5 +totalCompiles = 50 + +buildUrl = (path) -> "http://#{Settings.internal.clsi.host}:#{Settings.internal.clsi.port}/#{path}" + +mainTexContent = fs.readFileSync("./bulk.tex", "utf-8") + +compileTimes = [] +failedCount = 0 + +getAverageCompileTime = -> + totalTime = _.reduce compileTimes, (sum, time)-> + sum + time + , 0 + return totalTime / compileTimes.length + +makeRequest = (compileNumber, callback)-> + bulkBodyCount = 7 + bodyContent = "" + while --bulkBodyCount + bodyContent = bodyContent+=mainTexContent + + + startTime = new Date() + request.post { + url: buildUrl("project/loadcompile-#{compileNumber}/compile") + json: + compile: + resources: [ + path: "main.tex" + content: """ + \\documentclass{article} + \\begin{document} + #{bodyContent} + \\end{document} + """ + ] + }, (err, response, body)-> + if response.statusCode != 200 + failedCount++ + return callback("compile #{compileNumber} failed") + if err? + failedCount++ + return callback("failed") + totalTime = new Date() - startTime + console.log totalTime+"ms" + compileTimes.push(totalTime) + callback(err) + + +jobs = _.map [1..totalCompiles], (i)-> + return (cb)-> + makeRequest(i, cb) + +startTime = new Date() +async.parallelLimit jobs, concurentCompiles, (err)-> + if err? + console.error err + console.log("total time taken = #{(new Date() - startTime)/1000}s") + console.log("total compiles = #{totalCompiles}") + console.log("concurent compiles = #{concurentCompiles}") + console.log("average time = #{getAverageCompileTime()/1000}s") + console.log("max time = #{_.max(compileTimes)/1000}s") + console.log("min time = #{_.min(compileTimes)/1000}s") + console.log("total failures = #{failedCount}") + From b3c030b3ed45769766ae96f26c6c85665078a008 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Fri, 14 Aug 2015 14:47:42 +0100 Subject: [PATCH 104/754] add memory logger from metrics-sharelatex --- services/clsi/app.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 9083bb4bf3..0c1248a5df 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -10,6 +10,7 @@ fs = require "fs" Metrics = require "metrics-sharelatex" Metrics.initialize("clsi") Metrics.open_sockets.monitor(logger) +Metrics.memory.monitor(logger) ProjectPersistenceManager = require "./app/js/ProjectPersistenceManager" OutputCacheManager = require "./app/js/OutputCacheManager" From d3326656488313b7ca421755c7af7f7476b526db Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Tue, 8 Sep 2015 10:19:46 -0300 Subject: [PATCH 105/754] move texcount to docker --- .../clsi/app/coffee/CompileManager.coffee | 19 +++++++------- .../unit/coffee/CompileManagerTests.coffee | 25 +++++++++++++------ 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index f689ae8833..0392ccef2c 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -7,6 +7,8 @@ Path = require "path" logger = require "logger-sharelatex" Metrics = require "./Metrics" child_process = require "child_process" +CommandRunner = require(Settings.clsi?.commandRunner or "./CommandRunner") +fs = require("fs") module.exports = CompileManager = doCompile: (request, callback = (error, outputFiles) ->) -> @@ -142,15 +144,12 @@ module.exports = CompileManager = return results wordcount: (project_id, file_name, callback = (error, pdfPositions) ->) -> - base_dir = Settings.path.synctexBaseDir(project_id) - file_path = base_dir + "/" + file_name - CompileManager._runWordcount [file_path], (error, stdout) -> - return callback(error) if error? - logger.log project_id: project_id, file_name: file_name, stdout: stdout, "wordcount output" - callback null, CompileManager._parseWordcountFromOutput(stdout) + file_path = "$COMPILE_DIR/" + file_name + command = [ "texcount", file_path, "-out=" + file_path + ".wc"] + directory = Path.join(Settings.path.compilesDir, project_id) + timeout = 10 * 1000 - _runWordcount: (args, callback = (error, stdout) ->) -> - seconds = 1000 - child_process.execFile "texcount", args, timeout: 10 * seconds, (error, stdout, stderr) -> + CommandRunner.run project_id, command, directory, timeout, (error) -> return callback(error) if error? - callback(null, stdout) \ No newline at end of file + stdout = fs.readFileSync(directory + "/" + file_name + ".wc", "utf-8") + callback null, CompileManager._parseWordcountFromOutput(stdout) diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index eae49627c1..10ab2eac43 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -16,6 +16,8 @@ describe "CompileManager", -> "settings-sharelatex": @Settings = { path: compilesDir: "/compiles/dir" } "logger-sharelatex": @logger = { log: sinon.stub() } "child_process": @child_process = {} + "./CommandRunner": @CommandRunner = {} + "fs": @fs = {} @callback = sinon.stub() describe "doCompile", -> @@ -175,16 +177,24 @@ describe "CompileManager", -> describe "wordcount", -> beforeEach -> + @CommandRunner.run = sinon.stub().callsArg(4) + @fs.readFileSync = sinon.stub().returns @stdout = "Encoding: ascii\nWords in text: 2" + @callback = sinon.stub() + + @project_id = "project-id-123" + @timeout = 10 * 1000 @file_name = "main.tex" - @child_process.execFile = sinon.stub() - @child_process.execFile.callsArgWith(3, null, @stdout = "Encoding: ascii\nWords in text: 2", "") - @Settings.path.synctexBaseDir = (project_id) => "#{@Settings.path.compilesDir}/#{@project_id}" + @Settings.path.compilesDir = "/local/compile/directory" + @CompileManager.wordcount @project_id, @file_name, @callback - it "should execute the texcount binary", -> - file_path = "#{@Settings.path.compilesDir}/#{@project_id}/#{@file_name}" - @child_process.execFile - .calledWith("texcount", [file_path], timeout: 10000) + it "should run the texcount command", -> + @directory = "#{@Settings.path.compilesDir}/#{@project_id}" + @file_path = "$COMPILE_DIR/#{@file_name}" + @command =[ "texcount", @file_path, "-out=" + @file_path + ".wc"] + + @CommandRunner.run + .calledWith(@project_id, @command, @directory, @timeout) .should.equal true it "should call the callback with the parsed output", -> @@ -200,4 +210,3 @@ describe "CompileManager", -> mathDisplay: 0 }) .should.equal true - From 4b3aa933a395d6dfacac9aa8914174d67e9af0d7 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Tue, 8 Sep 2015 21:58:27 +0100 Subject: [PATCH 106/754] don't error if a http resource can not be download try and continue, log the error but you might still be able to compile. prevents issue with badly uploaded images in filstore --- services/clsi/app/coffee/ResourceWriter.coffee | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index 78667013a8..faeaf1194e 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -5,6 +5,7 @@ async = require "async" mkdirp = require "mkdirp" OutputFileFinder = require "./OutputFileFinder" Metrics = require "./Metrics" +logger = require "logger-sharelatex" module.exports = ResourceWriter = syncResourcesToDisk: (project_id, resources, basePath, callback = (error) ->) -> @@ -55,13 +56,10 @@ module.exports = ResourceWriter = return callback(error) if error? # TODO: Don't overwrite file if it hasn't been modified if resource.url? - UrlCache.downloadUrlToFile( - project_id, - resource.url, - path, - resource.modified, - callback - ) + UrlCache.downloadUrlToFile project_id, resource.url, path, resource.modified, (err)-> + if err? + logger.err err:err, "error downloading file for resources" + callback() #try and continue compiling even if http resource can not be downloaded at this time else fs.writeFile path, resource.content, callback From d44cfb8614eefffc7f5b1a4558215b2b22ac6325 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 9 Sep 2015 09:44:38 +0100 Subject: [PATCH 107/754] added test to check compile should continue on error downloading http resource also improved logging --- services/clsi/app/coffee/ResourceWriter.coffee | 2 +- services/clsi/test/unit/coffee/ResourceWriterTests.coffee | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index faeaf1194e..25b2625f19 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -58,7 +58,7 @@ module.exports = ResourceWriter = if resource.url? UrlCache.downloadUrlToFile project_id, resource.url, path, resource.modified, (err)-> if err? - logger.err err:err, "error downloading file for resources" + logger.err err:err, project_id:project_id, path:path, resource_url:resource.url, modified:resource.modified, "error downloading file for resources" callback() #try and continue compiling even if http resource can not be downloaded at this time else fs.writeFile path, resource.content, callback diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee index 1e7b3e776b..3e87b5ca34 100644 --- a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee +++ b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee @@ -1,6 +1,6 @@ SandboxedModule = require('sandboxed-module') sinon = require('sinon') -require('chai').should() +should = require('chai').should() modulePath = require('path').join __dirname, '../../../app/js/ResourceWriter' path = require "path" @@ -94,7 +94,7 @@ describe "ResourceWriter", -> path: "main.tex" url: "http://www.example.com/main.tex" modified: Date.now() - @UrlCache.downloadUrlToFile = sinon.stub().callsArg(4) + @UrlCache.downloadUrlToFile = sinon.stub().callsArgWith(4, "fake error downloading file") @ResourceWriter._writeResourceToDisk(@project_id, @resource, @basePath, @callback) it "should ensure the directory exists", -> @@ -110,6 +110,9 @@ describe "ResourceWriter", -> it "should call the callback", -> @callback.called.should.equal true + it "should not return an error if the resource writer errored", -> + should.not.exist @callback.args[0][0] + describe "with a content based resource", -> beforeEach -> @resource = From ec338f8c10427cabb6bbb4826408dff2893e1464 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 9 Sep 2015 12:43:32 +0100 Subject: [PATCH 108/754] add -inc to word count use -inc to word count included files also moved private function to bottom --- .../clsi/app/coffee/CompileController.coffee | 2 +- .../clsi/app/coffee/CompileManager.coffee | 24 +++++++++---------- .../unit/coffee/CompileManagerTests.coffee | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index ce107f8eab..f7d46b1305 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -68,7 +68,7 @@ module.exports = CompileController = } wordcount: (req, res, next = (error) ->) -> - file = req.query.file + file = req.query.file || "main.tex" project_id = req.params.project_id CompileManager.wordcount project_id, file, (error, result) -> diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 0392ccef2c..b4a8357bdb 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -111,6 +111,18 @@ module.exports = CompileManager = } return results + wordcount: (project_id, file_name, callback = (error, pdfPositions) ->) -> + logger.log project_id:project_id, file_name:file_name, "running wordcount" + file_path = "$COMPILE_DIR/" + file_name + command = [ "texcount", '-inc', file_path, "-out=" + file_path + ".wc"] + directory = Path.join(Settings.path.compilesDir, project_id) + timeout = 10 * 1000 + + CommandRunner.run project_id, command, directory, timeout, (error) -> + return callback(error) if error? + stdout = fs.readFileSync(directory + "/" + file_name + ".wc", "utf-8") + callback null, CompileManager._parseWordcountFromOutput(stdout) + _parseWordcountFromOutput: (output) -> results = { encode: "" @@ -140,16 +152,4 @@ module.exports = CompileManager = results['mathInline'] = parseInt(info, 10) if data.indexOf("displayed") > -1 results['mathDisplay'] = parseInt(info, 10) - return results - - wordcount: (project_id, file_name, callback = (error, pdfPositions) ->) -> - file_path = "$COMPILE_DIR/" + file_name - command = [ "texcount", file_path, "-out=" + file_path + ".wc"] - directory = Path.join(Settings.path.compilesDir, project_id) - timeout = 10 * 1000 - - CommandRunner.run project_id, command, directory, timeout, (error) -> - return callback(error) if error? - stdout = fs.readFileSync(directory + "/" + file_name + ".wc", "utf-8") - callback null, CompileManager._parseWordcountFromOutput(stdout) diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 10ab2eac43..d6678b9407 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -191,7 +191,7 @@ describe "CompileManager", -> it "should run the texcount command", -> @directory = "#{@Settings.path.compilesDir}/#{@project_id}" @file_path = "$COMPILE_DIR/#{@file_name}" - @command =[ "texcount", @file_path, "-out=" + @file_path + ".wc"] + @command =[ "texcount", '-inc', @file_path, "-out=" + @file_path + ".wc"] @CommandRunner.run .calledWith(@project_id, @command, @directory, @timeout) From 4ee50b7239e7325b9fcb74dc411474e2e1e51788 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 9 Sep 2015 13:52:45 +0100 Subject: [PATCH 109/754] - fixed bug with texcount returning wrong data for nauty lines - improved acceptence test for word count to use nauty lines --- .../clsi/app/coffee/CompileManager.coffee | 6 +- .../acceptance/coffee/SynctexTests.coffee | 18 - .../acceptance/coffee/WordcountTests.coffee | 34 + .../acceptance/fixtures/naugty_strings.txt | 626 ++++++++++++++++++ .../unit/coffee/CompileManagerTests.coffee | 2 +- 5 files changed, 664 insertions(+), 22 deletions(-) create mode 100644 services/clsi/test/acceptance/coffee/WordcountTests.coffee create mode 100644 services/clsi/test/acceptance/fixtures/naugty_strings.txt diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index b4a8357bdb..296522f6b4 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -146,10 +146,10 @@ module.exports = CompileManager = results['outside'] = parseInt(info, 10) if data.indexOf("of head") > -1 results['headers'] = parseInt(info, 10) - if data.indexOf("float") > -1 + if data.indexOf("Number of floats/tables/figures") > -1 results['elements'] = parseInt(info, 10) - if data.indexOf("inlines") > -1 + if data.indexOf("Number of math inlines") > -1 results['mathInline'] = parseInt(info, 10) - if data.indexOf("displayed") > -1 + if data.indexOf("Number of math displayed") > -1 results['mathDisplay'] = parseInt(info, 10) return results diff --git a/services/clsi/test/acceptance/coffee/SynctexTests.coffee b/services/clsi/test/acceptance/coffee/SynctexTests.coffee index 8979b983e5..02b2397897 100644 --- a/services/clsi/test/acceptance/coffee/SynctexTests.coffee +++ b/services/clsi/test/acceptance/coffee/SynctexTests.coffee @@ -35,21 +35,3 @@ describe "Syncing", -> code: [ { file: 'main.tex', line: 3, column: -1 } ] ) done() - - describe "wordcount file", -> - it "should return wordcount info", (done) -> - Client.wordcount @project_id, "main.tex", (error, result) -> - throw error if error? - expect(result).to.deep.equal( - texcount: { - encode: "ascii" - textWords: 2 - headWords: 0 - outside: 0 - headers: 0 - elements: 0 - mathInline: 0 - mathDisplay: 0 - } - ) - done() diff --git a/services/clsi/test/acceptance/coffee/WordcountTests.coffee b/services/clsi/test/acceptance/coffee/WordcountTests.coffee new file mode 100644 index 0000000000..1789cfcdd0 --- /dev/null +++ b/services/clsi/test/acceptance/coffee/WordcountTests.coffee @@ -0,0 +1,34 @@ +Client = require "./helpers/Client" +request = require "request" +require("chai").should() +expect = require("chai").expect +path = require("path") +fs = require("fs") + +describe "Syncing", -> + before (done) -> + @request = + resources: [ + path: "main.tex" + content: fs.readFileSync(path.join(__dirname,"../fixtures/naugty_strings.txt"),"utf-8") + ] + @project_id = Client.randomId() + Client.compile @project_id, @request, (@error, @res, @body) => done() + + describe "wordcount file", -> + it "should return wordcount info", (done) -> + Client.wordcount @project_id, "main.tex", (error, result) -> + throw error if error? + expect(result).to.deep.equal( + texcount: { + encode: "utf8" + textWords: 2281 + headWords: 2 + outside: 0 + headers: 2 + elements: 0 + mathInline: 6 + mathDisplay: 0 + } + ) + done() diff --git a/services/clsi/test/acceptance/fixtures/naugty_strings.txt b/services/clsi/test/acceptance/fixtures/naugty_strings.txt new file mode 100644 index 0000000000..92eb1ddce6 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/naugty_strings.txt @@ -0,0 +1,626 @@ +\documentclass{article} +\usepackage[utf8]{inputenc} + +\title{eee} +\author{henry.oswald } +\date{September 2015} + +\usepackage{natbib} +\usepackage{graphicx} + +\begin{document} + +\maketitle + +\section{Introduction} + +Encoding: utf8 + +# Reserved Strings +# +# Strings which may be used elsewhere in code + +undefined +undef +null +NULL +(null) +nil +NIL +true +false +True +False +None +\ +\\ + +# Numeric Strings +# +# Strings which can be interpreted as numeric + +0 +1 +1.00 +$1.00 +1/2 +1E2 +1E02 +1E+02 +-1 +-1.00 +-$1.00 +-1/2 +-1E2 +-1E02 +-1E+02 +1/0 +0/0 +-2147483648/-1 +-9223372036854775808/-1 +0.00 +0..0 +. +0.0.0 +0,00 +0,,0 +, +0,0,0 +0.0/0 +1.0/0.0 +0.0/0.0 +1,0/0,0 +0,0/0,0 +--1 +- +-. +-, +999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +NaN +Infinity +-Infinity +0x0 +0xffffffff +0xffffffffffffffff +0xabad1dea +123456789012345678901234567890123456789 +1,000.00 +1 000.00 +1'000.00 +1,000,000.00 +1 000 000.00 +1'000'000.00 +1.000,00 +1 000,00 +1'000,00 +1.000.000,00 +1 000 000,00 +1'000'000,00 +01000 +08 +09 +2.2250738585072011e-308 + +# Special Characters +# +# Strings which contain common special ASCII characters (may need to be escaped) + +,./;'[]\-= +<>?:"{}|_+ +!@#$%^&*()`~ + +# Unicode Symbols +# +# Strings which contain common unicode symbols (e.g. smart quotes) + +Ω≈ç√∫˜µ≤≥÷ +åß∂ƒ©˙∆˚¬…æ +œ∑´®†¥¨ˆøπ“‘ +¡™£¢∞§¶•ªº–≠ +¸˛Ç◊ı˜Â¯˘¿ +Ã…ÃÃŽÃËÓÔÒÚÆ☃ +Œ„´‰ˇÃ¨ˆØâˆâ€â€™ +`â„€‹›ï¬ï¬‚‡°·‚—± +⅛⅜â…â…ž +ÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛМÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыьÑÑŽÑ +٠١٢٣٤٥٦٧٨٩ + +# Unicode Subscript/Superscript +# +# Strings which contain unicode subscripts/superscripts; can cause rendering issues + +â°â´âµ +â‚€â‚â‚‚ +â°â´âµâ‚€â‚â‚‚ + +# Quotation Marks +# +# Strings which contain misplaced quotation marks; can cause encoding errors + +' +" +'' +"" +'"' +"''''"'" +"'"'"''''" + +# Two-Byte Characters +# +# Strings which contain two-byte characters: can cause rendering issues or character-length issues + +田中ã•ã‚“ã«ã‚ã’ã¦ä¸‹ã•ã„ +パーティーã¸è¡Œã‹ãªã„ã‹ +和製漢語 +部è½æ ¼ +ì‚¬íšŒê³¼í•™ì› ì–´í•™ì—°êµ¬ì†Œ +찦차를 타고 온 펲시맨과 쑛다리 똠방ê°í•˜ +社會科學院語學研究所 +울란바토르 +𠜎𠜱ð ¹ð ±“𠱸𠲖𠳠+ +# Japanese Emoticons +# +# Strings which consists of Japanese-style emoticons which are popular on the web + +ヽ༼ຈل͜ຈ༽ノ ヽ༼ຈل͜ຈ༽ノ +(。◕ ∀ ◕。) +`ィ(´∀`∩ +__ï¾›(,_,*) +・( ̄∀ ̄)・:*: +゚・✿ヾ╲(。◕‿◕。)╱✿・゚ +,。・:*:・゜’( ☻ ω ☻ )。・:*:・゜’ +(╯°□°)╯︵ â”»â”â”») +(ノಥ益ಥ)ノ â”»â”â”» +( ͡° ͜ʖ ͡°) + +# Emoji +# +# Strings which contain Emoji; should be the same behavior as two-byte characters, but not always + +😠+👩🽠+👾 🙇 💠🙅 🙆 🙋 🙎 🙠+🵠🙈 🙉 🙊 +â¤ï¸ 💔 💌 💕 💞 💓 💗 💖 💘 💠💟 💜 💛 💚 💙 +✋🿠💪🿠ðŸ‘🿠🙌🿠ðŸ‘🿠ðŸ™ðŸ¿ +🚾 🆒 🆓 🆕 🆖 🆗 🆙 🧠+0ï¸âƒ£ 1ï¸âƒ£ 2ï¸âƒ£ 3ï¸âƒ£ 4ï¸âƒ£ 5ï¸âƒ£ 6ï¸âƒ£ 7ï¸âƒ£ 8ï¸âƒ£ 9ï¸âƒ£ 🔟 + +# Unicode Numbers +# +# Strings which contain unicode numbers; if the code is localized, it should see the input as numeric + +123 +١٢٣ + +# Right-To-Left Strings +# +# Strings which contain text that should be rendered RTL if possible (e.g. Arabic, Hebrew) + +ثم Ù†Ùس سقطت وبالتحديد،, جزيرتي باستخدام أن دنو. إذ هنا؟ الستار وتنصيب كان. أهّل ايطاليا، بريطانيا-Ùرنسا قد أخذ. سليمان، إتÙاقية بين ما, يذكر الحدود أي بعد, معاملة بولندا، الإطلاق عل إيو. +בְּרֵ×שִ×ית, ×‘Ö¸Ö¼×¨Ö¸× ×ֱלֹהִי×, ×ֵת הַשָּ×מַיִ×, וְ×ֵת ×”Ö¸×ָרֶץ +הָיְתָהtestالصÙحات التّحول +ï·½ +ï·º + +# Unicode Spaces +# +# Strings which contain unicode space characters with special properties (c.f. https://www.cs.tut.fi/~jkorpela/chars/spaces.html) + +​ +  +á Ž +  + +⣠+⢠+â¡ + +# Trick Unicode +# +# Strings which contain unicode with unusual properties (e.g. Right-to-left override) (c.f. http://www.unicode.org/charts/PDF/U2000.pdf) + +‪‪test‪ +‫test‫ +
test
 +testâ test‫ +â¦test⧠+ +# Zalgo Text +# +# Strings which contain "corrupted" text. The corruption will not appear in non-HTML text, however. (via http://www.eeemo.net) + +Ṱ̺̺̕oÍž Ì·i̲̬͇̪͙nÌÌ—Í•v̟̜̘̦͟o̶̙̰̠kè͚̮̺̪̹̱̤ Ì–tÌ͕̳̣̻̪͞h̼͓̲̦̳̘̲e͇̣̰̦̬͎ ̢̼̻̱̘h͚͎͙̜̣̲ͅi̦̲̣̰̤vÌ»Íe̺̭̳̪̰-mÌ¢iÍ…n̖̺̞̲̯̰d̵̼̟͙̩̼̘̳ ̞̥̱̳̭r̛̗̘eÍ™pÍ r̼̞̻̭̗e̺̠̣͟s̘͇̳ÍÌ͉e͉̥̯̞̲͚̬͜ǹ̬͎͎̟̖͇̤tÍ̬̤͓̼̭͘ͅi̪̱nÍ g̴͉ Í͉ͅc̬̟hÍ¡a̫̻̯͘oÌ«ÌŸÌ–ÍÌ™Ì͉s̗̦̲.̨̹͈̣ +̡͓̞ͅI̗̘̦Ín͇͇͙v̮̫ok̲̫̙͈i̖͙̭̹̠̞n̡̻̮̣̺g̲͈͙̭͙̬͎ Ì°t͔̦h̞̲e̢̤ Í̬̲͖f̴̘͕̣è͖ẹ̥̩l͖͔͚i͓͚̦͠nÍ–Í̗͓̳̮gÍ Ì¨o͚̪͡f̘̣̬ ̖̘͖̟͙̮c҉͔̫͖͓͇͖ͅh̵̤̣͚͔á̗̼͕ͅo̼̣̥s̱͈̺̖̦̻͢.̛̖̞̠̫̰ +̗̺͖̹̯͓Ṯ̤Í̥͇͈h̲ÌeÍ͓̼̗̙̼̣͔ ͇̜̱̠͓ÍÍ…NÍ•Í e̗̱z̘Ì̜̺͙p̤̺̹Í̯͚e̠̻̠͜r̨̤Í̺̖͔̖̖d̠̟̭̬ÌÍŸi̦͖̩͓͔̤a̠̗̬͉̙n͚͜ ̻̞̰͚ͅh̵͉i̳̞v̢͇ḙ͎͟-҉̭̩̼͔m̤̭̫i͕͇Ì̦nÌ—Í™á¸ÌŸ ̯̲͕͞ǫ̟̯̰̲͙̻Ìf ̪̰̰̗̖̭̘͘c̦Í̲̞Í̩̙ḥ͚a̮͎̟̙͜ơ̩̹͎s̤.ÌÌ Ò‰Z̡̖̜͖̰̣͉̜a͖̰͙̬͡l̲̫̳ÍÌ©g̡̟̼̱͚̞̬ͅoÌ—Íœ.ÌŸ +̦H̬̤̗̤ÍeÍœ ̜̥ÌÌ»ÍÌŸÌwÌ•h̖̯͓oÌ͙̖͎̱̮ ҉̺̙̞̟͈W̷̼̭a̺̪Íį͈͕̭͙̯̜t̶̼̮s̘͙͖̕ Ì Ì«Ì BÌ»Í͙͉̳ͅe̵h̵̬͇̫͙i̹͓̳̳̮͎̫̕nÍŸd̴̪̜̖ ̰͉̩͇͙̲͞ͅT͖̼͓̪͢hÍ͓̮̻e̬ÌÌŸÍ… ̤̹ÌW͙̞Ì͔͇ÍÍ…aÍ͓͔̹̼̣l̴͔̰̤̟͔ḽ̫.Í• +Z̮̞̠͙͔ͅḀ̗̞͈̻̗Ḷ͙͎̯̹̞͓GÌ»OÌ­Ì—Ì® + +# Unicode Upsidedown +# +# Strings which contain unicode with an "upsidedown" effect (via http://www.upsidedowntext.com) + +Ë™Énbá´‰lÉ ÉuƃÉɯ Çɹolop Ê‡Ç ÇɹoqÉl ʇn ʇunpá´‰pᴉɔuá´‰ ɹodɯÇʇ poɯsná´‰Ç op pÇs 'ʇᴉlÇ Æƒuᴉɔsá´‰dá´‰pÉ É¹nʇÇʇɔÇsuoÉ” 'ʇÇÉ¯É Ê‡á´‰s ɹolop ɯnsdá´‰ ɯÇɹoË¥ +00˙Ɩ$- + +# Unicode font +# +# Strings which contain bold/italic/etc. versions of normal characters + +The quick brï½ï½—n fï½ï½˜ juï½ï½ï½“ ï½ï½–ï½…ï½’ the lï½ï½šï½™ dï½ï½‡ +ð“ð¡ðž ðªð®ð¢ðœð¤ ð›ð«ð¨ð°ð§ ðŸð¨ð± ð£ð®ð¦ð©ð¬ ð¨ð¯ðžð« ð­ð¡ðž ð¥ðšð³ð² ðð¨ð  +ð•¿ð–ð–Š ð––ð–šð–Žð–ˆð– ð–‡ð–—ð–”ð–œð–“ ð–‹ð–”ð– ð–ð–šð–’ð–•ð–˜ ð–”ð–›ð–Šð–— ð–™ð–ð–Š ð–‘ð–†ð–Ÿð–ž ð–‰ð–”𖌠+ð‘»ð’‰ð’† ð’’ð’–ð’Šð’„𒌠ð’ƒð’“ð’ð’˜ð’ ð’‡ð’ð’™ ð’‹ð’–ð’Žð’‘ð’” ð’ð’—ð’†ð’“ ð’•ð’‰ð’† ð’ð’‚ð’›ð’š ð’…ð’ð’ˆ +ð“£ð“±ð“® ð“ºð“¾ð“²ð“¬ð“´ ð“«ð“»ð“¸ð”€ð“· ð“¯ð“¸ð” ð“³ð“¾ð“¶ð“¹ð“¼ ð“¸ð“¿ð“®ð“» ð“½ð“±ð“® ð“µð“ªð”ƒð”‚ ð“­ð“¸ð“° +ð•‹ð•™ð•– ð•¢ð•¦ð•šð•”𕜠ð•“ð•£ð• ð•¨ð•Ÿ ð•—ð• ð•© ð•›ð•¦ð•žð•¡ð•¤ ð• ð•§ð•–ð•£ ð•¥ð•™ð•– ð•ð•’ð•«ð•ª ð••ð• ð•˜ +ðšƒðš‘𚎠ðššðšžðš’ðšŒðš” ðš‹ðš›ðš˜ðš ðš— ðšðš˜ðš¡ ðš“ðšžðš–ðš™ðšœ ðš˜ðšŸðšŽðš› ðšðš‘𚎠ðš•ðšŠðš£ðš¢ ðšðš˜ðš +⒯⒣⒠ ⒬⒰⒤⒞⒦ â’⒭⒪⒲⒩ ⒡⒪⒳ ⒥⒰⒨⒫⒮ ⒪⒱⒠⒭ ⒯⒣⒠ ⒧⒜⒵⒴ ⒟⒪⒢ + +# Script Injection +# +# Strings which attempt to invoke a benign script injection; shows vulnerability to XSS + + +<script>alert('123');</script> + + +"> +'> +> + +< / script >< script >alert(123)< / script > + onfocus=JaVaSCript:alert(123) autofocus +" onfocus=JaVaSCript:alert(123) autofocus +' onfocus=JaVaSCript:alert(123) autofocus +<script>alert(123)</script> +ript>alert(123)ript> +--> +";alert(123);t=" +';alert(123);t=' +JavaSCript:alert(123) +;alert(123); +src=JaVaSCript:prompt(132) +">javascript:alert(1); +javascript:alert(1); +javascript:alert(1); +javascript:alert(1); +javascript:alert(1); +javascript:alert(1); +javascript:alert(1); +'`"><\x3Cscript>javascript:alert(1) +'`"><\x00script>javascript:alert(1) +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +`"'> +`"'> +`"'> +`"'> +`"'> +`"'> +`"'> +`"'> +`"'> +`"'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +XXX + + + +<a href=http://foo.bar/#x=`y></a><img alt="`><img src=x:x onerror=javascript:alert(1)></a>"> +<!--[if]><script>javascript:alert(1)</script --> +<!--[if<img src=x onerror=javascript:alert(1)//]> --> +<script src="/\%(jscript)s"></script> +<script src="\\%(jscript)s"></script> +<IMG """><SCRIPT>alert("XSS")</SCRIPT>"> +<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))> +<IMG SRC=# onmouseover="alert('xxs')"> +<IMG SRC= onmouseover="alert('xxs')"> +<IMG onmouseover="alert('xxs')"> +<IMG SRC=javascript:alert('XSS')> +<IMG SRC=javascript:alert('XSS')> +<IMG SRC=javascript:alert('XSS')> +<IMG SRC="jav ascript:alert('XSS');"> +<IMG SRC="jav ascript:alert('XSS');"> +<IMG SRC="jav ascript:alert('XSS');"> +<IMG SRC="jav ascript:alert('XSS');"> +perl -e 'print "<IMG SRC=java\0script:alert(\"XSS\")>";' > out +<IMG SRC="  javascript:alert('XSS');"> +<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT> +<BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")> +<SCRIPT/SRC="http://ha.ckers.org/xss.js"></SCRIPT> +<<SCRIPT>alert("XSS");//<</SCRIPT> +<SCRIPT SRC=http://ha.ckers.org/xss.js?< B > +<SCRIPT SRC=//ha.ckers.org/.j> +<IMG SRC="javascript:alert('XSS')" +<iframe src=http://ha.ckers.org/scriptlet.html < +\";alert('XSS');// +<plaintext> + +# SQL Injection +# +# Strings which can cause a SQL injection if inputs are not sanitized + +1;DROP TABLE users +1'; DROP TABLE users-- 1 +' OR 1=1 -- 1 +' OR '1'='1 + +# Server Code Injection +# +# Strings which can cause user to run code on server as a privileged user (c.f. https://news.ycombinator.com/item?id=7665153) + +- +-- +--version +--help +$USER +/dev/null; touch /tmp/blns.fail ; echo +`touch /tmp/blns.fail` +$(touch /tmp/blns.fail) +@{[system "touch /tmp/blns.fail"]} + +# Command Injection (Ruby) +# +# Strings which can call system commands within Ruby/Rails applications + +eval("puts 'hello world'") +System("ls -al /") +`ls -al /` +Kernel.exec("ls -al /") +Kernel.exit(1) +%x('ls -al /') + +# XXE Injection (XML) +# +# String which can reveal system files when parsed by a badly configured XML parser + +<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [ <!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo> + +# Unwanted Interpolation +# +# Strings which can be accidentally expanded into different strings if evaluated in the wrong context, e.g. used as a printf format string or via Perl or shell eval. Might expose sensitive data from the program doing the interpolation, or might just represent the wrong string. + +$HOME +$ENV{'HOME'} +%d +%s +%*.*s + +# File Inclusion +# +# Strings which can cause user to pull in files that should not be a part of a web server + +../../../../../../../../../../../etc/passwd%00 +../../../../../../../../../../../etc/hosts + +# Known CVEs and Vulnerabilities +# +# Strings that test for known vulnerabilities + +() { 0; }; touch /tmp/blns.shellshock1.fail; +() { _; } >_[$($())] { touch /tmp/blns.shellshock2.fail; } + +# MSDOS/Windows Special Filenames +# +# Strings which are reserved characters in MSDOS/Windows + +CON +PRN +AUX +CLOCK$ +NUL +A: +ZZ: +COM1 +LPT1 +LPT2 +LPT3 +COM2 +COM3 +COM4 + +# Scunthorpe Problem +# +# Innocuous strings which may be blocked by profanity filters (https://en.wikipedia.org/wiki/Scunthorpe_problem) + +Scunthorpe General Hospital +Penistone Community Church +Lightwater Country Park +Jimmy Clitheroe +Horniman Museum +shitake mushrooms +RomansInSussex.co.uk +http://www.cum.qc.ca/ +Craig Cockburn, Software Specialist +Linda Callahan +Dr. Herman I. Libshitz +magna cum laude +Super Bowl XXX +medieval erection of parapets +evaluate +mocha +expression +Arsenal canal +classic +Tyson Gay + +# Human injection +# +# Strings which may cause human to reinterpret worldview + +If you're reading this, you've been in a coma for almost 20 years now. We're trying a new technique. We don't know where this message will end up in your dream, but we hope it works. Please wake up, we miss you. + +# Terminal escape codes +# +# Strings which punish the fools who use cat/type on this file + +Roses are red, violets are blue. Hope you enjoy terminal hue +But now...for my greatest trick... +The quick brown fox... [Beeeep] + +# iOS Vulnerability +# +# Strings which crashed iMessage in iOS versions 8.3 and earlier + +PowerÙ„ÙÙ„ÙصّبÙÙ„ÙلصّبÙررً ॣ ॣh ॣ ॣ冗 + + +\end{document} diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index d6678b9407..87f424b0f9 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -191,7 +191,7 @@ describe "CompileManager", -> it "should run the texcount command", -> @directory = "#{@Settings.path.compilesDir}/#{@project_id}" @file_path = "$COMPILE_DIR/#{@file_name}" - @command =[ "texcount", '-inc', @file_path, "-out=" + @file_path + ".wc"] + @command =[ "texcount", "-inc", @file_path, "-out=" + @file_path + ".wc"] @CommandRunner.run .calledWith(@project_id, @command, @directory, @timeout) From ddcfc3ee6167956c6a7843d37a01bae6ab4a3427 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 17 Sep 2015 10:30:12 +0100 Subject: [PATCH 110/754] lock down smoke test and metrics version --- services/clsi/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index fc75cede50..f2157177c4 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -15,10 +15,10 @@ "request": "~2.21.0", "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.0.0", "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", - "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git", + "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.3.0", "sequelize": "^2.1.3", "wrench": "~1.5.4", - "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git", + "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", "sqlite3": "~2.2.0", "express": "^4.2.0", "body-parser": "^1.2.0", From 3217b1d58ff9bcf2b99b33245313566444dc3aca Mon Sep 17 00:00:00 2001 From: Shane Kilkelly <shane@kilkelly.me> Date: Mon, 21 Sep 2015 14:04:08 +0100 Subject: [PATCH 111/754] When serving output files, intelligently determine the appropriate content-type. cherry pick 6fa3fda3ed28239cf3ac9720629f9707663aa197 from datajoy. --- services/clsi/app.coffee | 7 +-- .../clsi/app/coffee/ContentTypeMapper.coffee | 26 ++++++++++ .../unit/coffee/ContentTypeMapperTests.coffee | 51 +++++++++++++++++++ 3 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 services/clsi/app/coffee/ContentTypeMapper.coffee create mode 100644 services/clsi/test/unit/coffee/ContentTypeMapperTests.coffee diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 8b4e2a9dcd..bd0a5864a9 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -3,6 +3,7 @@ Settings = require "settings-sharelatex" logger = require "logger-sharelatex" logger.initialize("clsi") smokeTest = require "smoke-test-sharelatex" +ContentTypeMapper = require "./app/js/ContentTypeMapper" Path = require "path" fs = require "fs" @@ -46,17 +47,13 @@ ForbidSymlinks = require "./app/js/StaticServerForbidSymlinks" # and serving the files staticServer = ForbidSymlinks express.static, Settings.path.compilesDir, setHeaders: (res, path, stat) -> if Path.basename(path) == "output.pdf" - res.set("Content-Type", "application/pdf") # Calculate an etag in the same way as nginx # https://github.com/tj/send/issues/65 etag = (path, stat) -> '"' + Math.ceil(+stat.mtime / 1000).toString(16) + '-' + Number(stat.size).toString(16) + '"' res.set("Etag", etag(path, stat)) - else - # Force plain treatment of other file types to prevent hosting of HTTP/JS files - # that could be used in same-origin/XSS attacks. - res.set("Content-Type", "text/plain") + res.set("Content-Type", ContentTypeMapper.map(path)) app.get "/project/:project_id/output/*", (req, res, next) -> if req.query?.build? && req.query.build.match(OutputCacheManager.BUILD_REGEX) diff --git a/services/clsi/app/coffee/ContentTypeMapper.coffee b/services/clsi/app/coffee/ContentTypeMapper.coffee new file mode 100644 index 0000000000..4f0eb2d114 --- /dev/null +++ b/services/clsi/app/coffee/ContentTypeMapper.coffee @@ -0,0 +1,26 @@ +Path = require 'path' + +# here we coerce html, css and js to text/plain, +# otherwise choose correct mime type based on file extension, +# falling back to octet-stream +module.exports = ContentTypeMapper = + map: (path) -> + switch Path.extname(path) + when '.txt', '.html', '.js', '.css' + return 'text/plain' + when '.csv' + return 'text/csv' + when '.pdf' + return 'application/pdf' + when '.png' + return 'image/png' + when '.jpg', '.jpeg' + return 'image/jpeg' + when '.tiff' + return 'image/tiff' + when '.gif' + return 'image/gif' + when '.svg' + return 'image/svg+xml' + else + return 'application/octet-stream' diff --git a/services/clsi/test/unit/coffee/ContentTypeMapperTests.coffee b/services/clsi/test/unit/coffee/ContentTypeMapperTests.coffee new file mode 100644 index 0000000000..d201b8690a --- /dev/null +++ b/services/clsi/test/unit/coffee/ContentTypeMapperTests.coffee @@ -0,0 +1,51 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +require('chai').should() +modulePath = require('path').join __dirname, '../../../app/js/ContentTypeMapper' + +describe 'ContentTypeMapper', -> + + beforeEach -> + @ContentTypeMapper = SandboxedModule.require modulePath + + describe 'map', -> + + it 'should map .txt to text/plain', -> + content_type = @ContentTypeMapper.map('example.txt') + content_type.should.equal 'text/plain' + + it 'should map .csv to text/csv', -> + content_type = @ContentTypeMapper.map('example.csv') + content_type.should.equal 'text/csv' + + it 'should map .pdf to application/pdf', -> + content_type = @ContentTypeMapper.map('example.pdf') + content_type.should.equal 'application/pdf' + + it 'should fall back to octet-stream', -> + content_type = @ContentTypeMapper.map('example.unknown') + content_type.should.equal 'application/octet-stream' + + describe 'coercing web files to plain text', -> + + it 'should map .js to plain text', -> + content_type = @ContentTypeMapper.map('example.js') + content_type.should.equal 'text/plain' + + it 'should map .html to plain text', -> + content_type = @ContentTypeMapper.map('example.html') + content_type.should.equal 'text/plain' + + it 'should map .css to plain text', -> + content_type = @ContentTypeMapper.map('example.css') + content_type.should.equal 'text/plain' + + describe 'image files', -> + + it 'should map .png to image/png', -> + content_type = @ContentTypeMapper.map('example.png') + content_type.should.equal 'image/png' + + it 'should map .jpeg to image/jpeg', -> + content_type = @ContentTypeMapper.map('example.jpeg') + content_type.should.equal 'image/jpeg' From 069220dcc8ebb026f8f892be93959210397f70b0 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Wed, 21 Oct 2015 10:02:30 +0100 Subject: [PATCH 112/754] increased cache time to 1.5 days --- services/clsi/app/coffee/ProjectPersistenceManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.coffee b/services/clsi/app/coffee/ProjectPersistenceManager.coffee index 2e23d46cec..2767dce968 100644 --- a/services/clsi/app/coffee/ProjectPersistenceManager.coffee +++ b/services/clsi/app/coffee/ProjectPersistenceManager.coffee @@ -5,7 +5,7 @@ async = require "async" logger = require "logger-sharelatex" module.exports = ProjectPersistenceManager = - EXPIRY_TIMEOUT: oneDay = 24 * 60 * 60 * 1000 #ms + EXPIRY_TIMEOUT: (oneDay = 24 * 60 * 60 * 1000) * 1.5 #ms markProjectAsJustAccessed: (project_id, callback = (error) ->) -> db.Project.findOrCreate(where: {project_id: project_id}) From 253a99e78029ab1a5d1e5202e8eaab09ab2fabf8 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 12 Nov 2015 15:19:22 +0000 Subject: [PATCH 113/754] added try catch around word count where a file is not created --- services/clsi/app/coffee/CompileManager.coffee | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 296522f6b4..945da42e70 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -120,7 +120,12 @@ module.exports = CompileManager = CommandRunner.run project_id, command, directory, timeout, (error) -> return callback(error) if error? - stdout = fs.readFileSync(directory + "/" + file_name + ".wc", "utf-8") + try + stdout = fs.readFileSync(directory + "/" + file_name + ".wc", "utf-8") + catch err + logger.err err:err, command:command, directory:directory, project_id:project_id, "error reading word count output" + return callback(err) + console.log "rooooof" callback null, CompileManager._parseWordcountFromOutput(stdout) _parseWordcountFromOutput: (output) -> From 3b559d3966329db08cc97a1c078225caf55fa6d2 Mon Sep 17 00:00:00 2001 From: James Allen <james@sharelatex.com> Date: Thu, 3 Dec 2015 14:54:48 +0000 Subject: [PATCH 114/754] Remove undefined reference to dst --- services/clsi/app/coffee/OutputCacheManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee index 66abc4eaa8..5aca4a5cb2 100644 --- a/services/clsi/app/coffee/OutputCacheManager.coffee +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -40,7 +40,7 @@ module.exports = OutputCacheManager = callback(err) else if not stats.isFile() # other filetype - reject it - logger.error err: err, src: src, dst: dst, stat: stats, "nonfile output - refusing to copy to cache" + logger.error err: err, src: src, stat: stats, "nonfile output - refusing to copy to cache" callback(new Error("output file is not a file"), file) else # it's a plain file, ok to copy From e8e0b673eb0cd6953372d5c9adfed434f78c70b7 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 15 Dec 2015 19:33:37 +0000 Subject: [PATCH 115/754] fixed missing value in logger --- services/clsi/app/coffee/OutputCacheManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee index 66abc4eaa8..5aca4a5cb2 100644 --- a/services/clsi/app/coffee/OutputCacheManager.coffee +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -40,7 +40,7 @@ module.exports = OutputCacheManager = callback(err) else if not stats.isFile() # other filetype - reject it - logger.error err: err, src: src, dst: dst, stat: stats, "nonfile output - refusing to copy to cache" + logger.error err: err, src: src, stat: stats, "nonfile output - refusing to copy to cache" callback(new Error("output file is not a file"), file) else # it's a plain file, ok to copy From d924da0d2deddff0ccbc3928991d0a6df07215ed Mon Sep 17 00:00:00 2001 From: James Allen <james@sharelatex.com> Date: Tue, 12 Jan 2016 17:04:42 +0000 Subject: [PATCH 116/754] Allow optional image name to be passed --- services/clsi/app/coffee/CommandRunner.coffee | 2 +- services/clsi/app/coffee/CompileManager.coffee | 1 + services/clsi/app/coffee/LatexRunner.coffee | 4 ++-- services/clsi/app/coffee/RequestParser.coffee | 4 +++- services/clsi/test/unit/coffee/CompileManagerTests.coffee | 2 ++ services/clsi/test/unit/coffee/LatexRunnerTests.coffee | 7 +++++-- 6 files changed, 14 insertions(+), 6 deletions(-) diff --git a/services/clsi/app/coffee/CommandRunner.coffee b/services/clsi/app/coffee/CommandRunner.coffee index 55dec333d6..41cbfaa8ef 100644 --- a/services/clsi/app/coffee/CommandRunner.coffee +++ b/services/clsi/app/coffee/CommandRunner.coffee @@ -2,7 +2,7 @@ spawn = require("child_process").spawn logger = require "logger-sharelatex" module.exports = CommandRunner = - run: (project_id, command, directory, timeout, callback = (error) ->) -> + run: (project_id, command, directory, image, timeout, callback = (error) ->) -> command = (arg.replace('$COMPILE_DIR', directory) for arg in command) logger.log project_id: project_id, command: command, directory: directory, "running command" logger.warn "timeouts and sandboxing are not enabled with CommandRunner" diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 945da42e70..e7e884998e 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -28,6 +28,7 @@ module.exports = CompileManager = mainFile: request.rootResourcePath compiler: request.compiler timeout: request.timeout + image: request.imageName }, (error) -> return callback(error) if error? logger.log project_id: request.project_id, time_taken: Date.now() - timer.start, "done compile" diff --git a/services/clsi/app/coffee/LatexRunner.coffee b/services/clsi/app/coffee/LatexRunner.coffee index cd6e356195..169a216f90 100644 --- a/services/clsi/app/coffee/LatexRunner.coffee +++ b/services/clsi/app/coffee/LatexRunner.coffee @@ -6,7 +6,7 @@ CommandRunner = require(Settings.clsi?.commandRunner or "./CommandRunner") module.exports = LatexRunner = runLatex: (project_id, options, callback = (error) ->) -> - {directory, mainFile, compiler, timeout} = options + {directory, mainFile, compiler, timeout, image} = options compiler ||= "pdflatex" timeout ||= 60000 # milliseconds @@ -27,7 +27,7 @@ module.exports = LatexRunner = else return callback new Error("unknown compiler: #{compiler}") - CommandRunner.run project_id, command, directory, timeout, callback + CommandRunner.run project_id, command, directory, image, timeout, callback _latexmkBaseCommand: [ "latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR"] diff --git a/services/clsi/app/coffee/RequestParser.coffee b/services/clsi/app/coffee/RequestParser.coffee index 53268103a2..1b4e06d9d8 100644 --- a/services/clsi/app/coffee/RequestParser.coffee +++ b/services/clsi/app/coffee/RequestParser.coffee @@ -21,6 +21,9 @@ module.exports = RequestParser = compile.options.timeout default: RequestParser.MAX_TIMEOUT type: "number" + response.imageName = @_parseAttribute "imageName", + compile.options.imageName, + type: "string" if response.timeout > RequestParser.MAX_TIMEOUT response.timeout = RequestParser.MAX_TIMEOUT @@ -71,7 +74,6 @@ module.exports = RequestParser = throw "#{name} attribute should be a #{options.type}" else return options.default if options.default? - throw "Default not implemented" return attribute _sanitizePath: (path) -> diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 87f424b0f9..dff30524f1 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -44,6 +44,7 @@ describe "CompileManager", -> project_id: @project_id = "project-id-123" compiler: @compiler = "pdflatex" timeout: @timeout = 42000 + imageName: @image = "example.com/image" @Settings.compileDir = "compiles" @compileDir = "#{@Settings.path.compilesDir}/#{@project_id}" @ResourceWriter.syncResourcesToDisk = sinon.stub().callsArg(3) @@ -64,6 +65,7 @@ describe "CompileManager", -> mainFile: @rootResourcePath compiler: @compiler timeout: @timeout + image: @image }) .should.equal true diff --git a/services/clsi/test/unit/coffee/LatexRunnerTests.coffee b/services/clsi/test/unit/coffee/LatexRunnerTests.coffee index d3782a6f3c..ace3d18ab7 100644 --- a/services/clsi/test/unit/coffee/LatexRunnerTests.coffee +++ b/services/clsi/test/unit/coffee/LatexRunnerTests.coffee @@ -19,12 +19,13 @@ describe "LatexRunner", -> @directory = "/local/compile/directory" @mainFile = "main-file.tex" @compiler = "pdflatex" + @image = "example.com/image" @callback = sinon.stub() @project_id = "project-id-123" describe "runLatex", -> beforeEach -> - @CommandRunner.run = sinon.stub().callsArg(4) + @CommandRunner.run = sinon.stub().callsArg(5) describe "normally", -> beforeEach -> @@ -33,11 +34,12 @@ describe "LatexRunner", -> mainFile: @mainFile compiler: @compiler timeout: @timeout = 42000 + image: @image @callback it "should run the latex command", -> @CommandRunner.run - .calledWith(@project_id, sinon.match.any, @directory, @timeout) + .calledWith(@project_id, sinon.match.any, @directory, @image, @timeout) .should.equal true describe "with an .Rtex main file", -> @@ -46,6 +48,7 @@ describe "LatexRunner", -> directory: @directory mainFile: "main-file.Rtex" compiler: @compiler + image: @image timeout: @timeout = 42000 @callback From de280d0ed437dddd3d01bb6cb6c3a0fc8bbc3e9f Mon Sep 17 00:00:00 2001 From: James Allen <james@sharelatex.com> Date: Tue, 19 Jan 2016 14:12:41 +0000 Subject: [PATCH 117/754] Support configurable images in wordcount end point --- services/clsi/app/coffee/CompileController.coffee | 4 +++- services/clsi/app/coffee/CompileManager.coffee | 6 +++--- .../clsi/test/unit/coffee/CompileControllerTests.coffee | 5 +++-- services/clsi/test/unit/coffee/CompileManagerTests.coffee | 7 ++++--- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index f7d46b1305..dd73040958 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -70,8 +70,10 @@ module.exports = CompileController = wordcount: (req, res, next = (error) ->) -> file = req.query.file || "main.tex" project_id = req.params.project_id + image = req.query.image + logger.log {image, file, project_id}, "word count request" - CompileManager.wordcount project_id, file, (error, result) -> + CompileManager.wordcount project_id, file, image, (error, result) -> return next(error) if error? res.send JSON.stringify { texcount: result diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index e7e884998e..20e9bc964e 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -112,14 +112,14 @@ module.exports = CompileManager = } return results - wordcount: (project_id, file_name, callback = (error, pdfPositions) ->) -> - logger.log project_id:project_id, file_name:file_name, "running wordcount" + wordcount: (project_id, file_name, image, callback = (error, pdfPositions) ->) -> + logger.log project_id:project_id, file_name:file_name, image:image, "running wordcount" file_path = "$COMPILE_DIR/" + file_name command = [ "texcount", '-inc', file_path, "-out=" + file_path + ".wc"] directory = Path.join(Settings.path.compilesDir, project_id) timeout = 10 * 1000 - CommandRunner.run project_id, command, directory, timeout, (error) -> + CommandRunner.run project_id, command, directory, image, timeout, (error) -> return callback(error) if error? try stdout = fs.readFileSync(directory + "/" + file_name + ".wc", "utf-8") diff --git a/services/clsi/test/unit/coffee/CompileControllerTests.coffee b/services/clsi/test/unit/coffee/CompileControllerTests.coffee index e0307d20c9..6a18ce795f 100644 --- a/services/clsi/test/unit/coffee/CompileControllerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileControllerTests.coffee @@ -197,14 +197,15 @@ describe "CompileController", -> project_id: @project_id @req.query = file: @file + image: @image = "example.com/image" @res.send = sinon.stub() - @CompileManager.wordcount = sinon.stub().callsArgWith(2, null, @texcount = ["mock-texcount"]) + @CompileManager.wordcount = sinon.stub().callsArgWith(3, null, @texcount = ["mock-texcount"]) @CompileController.wordcount @req, @res, @next it "should return the word count of a file", -> @CompileManager.wordcount - .calledWith(@project_id, @file) + .calledWith(@project_id, @file, @image) .should.equal true it "should return the texcount info", -> diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index dff30524f1..54576d8ea8 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -179,7 +179,7 @@ describe "CompileManager", -> describe "wordcount", -> beforeEach -> - @CommandRunner.run = sinon.stub().callsArg(4) + @CommandRunner.run = sinon.stub().callsArg(5) @fs.readFileSync = sinon.stub().returns @stdout = "Encoding: ascii\nWords in text: 2" @callback = sinon.stub() @@ -187,8 +187,9 @@ describe "CompileManager", -> @timeout = 10 * 1000 @file_name = "main.tex" @Settings.path.compilesDir = "/local/compile/directory" + @image = "example.com/image" - @CompileManager.wordcount @project_id, @file_name, @callback + @CompileManager.wordcount @project_id, @file_name, @image, @callback it "should run the texcount command", -> @directory = "#{@Settings.path.compilesDir}/#{@project_id}" @@ -196,7 +197,7 @@ describe "CompileManager", -> @command =[ "texcount", "-inc", @file_path, "-out=" + @file_path + ".wc"] @CommandRunner.run - .calledWith(@project_id, @command, @directory, @timeout) + .calledWith(@project_id, @command, @directory, @image, @timeout) .should.equal true it "should call the callback with the parsed output", -> From 37dbdc21bb84478cc8ff6215290b2ed28063aabc Mon Sep 17 00:00:00 2001 From: James Allen <james@sharelatex.com> Date: Mon, 1 Feb 2016 13:19:16 +0000 Subject: [PATCH 118/754] Download up to 5 files in parallel --- services/clsi/app/coffee/ResourceWriter.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index 25b2625f19..a8b010665a 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -14,7 +14,7 @@ module.exports = ResourceWriter = jobs = for resource in resources do (resource) => (callback) => @_writeResourceToDisk(project_id, resource, basePath, callback) - async.series jobs, callback + async.parallelLimit jobs, 5, callback _removeExtraneousFiles: (resources, basePath, _callback = (error) ->) -> timer = new Metrics.Timer("unlink-output-files") From 0f55ce18ac26e91d816052f918ab1c5bc3d8432a Mon Sep 17 00:00:00 2001 From: James Allen <james@sharelatex.com> Date: Tue, 2 Feb 2016 14:26:14 +0000 Subject: [PATCH 119/754] Inject [draft] option to documentclass if draft option is passed --- .../clsi/app/coffee/CompileController.coffee | 1 + .../clsi/app/coffee/CompileManager.coffee | 41 ++++++++----- .../clsi/app/coffee/DraftModeManager.coffee | 21 +++++++ services/clsi/app/coffee/RequestParser.coffee | 4 ++ .../unit/coffee/CompileManagerTests.coffee | 60 +++++++++++------- .../unit/coffee/DraftModeManagerTests.coffee | 61 +++++++++++++++++++ 6 files changed, 151 insertions(+), 37 deletions(-) create mode 100644 services/clsi/app/coffee/DraftModeManager.coffee create mode 100644 services/clsi/test/unit/coffee/DraftModeManagerTests.coffee diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index dd73040958..7ee9eefb8b 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -11,6 +11,7 @@ module.exports = CompileController = RequestParser.parse req.body, (error, request) -> return next(error) if error? request.project_id = req.params.project_id + logger.log {request}, "got request" ProjectPersistenceManager.markProjectAsJustAccessed request.project_id, (error) -> return next(error) if error? CompileManager.doCompile request, (error, outputFiles = []) -> diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 20e9bc964e..82ac793dd7 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -8,6 +8,7 @@ logger = require "logger-sharelatex" Metrics = require "./Metrics" child_process = require "child_process" CommandRunner = require(Settings.clsi?.commandRunner or "./CommandRunner") +DraftModeManager = require "./DraftModeManager" fs = require("fs") module.exports = CompileManager = @@ -20,24 +21,32 @@ module.exports = CompileManager = return callback(error) if error? logger.log project_id: request.project_id, time_taken: Date.now() - timer.start, "written files to disk" timer.done() - - timer = new Metrics.Timer("run-compile") - Metrics.inc("compiles") - LatexRunner.runLatex request.project_id, { - directory: compileDir - mainFile: request.rootResourcePath - compiler: request.compiler - timeout: request.timeout - image: request.imageName - }, (error) -> + + injectDraftModeIfRequired = (callback) -> + if request.draft + DraftModeManager.injectDraftMode Path.join(compileDir, request.rootResourcePath), callback + else + callback() + + injectDraftModeIfRequired (error) -> return callback(error) if error? - logger.log project_id: request.project_id, time_taken: Date.now() - timer.start, "done compile" - timer.done() - - OutputFileFinder.findOutputFiles request.resources, compileDir, (error, outputFiles) -> + timer = new Metrics.Timer("run-compile") + Metrics.inc("compiles") + LatexRunner.runLatex request.project_id, { + directory: compileDir + mainFile: request.rootResourcePath + compiler: request.compiler + timeout: request.timeout + image: request.imageName + }, (error) -> return callback(error) if error? - OutputCacheManager.saveOutputFiles outputFiles, compileDir, (error, newOutputFiles) -> - callback null, newOutputFiles + logger.log project_id: request.project_id, time_taken: Date.now() - timer.start, "done compile" + timer.done() + + OutputFileFinder.findOutputFiles request.resources, compileDir, (error, outputFiles) -> + return callback(error) if error? + OutputCacheManager.saveOutputFiles outputFiles, compileDir, (error, newOutputFiles) -> + callback null, newOutputFiles clearProject: (project_id, _callback = (error) ->) -> callback = (error) -> diff --git a/services/clsi/app/coffee/DraftModeManager.coffee b/services/clsi/app/coffee/DraftModeManager.coffee new file mode 100644 index 0000000000..a0d859d728 --- /dev/null +++ b/services/clsi/app/coffee/DraftModeManager.coffee @@ -0,0 +1,21 @@ +fs = require "fs" +logger = require "logger-sharelatex" + +module.exports = DraftModeManager = + injectDraftMode: (filename, callback = (error) ->) -> + fs.readFile filename, "utf8", (error, content) -> + return callback(error) if error? + modified_content = DraftModeManager._injectDraftOption content + logger.log { + content: content.slice(0,1024), # \documentclass is normally v near the top + modified_content: modified_content.slice(0,1024), + filename + }, "injected draft class" + fs.writeFile filename, modified_content, callback + + _injectDraftOption: (content) -> + content + # With existing options (must be first, otherwise both are applied) + .replace(/\\documentclass\[/, "\\documentclass[draft,") + # Without existing options + .replace(/\\documentclass\{/, "\\documentclass[draft]{") \ No newline at end of file diff --git a/services/clsi/app/coffee/RequestParser.coffee b/services/clsi/app/coffee/RequestParser.coffee index 1b4e06d9d8..bd081fd011 100644 --- a/services/clsi/app/coffee/RequestParser.coffee +++ b/services/clsi/app/coffee/RequestParser.coffee @@ -24,6 +24,10 @@ module.exports = RequestParser = response.imageName = @_parseAttribute "imageName", compile.options.imageName, type: "string" + response.draft = @_parseAttribute "draft", + compile.options.draft, + default: false, + type: "boolean" if response.timeout > RequestParser.MAX_TIMEOUT response.timeout = RequestParser.MAX_TIMEOUT diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 54576d8ea8..018e01d245 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -17,6 +17,7 @@ describe "CompileManager", -> "logger-sharelatex": @logger = { log: sinon.stub() } "child_process": @child_process = {} "./CommandRunner": @CommandRunner = {} + "./DraftModeManager": @DraftModeManager = {} "fs": @fs = {} @callback = sinon.stub() @@ -51,31 +52,48 @@ describe "CompileManager", -> @LatexRunner.runLatex = sinon.stub().callsArg(2) @OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, @output_files) @OutputCacheManager.saveOutputFiles = sinon.stub().callsArgWith(2, null, @build_files) - @CompileManager.doCompile @request, @callback + @DraftModeManager.injectDraftMode = sinon.stub().callsArg(1) + + describe "normally", -> + beforeEach -> + @CompileManager.doCompile @request, @callback - it "should write the resources to disk", -> - @ResourceWriter.syncResourcesToDisk - .calledWith(@project_id, @resources, @compileDir) - .should.equal true + it "should write the resources to disk", -> + @ResourceWriter.syncResourcesToDisk + .calledWith(@project_id, @resources, @compileDir) + .should.equal true - it "should run LaTeX", -> - @LatexRunner.runLatex - .calledWith(@project_id, { - directory: @compileDir - mainFile: @rootResourcePath - compiler: @compiler - timeout: @timeout - image: @image - }) - .should.equal true + it "should run LaTeX", -> + @LatexRunner.runLatex + .calledWith(@project_id, { + directory: @compileDir + mainFile: @rootResourcePath + compiler: @compiler + timeout: @timeout + image: @image + }) + .should.equal true - it "should find the output files", -> - @OutputFileFinder.findOutputFiles - .calledWith(@resources, @compileDir) - .should.equal true + it "should find the output files", -> + @OutputFileFinder.findOutputFiles + .calledWith(@resources, @compileDir) + .should.equal true - it "should return the output files", -> - @callback.calledWith(null, @build_files).should.equal true + it "should return the output files", -> + @callback.calledWith(null, @build_files).should.equal true + + it "should not inject draft mode by default", -> + @DraftModeManager.injectDraftMode.called.should.equal false + + describe "with draft mode", -> + beforeEach -> + @request.draft = true + @CompileManager.doCompile @request, @callback + + it "should inject the draft mode header", -> + @DraftModeManager.injectDraftMode + .calledWith(@compileDir + "/" + @rootResourcePath) + .should.equal true describe "clearProject", -> describe "succesfully", -> diff --git a/services/clsi/test/unit/coffee/DraftModeManagerTests.coffee b/services/clsi/test/unit/coffee/DraftModeManagerTests.coffee new file mode 100644 index 0000000000..549be29310 --- /dev/null +++ b/services/clsi/test/unit/coffee/DraftModeManagerTests.coffee @@ -0,0 +1,61 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +require('chai').should() +modulePath = require('path').join __dirname, '../../../app/js/DraftModeManager' + +describe 'DraftModeManager', -> + beforeEach -> + @DraftModeManager = SandboxedModule.require modulePath, requires: + "fs": @fs = {} + "logger-sharelatex": @logger = {log: () ->} + + describe "_injectDraftOption", -> + it "should add draft option into documentclass with existing options", -> + @DraftModeManager + ._injectDraftOption(''' + \\documentclass[a4paper,foo=bar]{article} + ''') + .should.equal(''' + \\documentclass[draft,a4paper,foo=bar]{article} + ''') + + it "should add draft option into documentclass with no options", -> + @DraftModeManager + ._injectDraftOption(''' + \\documentclass{article} + ''') + .should.equal(''' + \\documentclass[draft]{article} + ''') + + describe "injectDraftMode", -> + beforeEach -> + @filename = "/mock/filename.tex" + @callback = sinon.stub() + content = ''' + \\documentclass{article} + \\begin{document} + Hello world + \\end{document} + ''' + @fs.readFile = sinon.stub().callsArgWith(2, null, content) + @fs.writeFile = sinon.stub().callsArg(2) + @DraftModeManager.injectDraftMode @filename, @callback + + it "should read the file", -> + @fs.readFile + .calledWith(@filename, "utf8") + .should.equal true + + it "should write the modified file", -> + @fs.writeFile + .calledWith(@filename, """ + \\documentclass[draft]{article} + \\begin{document} + Hello world + \\end{document} + """) + .should.equal true + + it "should call the callback", -> + @callback.called.should.equal true From 97b5ac6a7f785476ce393ec8f55cb888f0fe957b Mon Sep 17 00:00:00 2001 From: James Allen <james@sharelatex.com> Date: Tue, 2 Feb 2016 14:28:51 +0000 Subject: [PATCH 120/754] Remove left over debug log line --- services/clsi/app/coffee/CompileController.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 7ee9eefb8b..dd73040958 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -11,7 +11,6 @@ module.exports = CompileController = RequestParser.parse req.body, (error, request) -> return next(error) if error? request.project_id = req.params.project_id - logger.log {request}, "got request" ProjectPersistenceManager.markProjectAsJustAccessed request.project_id, (error) -> return next(error) if error? CompileManager.doCompile request, (error, outputFiles = []) -> From 263ee43cb56f369688919725db6f0124c9b52bce Mon Sep 17 00:00:00 2001 From: James Allen <james@sharelatex.com> Date: Tue, 2 Feb 2016 15:28:59 +0000 Subject: [PATCH 121/754] Make draft mode regex global --- services/clsi/app/coffee/DraftModeManager.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/DraftModeManager.coffee b/services/clsi/app/coffee/DraftModeManager.coffee index a0d859d728..253cbffdd3 100644 --- a/services/clsi/app/coffee/DraftModeManager.coffee +++ b/services/clsi/app/coffee/DraftModeManager.coffee @@ -16,6 +16,6 @@ module.exports = DraftModeManager = _injectDraftOption: (content) -> content # With existing options (must be first, otherwise both are applied) - .replace(/\\documentclass\[/, "\\documentclass[draft,") + .replace(/\\documentclass\[/g, "\\documentclass[draft,") # Without existing options - .replace(/\\documentclass\{/, "\\documentclass[draft]{") \ No newline at end of file + .replace(/\\documentclass\{/g, "\\documentclass[draft]{") \ No newline at end of file From 320e2257005fc77e3d891e229fa9012f8b26f892 Mon Sep 17 00:00:00 2001 From: James Allen <james@sharelatex.com> Date: Thu, 10 Mar 2016 09:32:32 +0000 Subject: [PATCH 122/754] Send .svg files as text/plain to prevent executable JS if they are loaded as SVG in the browser --- services/clsi/app/coffee/ContentTypeMapper.coffee | 4 +--- services/clsi/test/unit/coffee/ContentTypeMapperTests.coffee | 4 ++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/services/clsi/app/coffee/ContentTypeMapper.coffee b/services/clsi/app/coffee/ContentTypeMapper.coffee index 4f0eb2d114..68b2d14f38 100644 --- a/services/clsi/app/coffee/ContentTypeMapper.coffee +++ b/services/clsi/app/coffee/ContentTypeMapper.coffee @@ -6,7 +6,7 @@ Path = require 'path' module.exports = ContentTypeMapper = map: (path) -> switch Path.extname(path) - when '.txt', '.html', '.js', '.css' + when '.txt', '.html', '.js', '.css', '.svg' return 'text/plain' when '.csv' return 'text/csv' @@ -20,7 +20,5 @@ module.exports = ContentTypeMapper = return 'image/tiff' when '.gif' return 'image/gif' - when '.svg' - return 'image/svg+xml' else return 'application/octet-stream' diff --git a/services/clsi/test/unit/coffee/ContentTypeMapperTests.coffee b/services/clsi/test/unit/coffee/ContentTypeMapperTests.coffee index d201b8690a..2439120b16 100644 --- a/services/clsi/test/unit/coffee/ContentTypeMapperTests.coffee +++ b/services/clsi/test/unit/coffee/ContentTypeMapperTests.coffee @@ -49,3 +49,7 @@ describe 'ContentTypeMapper', -> it 'should map .jpeg to image/jpeg', -> content_type = @ContentTypeMapper.map('example.jpeg') content_type.should.equal 'image/jpeg' + + it 'should map .svg to text/plain to protect against XSS (SVG can execute JS)', -> + content_type = @ContentTypeMapper.map('example.svg') + content_type.should.equal 'text/plain' From 124c91e30215878ae5fb011e788fd11415ce1913 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 10 Mar 2016 10:30:37 +0000 Subject: [PATCH 123/754] increased EXPIRY_TIMEOUT from 1.5 days to 2.5 days --- services/clsi/app/coffee/ProjectPersistenceManager.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.coffee b/services/clsi/app/coffee/ProjectPersistenceManager.coffee index 2767dce968..9f11cf312d 100644 --- a/services/clsi/app/coffee/ProjectPersistenceManager.coffee +++ b/services/clsi/app/coffee/ProjectPersistenceManager.coffee @@ -3,9 +3,11 @@ CompileManager = require "./CompileManager" db = require "./db" async = require "async" logger = require "logger-sharelatex" +oneDay = 24 * 60 * 60 * 1000 module.exports = ProjectPersistenceManager = - EXPIRY_TIMEOUT: (oneDay = 24 * 60 * 60 * 1000) * 1.5 #ms + + EXPIRY_TIMEOUT: oneDay * 2.5 markProjectAsJustAccessed: (project_id, callback = (error) ->) -> db.Project.findOrCreate(where: {project_id: project_id}) From a26f4fc5f6200fa244c75860d55f3cbd1f82effe Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 15 Mar 2016 14:04:37 +0000 Subject: [PATCH 124/754] add metrics for latexmk runs and errors --- services/clsi/app/coffee/CompileManager.coffee | 14 +++++++++++--- services/clsi/app/coffee/LatexRunner.coffee | 15 +++++++++++++-- services/clsi/package.json | 2 +- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 82ac793dd7..f33301821f 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -10,6 +10,7 @@ child_process = require "child_process" CommandRunner = require(Settings.clsi?.commandRunner or "./CommandRunner") DraftModeManager = require "./DraftModeManager" fs = require("fs") +os = require("os") module.exports = CompileManager = doCompile: (request, callback = (error, outputFiles) ->) -> @@ -38,10 +39,17 @@ module.exports = CompileManager = compiler: request.compiler timeout: request.timeout image: request.imageName - }, (error) -> + }, (error, output, stats) -> return callback(error) if error? - logger.log project_id: request.project_id, time_taken: Date.now() - timer.start, "done compile" - timer.done() + Metrics.inc("compiles") + for metric_key, metric_value of stats or {} + Metrics.count(metric_key, metric_value) + loadavg = os.loadavg?() + Metrics.gauge("load-avg", loadavg[0]) if loadavg? + ts = timer.done() + logger.log {project_id: request.project_id, time_taken: ts, stats:stats, loadavg:loadavg}, "done compile" + if stats?["latex-runs"] > 0 + Metrics.timing("run-compile-per-pass", ts / stats["latex-runs"]) OutputFileFinder.findOutputFiles request.resources, compileDir, (error, outputFiles) -> return callback(error) if error? diff --git a/services/clsi/app/coffee/LatexRunner.coffee b/services/clsi/app/coffee/LatexRunner.coffee index 169a216f90..1cd851bad8 100644 --- a/services/clsi/app/coffee/LatexRunner.coffee +++ b/services/clsi/app/coffee/LatexRunner.coffee @@ -27,9 +27,20 @@ module.exports = LatexRunner = else return callback new Error("unknown compiler: #{compiler}") - CommandRunner.run project_id, command, directory, image, timeout, callback + CommandRunner.run project_id, command, directory, image, timeout, (error, output) -> + return callback(error) if error? + runs = output?.stderr?.match(/^Run number \d+ of .*latex/mg)?.length or 0 + failed = if output?.stdout?.match(/^Latexmk: Errors/m)? then 1 else 0 + # counters from latexmk output + stats = {} + stats["latexmk-errors"] = failed + stats["latex-runs"] = runs + stats["latex-runs-with-errors"] = if failed then runs else 0 + stats["latex-runs-#{runs}"] = 1 + stats["latex-runs-with-errors-#{runs}"] = if failed then 1 else 0 + callback error, output, stats - _latexmkBaseCommand: [ "latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR"] + _latexmkBaseCommand: ["latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR"] _pdflatexCommand: (mainFile) -> LatexRunner._latexmkBaseCommand.concat [ diff --git a/services/clsi/package.json b/services/clsi/package.json index f2157177c4..33560b5997 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -15,7 +15,7 @@ "request": "~2.21.0", "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.0.0", "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", - "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.3.0", + "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.5.0", "sequelize": "^2.1.3", "wrench": "~1.5.4", "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", From 456a8ca8cd27a805c33168476e68bb9fc9d01917 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Wed, 16 Mar 2016 15:49:13 +0000 Subject: [PATCH 125/754] add metric for qpdf --- services/clsi/app/coffee/OutputFileOptimiser.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/clsi/app/coffee/OutputFileOptimiser.coffee b/services/clsi/app/coffee/OutputFileOptimiser.coffee index f337a7a953..eb5266e161 100644 --- a/services/clsi/app/coffee/OutputFileOptimiser.coffee +++ b/services/clsi/app/coffee/OutputFileOptimiser.coffee @@ -2,6 +2,7 @@ fs = require "fs" Path = require "path" spawn = require("child_process").spawn logger = require "logger-sharelatex" +Metrics = require "./Metrics" _ = require "underscore" module.exports = OutputFileOptimiser = @@ -19,6 +20,7 @@ module.exports = OutputFileOptimiser = args = ["--linearize", src, tmpOutput] logger.log args: args, "running qpdf command" + timer = new Metrics.Timer("qpdf") proc = spawn("qpdf", args) stdout = "" proc.stdout.on "data", (chunk) -> @@ -28,6 +30,7 @@ module.exports = OutputFileOptimiser = logger.warn {err, args}, "qpdf failed" callback(null) # ignore the error proc.on "close", (code) -> + timer.done() if code != 0 logger.warn {code, args}, "qpdf returned error" return callback(null) # ignore the error From 285546fbcf5f2fcc7478fd59e09a217b9baa572b Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 17 Mar 2016 14:37:34 +0000 Subject: [PATCH 126/754] bugfix - avoid double counting compiles --- services/clsi/app/coffee/CompileManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index f33301821f..a193ac9fb9 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -41,7 +41,7 @@ module.exports = CompileManager = image: request.imageName }, (error, output, stats) -> return callback(error) if error? - Metrics.inc("compiles") + Metrics.inc("compiles-succeeded") for metric_key, metric_value of stats or {} Metrics.count(metric_key, metric_value) loadavg = os.loadavg?() From da7b8b99fb020e2b9a689c17fc4ad0272ec70918 Mon Sep 17 00:00:00 2001 From: James Allen <james@sharelatex.com> Date: Tue, 29 Mar 2016 16:45:06 +0100 Subject: [PATCH 127/754] Add in flags to run strace and capture logs --- services/clsi/app/coffee/LatexRunner.coffee | 3 + .../clsi/app/coffee/OutputCacheManager.coffee | 101 +++++++++++++----- 2 files changed, 76 insertions(+), 28 deletions(-) diff --git a/services/clsi/app/coffee/LatexRunner.coffee b/services/clsi/app/coffee/LatexRunner.coffee index 1cd851bad8..938d4a51ba 100644 --- a/services/clsi/app/coffee/LatexRunner.coffee +++ b/services/clsi/app/coffee/LatexRunner.coffee @@ -26,6 +26,9 @@ module.exports = LatexRunner = command = LatexRunner._lualatexCommand mainFile else return callback new Error("unknown compiler: #{compiler}") + + if Settings.clsi?.strace + command = ["strace", "-o", "strace-#{Date.now()}", "-ff"].concat(command) CommandRunner.run project_id, command, directory, image, timeout, (error, output) -> return callback(error) if error? diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee index 5aca4a5cb2..1517ccc367 100644 --- a/services/clsi/app/coffee/OutputCacheManager.coffee +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -4,11 +4,13 @@ fse = require "fs-extra" Path = require "path" logger = require "logger-sharelatex" _ = require "underscore" +Settings = require "settings-sharelatex" OutputFileOptimiser = require "./OutputFileOptimiser" module.exports = OutputCacheManager = CACHE_SUBDIR: '.cache/clsi' + ARCHIVE_SUBDIR: '.archive/clsi' BUILD_REGEX: /^[0-9a-f]+$/ # build id is Date.now() converted to hex CACHE_LIMIT: 2 # maximum number of cache directories CACHE_AGE: 60*60*1000 # up to one hour old @@ -28,33 +30,15 @@ module.exports = OutputCacheManager = # Put the files into a new cache subdirectory buildId = Date.now().toString(16) cacheDir = Path.join(compileDir, OutputCacheManager.CACHE_SUBDIR, buildId) + # let file expiry run in the background OutputCacheManager.expireOutputFiles cacheRoot, {keep: buildId} - checkFile = (src, callback) -> - # check if we have a valid file to copy into the cache - fs.stat src, (err, stats) -> + # Archive logs in background + if Settings.clsi?.archive_logs + OutputCacheManager.archiveLogs outputFiles, compileDir, (err) -> if err? - # some problem reading the file - logger.error err: err, file: src, "stat error for file in cache" - callback(err) - else if not stats.isFile() - # other filetype - reject it - logger.error err: err, src: src, stat: stats, "nonfile output - refusing to copy to cache" - callback(new Error("output file is not a file"), file) - else - # it's a plain file, ok to copy - callback(null) - - copyFile = (src, dst, callback) -> - # copy output file into the cache - fse.copy src, dst, (err) -> - if err? - logger.error err: err, src: src, dst: dst, "copy error for file in cache" - callback(err) - else - # call the optimiser for the file too - OutputFileOptimiser.optimiseFile src, dst, callback + logger.warn err:err, "erroring archiving log files" # make the new cache directory fse.ensureDir cacheDir, (err) -> @@ -63,21 +47,47 @@ module.exports = OutputCacheManager = callback(err, outputFiles) else # copy all the output files into the new cache directory + results = [] async.mapSeries outputFiles, (file, cb) -> newFile = _.clone(file) [src, dst] = [Path.join(compileDir, file.path), Path.join(cacheDir, file.path)] - checkFile src, (err) -> - copyFile src, dst, (err) -> - if not err? + OutputCacheManager._checkFileIsSafe src, (err, isSafe) -> + return cb(err) if err? + if !isSafe + return cb() + OutputCacheManager._checkIfShouldCopy src, (err, shouldCopy) -> + return cb(err) if err? + if !shouldCopy + return cb() + OutputCacheManager._copyFile src, dst, (err) -> + return cb(err) if err? newFile.build = buildId # attach a build id if we cached the file - cb(err, newFile) - , (err, results) -> + results.push newFile + cb() + , (err) -> if err? # pass back the original files if we encountered *any* error callback(err, outputFiles) else # pass back the list of new files in the cache callback(err, results) + + archiveLogs: (outputFiles, compileDir, callback = (error) ->) -> + buildId = Date.now().toString(16) + archiveDir = Path.join(compileDir, OutputCacheManager.ARCHIVE_SUBDIR, buildId) + logger.log {dir: archiveDir}, "archiving log files for project" + fse.ensureDir archiveDir, (err) -> + return callback(err) if err? + async.mapSeries outputFiles, (file, cb) -> + [src, dst] = [Path.join(compileDir, file.path), Path.join(archiveDir, file.path)] + OutputCacheManager._checkFileIsSafe src, (err, isSafe) -> + return cb(err) if err? + return cb() if !isSafe + OutputCacheManager._checkIfShouldArchive src, (err, shouldArchive) -> + return cb(err) if err? + return cb() if !shouldArchive + OutputCacheManager._copyFile src, dst, cb + , callback expireOutputFiles: (cacheRoot, options, callback = (error) ->) -> # look in compileDir for build dirs and delete if > N or age of mod time > T @@ -111,3 +121,38 @@ module.exports = OutputCacheManager = async.eachSeries toRemove, (dir, cb) -> removeDir dir, cb , callback + + _checkFileIsSafe: (src, callback = (error, isSafe) ->) -> + # check if we have a valid file to copy into the cache + fs.stat src, (err, stats) -> + if err? + # some problem reading the file + logger.error err: err, file: src, "stat error for file in cache" + callback(err, false) + else if not stats.isFile() + # other filetype - reject it + logger.warn src: src, stat: stats, "nonfile output - refusing to copy to cache" + callback(null, false) + else + # it's a plain file, ok to copy + callback(null, true) + + _copyFile: (src, dst, callback) -> + # copy output file into the cache + fse.copy src, dst, (err) -> + if err? + logger.error err: err, src: src, dst: dst, "copy error for file in cache" + callback(err) + else + # call the optimiser for the file too + OutputFileOptimiser.optimiseFile src, dst, callback + + _checkIfShouldCopy: (src, callback = (err, shouldCopy) ->) -> + return callback(null, !Path.basename(src).match(/^strace/)) + + _checkIfShouldArchive: (src, callback = (err, shouldCopy) ->) -> + if Path.basename(src).match(/^strace/) + return callback(null, true) + if Path.basename(src).match(/^output\.(?!pdf)/) + return callback(null, true) + return callback(null, false) \ No newline at end of file From 66fa6a3e2ec0cdb227b80c158404cf29023c2d5b Mon Sep 17 00:00:00 2001 From: James Allen <james@sharelatex.com> Date: Wed, 30 Mar 2016 10:59:01 +0100 Subject: [PATCH 128/754] Don't timestamp strace logs otherwise it runs as anew container each time since the command changes --- services/clsi/app/coffee/LatexRunner.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/LatexRunner.coffee b/services/clsi/app/coffee/LatexRunner.coffee index 938d4a51ba..4280d95a67 100644 --- a/services/clsi/app/coffee/LatexRunner.coffee +++ b/services/clsi/app/coffee/LatexRunner.coffee @@ -28,7 +28,7 @@ module.exports = LatexRunner = return callback new Error("unknown compiler: #{compiler}") if Settings.clsi?.strace - command = ["strace", "-o", "strace-#{Date.now()}", "-ff"].concat(command) + command = ["strace", "-o", "strace", "-ff"].concat(command) CommandRunner.run project_id, command, directory, image, timeout, (error, output) -> return callback(error) if error? From 9c7e8af851cb390d12ad19b9789083801b39b24c Mon Sep 17 00:00:00 2001 From: James Allen <james@sharelatex.com> Date: Wed, 30 Mar 2016 11:41:11 +0100 Subject: [PATCH 129/754] Ignore both .cache and .archive and other hidden files in finding output files --- services/clsi/app/coffee/OutputFileFinder.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/OutputFileFinder.coffee b/services/clsi/app/coffee/OutputFileFinder.coffee index dcec5f5df6..b4a9162581 100644 --- a/services/clsi/app/coffee/OutputFileFinder.coffee +++ b/services/clsi/app/coffee/OutputFileFinder.coffee @@ -29,7 +29,7 @@ module.exports = OutputFileFinder = _callback(error, fileList) _callback = () -> - args = [directory, "-name", ".cache", "-prune", "-o", "-type", "f", "-print"] + args = [directory, "-name", ".*", "-prune", "-o", "-type", "f", "-print"] logger.log args: args, "running find command" proc = spawn("find", args) From a2edeb800b8968e4942a15413a08ecf65026473e Mon Sep 17 00:00:00 2001 From: James Allen <james@sharelatex.com> Date: Wed, 30 Mar 2016 14:10:07 +0100 Subject: [PATCH 130/754] Only archive main log and blg --- services/clsi/app/coffee/OutputCacheManager.coffee | 4 ++-- services/clsi/config/settings.defaults.coffee | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee index 1517ccc367..5f7558a50f 100644 --- a/services/clsi/app/coffee/OutputCacheManager.coffee +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -35,7 +35,7 @@ module.exports = OutputCacheManager = OutputCacheManager.expireOutputFiles cacheRoot, {keep: buildId} # Archive logs in background - if Settings.clsi?.archive_logs + if Settings.clsi?.archive_logs or Settings.clsi?.strace OutputCacheManager.archiveLogs outputFiles, compileDir, (err) -> if err? logger.warn err:err, "erroring archiving log files" @@ -153,6 +153,6 @@ module.exports = OutputCacheManager = _checkIfShouldArchive: (src, callback = (err, shouldCopy) ->) -> if Path.basename(src).match(/^strace/) return callback(null, true) - if Path.basename(src).match(/^output\.(?!pdf)/) + if Settings.clsi?.archive_logs and Path.basename(src) in ["output.log", "output.blg"] return callback(null, true) return callback(null, false) \ No newline at end of file diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index 4169f27540..8e589a5550 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -17,6 +17,8 @@ module.exports = synctexBaseDir: (project_id) -> Path.join(@compilesDir, project_id) # clsi: + # strace: true + # archive_logs: true # commandRunner: "docker-runner-sharelatex" # docker: # image: "quay.io/sharelatex/texlive-full" From 80601eaa85a65b78b16a07f7e0a89904820f4859 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Wed, 30 Mar 2016 14:35:47 +0100 Subject: [PATCH 131/754] add support for sentry --- services/clsi/app.coffee | 8 ++++++++ services/clsi/package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index bd0a5864a9..7412c7f7ec 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -2,6 +2,9 @@ CompileController = require "./app/js/CompileController" Settings = require "settings-sharelatex" logger = require "logger-sharelatex" logger.initialize("clsi") +if Settings.sentry?.dsn? + logger.initializeErrorReporting(Settings.sentry.dsn) + smokeTest = require "smoke-test-sharelatex" ContentTypeMapper = require "./app/js/ContentTypeMapper" @@ -63,6 +66,11 @@ app.get "/project/:project_id/output/*", (req, res, next) -> req.url = "/#{req.params.project_id}/#{req.params[0]}" staticServer(req, res, next) +app.get "/oops", (req, res, next) -> + logger.error {err: "hello"}, "test error" + res.send "error\n" + + app.get "/status", (req, res, next) -> res.send "CLSI is alive\n" diff --git a/services/clsi/package.json b/services/clsi/package.json index 33560b5997..b5b1c9b6c6 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -13,7 +13,7 @@ "mkdirp": "0.3.5", "mysql": "2.6.2", "request": "~2.21.0", - "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.0.0", + "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.4.0", "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.5.0", "sequelize": "^2.1.3", From 308ca01b3cd6d0a3c22ef0b25aa0d2cb03bcb328 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 31 Mar 2016 11:14:39 +0100 Subject: [PATCH 132/754] don't log missing files as warnings, but do report file access errors --- services/clsi/app/coffee/StaticServerForbidSymlinks.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/StaticServerForbidSymlinks.coffee b/services/clsi/app/coffee/StaticServerForbidSymlinks.coffee index 83ca4ca703..1b3cd45836 100644 --- a/services/clsi/app/coffee/StaticServerForbidSymlinks.coffee +++ b/services/clsi/app/coffee/StaticServerForbidSymlinks.coffee @@ -29,10 +29,10 @@ module.exports = ForbidSymlinks = (staticFn, root, options) -> # check that the requested path is not a symlink fs.realpath requestedFsPath, (err, realFsPath)-> if err? - logger.warn err:err, requestedFsPath:requestedFsPath, realFsPath:realFsPath, path: req.params[0], project_id: req.params.project_id, "error checking file access" if err.code == 'ENOENT' return res.sendStatus(404) else + logger.error err:err, requestedFsPath:requestedFsPath, realFsPath:realFsPath, path: req.params[0], project_id: req.params.project_id, "error checking file access" return res.sendStatus(500) else if requestedFsPath != realFsPath logger.warn requestedFsPath:requestedFsPath, realFsPath:realFsPath, path: req.params[0], project_id: req.params.project_id, "trying to access a different file (symlink), aborting" From 26e94f2549b1b33755cdee9a1460075ef5be5a68 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 31 Mar 2016 11:25:25 +0100 Subject: [PATCH 133/754] always create project directory when syncing resources to disk avoids errors when project is empty --- .../clsi/app/coffee/ResourceWriter.coffee | 23 +++++++++++++++---- .../unit/coffee/ResourceWriterTests.coffee | 2 +- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index a8b010665a..22c8b9f7bb 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -9,12 +9,25 @@ logger = require "logger-sharelatex" module.exports = ResourceWriter = syncResourcesToDisk: (project_id, resources, basePath, callback = (error) ->) -> - @_removeExtraneousFiles resources, basePath, (error) => + @_createDirectory basePath, (error) => return callback(error) if error? - jobs = for resource in resources - do (resource) => - (callback) => @_writeResourceToDisk(project_id, resource, basePath, callback) - async.parallelLimit jobs, 5, callback + @_removeExtraneousFiles resources, basePath, (error) => + return callback(error) if error? + jobs = for resource in resources + do (resource) => + (callback) => @_writeResourceToDisk(project_id, resource, basePath, callback) + async.parallelLimit jobs, 5, callback + + _createDirectory: (basePath, callback = (error) ->) -> + fs.mkdir basePath, (err) -> + if err? + if err.code is 'EEXIST' + return callback() + else + logger.log {err: err, dir:basePath}, "error creating directory" + return callback(err) + else + return callback() _removeExtraneousFiles: (resources, basePath, _callback = (error) ->) -> timer = new Metrics.Timer("unlink-output-files") diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee index 3e87b5ca34..5480bd6b7c 100644 --- a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee +++ b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee @@ -7,7 +7,7 @@ path = require "path" describe "ResourceWriter", -> beforeEach -> @ResourceWriter = SandboxedModule.require modulePath, requires: - "fs": @fs = {} + "fs": @fs = { mkdir: sinon.stub().callsArg(1) } "wrench": @wrench = {} "./UrlCache" : @UrlCache = {} "mkdirp" : @mkdirp = sinon.stub().callsArg(1) From 99c6b746f866037bd458f8fe818325bcf6950780 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 31 Mar 2016 11:43:36 +0100 Subject: [PATCH 134/754] check directory exists before attempting to clear it --- .../clsi/app/coffee/CompileManager.coffee | 34 ++++++++++++++----- .../unit/coffee/CompileManagerTests.coffee | 2 ++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index a193ac9fb9..faae9da9ee 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -62,18 +62,36 @@ module.exports = CompileManager = _callback = () -> compileDir = Path.join(Settings.path.compilesDir, project_id) - proc = child_process.spawn "rm", ["-r", compileDir] - proc.on "error", callback + CompileManager._checkDirectory compileDir, (err, exists) -> + return callback(err) if err? + return callback() if not exists # skip removal if no directory present - stderr = "" - proc.stderr.on "data", (chunk) -> stderr += chunk.toString() + proc = child_process.spawn "rm", ["-r", compileDir] - proc.on "close", (code) -> - if code == 0 - return callback(null) + proc.on "error", callback + + stderr = "" + proc.stderr.on "data", (chunk) -> stderr += chunk.toString() + + proc.on "close", (code) -> + if code == 0 + return callback(null) + else + return callback(new Error("rm -r #{compileDir} failed: #{stderr}")) + + _checkDirectory: (compileDir, callback = (error, exists) ->) -> + fs.lstat compileDir, (err, stats) -> + if err?.code is 'ENOENT' + return callback(null, false) # directory does not exist + else if err? + logger.err {dir: compileDir, err:err}, "error on stat of project directory for removal" + return callback(err) + else if not stats?.isDirectory() + logger.err {dir: compileDir, stats:stats}, "bad project directory for removal" + return callback new Error("project directory is not directory") else - return callback(new Error("rm -r #{compileDir} failed: #{stderr}")) + callback(null, true) # directory exists syncFromCode: (project_id, file_name, line, column, callback = (error, pdfPositions) ->) -> # If LaTeX was run in a virtual environment, the file path that synctex expects diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 018e01d245..aa9a04ab96 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -99,6 +99,7 @@ describe "CompileManager", -> describe "succesfully", -> beforeEach -> @Settings.compileDir = "compiles" + @fs.lstat = sinon.stub().callsArgWith(1, null,{isDirectory: ()->true}) @proc = new EventEmitter() @proc.stdout = new EventEmitter() @proc.stderr = new EventEmitter() @@ -117,6 +118,7 @@ describe "CompileManager", -> describe "with a non-success status code", -> beforeEach -> @Settings.compileDir = "compiles" + @fs.lstat = sinon.stub().callsArgWith(1, null,{isDirectory: ()->true}) @proc = new EventEmitter() @proc.stdout = new EventEmitter() @proc.stderr = new EventEmitter() From 01d7a59dfd6c8e20d1fccfe7ff28171ada2ce060 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 31 Mar 2016 11:49:15 +0100 Subject: [PATCH 135/754] remove console.log --- services/clsi/app/coffee/CompileManager.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index faae9da9ee..6f71c321a5 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -161,7 +161,6 @@ module.exports = CompileManager = catch err logger.err err:err, command:command, directory:directory, project_id:project_id, "error reading word count output" return callback(err) - console.log "rooooof" callback null, CompileManager._parseWordcountFromOutput(stdout) _parseWordcountFromOutput: (output) -> From 2f15adaf4713455474312ef78402f788fcabb1e1 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 31 Mar 2016 12:12:25 +0100 Subject: [PATCH 136/754] parameter check on project_id --- services/clsi/app.coffee | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 7412c7f7ec..c7bbd8cf64 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -36,6 +36,12 @@ app.use (req, res, next) -> res.setTimeout TIMEOUT next() +app.param 'project_id', (req, res, next, project_id) -> + if project_id?.match /^[a-zA-Z0-9_-]+$/ + next() + else + next new Error("invalid project id") + app.post "/project/:project_id/compile", bodyParser.json(limit: "5mb"), CompileController.compile app.delete "/project/:project_id", CompileController.clearCache From c1cb0f8800b95c0cf050beaa5a2a1cef3b3f4dad Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 31 Mar 2016 13:33:42 +0100 Subject: [PATCH 137/754] suppress error when removing nonexistent file from cache --- services/clsi/app/coffee/UrlCache.coffee | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/UrlCache.coffee b/services/clsi/app/coffee/UrlCache.coffee index 535a70570c..b72b78ca0f 100644 --- a/services/clsi/app/coffee/UrlCache.coffee +++ b/services/clsi/app/coffee/UrlCache.coffee @@ -87,7 +87,11 @@ module.exports = UrlCache = callback null _deleteUrlCacheFromDisk: (project_id, url, callback = (error) ->) -> - fs.unlink UrlCache._cacheFilePathForUrl(project_id, url), callback + fs.unlink UrlCache._cacheFilePathForUrl(project_id, url), (error) -> + if error? and error.code != 'ENOENT' # no error if the file isn't present + return callback(error) + else + return callback() _findUrlDetails: (project_id, url, callback = (error, urlDetails) ->) -> db.UrlCache.find(where: { url: url, project_id: project_id }) From 0ca584cb55add2da5f0d96b47d3f27143d5c3674 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 31 Mar 2016 14:03:48 +0100 Subject: [PATCH 138/754] upgrade to the latest version of request --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index b5b1c9b6c6..eaa9be8cd1 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -12,7 +12,7 @@ "lynx": "0.0.11", "mkdirp": "0.3.5", "mysql": "2.6.2", - "request": "~2.21.0", + "request": "^2.21.0", "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.4.0", "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.5.0", From 4a785ff43c7db7faf654a4361e0e4e711327b935 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Mon, 4 Apr 2016 16:22:48 +0100 Subject: [PATCH 139/754] log errors in detail when file cannot be removed --- services/clsi/app/coffee/ResourceWriter.coffee | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index 22c8b9f7bb..835d45e1a4 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -54,9 +54,18 @@ module.exports = ResourceWriter = _deleteFileIfNotDirectory: (path, callback = (error) ->) -> fs.stat path, (error, stat) -> - return callback(error) if error? - if stat.isFile() - fs.unlink path, callback + if error? and error.code is 'ENOENT' + return callback() + else if error? + logger.err {err: error, path: path}, "error stating file in deleteFileIfNotDirectory" + return callback(error) + else if stat.isFile() + fs.unlink path, (error) -> + if error? + logger.err {err: error, path: path}, "error removing file in deleteFileIfNotDirectory" + callback(error) + else + callback() else callback() From 27b739b0819ea748eabbf1faf07f204e91853d20 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 7 Apr 2016 16:16:39 +0100 Subject: [PATCH 140/754] don't log errors when files have disappeared from build directory --- services/clsi/app/coffee/OutputCacheManager.coffee | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee index 5f7558a50f..aff332be58 100644 --- a/services/clsi/app/coffee/OutputCacheManager.coffee +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -125,8 +125,11 @@ module.exports = OutputCacheManager = _checkFileIsSafe: (src, callback = (error, isSafe) ->) -> # check if we have a valid file to copy into the cache fs.stat src, (err, stats) -> - if err? - # some problem reading the file + if err?.code is 'ENOENT' + logger.warn err: err, file: src, "file has disappeared before copying to build cache" + callback(err, false) + else if err? + # some other problem reading the file logger.error err: err, file: src, "stat error for file in cache" callback(err, false) else if not stats.isFile() @@ -140,7 +143,10 @@ module.exports = OutputCacheManager = _copyFile: (src, dst, callback) -> # copy output file into the cache fse.copy src, dst, (err) -> - if err? + if err?.code is 'ENOENT' + logger.warn err: err, file: src, "file has disappeared when copying to build cache" + callback(err, false) + else if err? logger.error err: err, src: src, dst: dst, "copy error for file in cache" callback(err) else @@ -155,4 +161,4 @@ module.exports = OutputCacheManager = return callback(null, true) if Settings.clsi?.archive_logs and Path.basename(src) in ["output.log", "output.blg"] return callback(null, true) - return callback(null, false) \ No newline at end of file + return callback(null, false) From ab15c9e32011f712d3d6c82949f7dff2818becca Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 8 Apr 2016 13:31:23 +0100 Subject: [PATCH 141/754] added example server load tcp server --- services/clsi/app.coffee | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index c7bbd8cf64..bdc801b2d3 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -122,3 +122,26 @@ app.listen port = (Settings.internal?.clsi?.port or 3013), host = (Settings.inte setInterval () -> ProjectPersistenceManager.clearExpiredProjects() , tenMinutes = 10 * 60 * 1000 + + + +net = require('net') +os = require('os') + +fiveMinLoad = os.loadavg()[1] +availableWorkingCpus = os.cpus().length - 1 +freeLoad = availableWorkingCpus - fiveMinLoad +freeLoadPercentage = Math.round((freeLoad / availableWorkingCpus) * 100) + +server = net.createServer (socket) -> + socket.write "#{freeLoadPercentage}%\n", "ASCII" + socket.pipe socket + return + +port = 4080 + +server.listen port, -> + console.log "listening on port #{port}" + # netcat 127.0.0.1 4080 + + From 260987336da4a85fcd2d837b1dc09768a41a18f7 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 8 Apr 2016 14:14:05 +0100 Subject: [PATCH 142/754] evaluate on every call --- services/clsi/app.coffee | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index bdc801b2d3..209c9ff959 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -128,15 +128,16 @@ setInterval () -> net = require('net') os = require('os') -fiveMinLoad = os.loadavg()[1] -availableWorkingCpus = os.cpus().length - 1 -freeLoad = availableWorkingCpus - fiveMinLoad -freeLoadPercentage = Math.round((freeLoad / availableWorkingCpus) * 100) + server = net.createServer (socket) -> - socket.write "#{freeLoadPercentage}%\n", "ASCII" - socket.pipe socket - return + fiveMinLoad = os.loadavg()[1] + availableWorkingCpus = os.cpus().length - 1 + freeLoad = availableWorkingCpus - fiveMinLoad + freeLoadPercentage = Math.round((freeLoad / availableWorkingCpus) * 100) + socket.write "#{freeLoadPercentage}%\n", "ASCII" + socket.pipe socket + return port = 4080 From 2864d776f50101ba134b4cfefa3f3f25464b1102 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 8 Apr 2016 15:18:22 +0100 Subject: [PATCH 143/754] work of 1 min load and set server as up --- services/clsi/app.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 209c9ff959..3a5a7ca8f1 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -131,11 +131,11 @@ os = require('os') server = net.createServer (socket) -> - fiveMinLoad = os.loadavg()[1] + fiveMinLoad = os.loadavg()[0] availableWorkingCpus = os.cpus().length - 1 freeLoad = availableWorkingCpus - fiveMinLoad freeLoadPercentage = Math.round((freeLoad / availableWorkingCpus) * 100) - socket.write "#{freeLoadPercentage}%\n", "ASCII" + socket.write "up, #{freeLoadPercentage}%\n", "ASCII" socket.pipe socket return From 02c7a961619c532ef19463d51e2edf44a0cc88b5 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 8 Apr 2016 15:25:00 +0100 Subject: [PATCH 144/754] added err handler to socket --- services/clsi/app.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 3a5a7ca8f1..0a2972f3c2 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -136,6 +136,8 @@ server = net.createServer (socket) -> freeLoad = availableWorkingCpus - fiveMinLoad freeLoadPercentage = Math.round((freeLoad / availableWorkingCpus) * 100) socket.write "up, #{freeLoadPercentage}%\n", "ASCII" + socket.on "error", (err)-> + console.log err, "error with socket" socket.pipe socket return From 29e3668b529e95de88022e43cdb9c5363713cd01 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 8 Apr 2016 15:39:02 +0100 Subject: [PATCH 145/754] return 0 for server which is being hammered socket.destroy when finished --- services/clsi/app.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 0a2972f3c2..b9bf18924c 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -135,10 +135,12 @@ server = net.createServer (socket) -> availableWorkingCpus = os.cpus().length - 1 freeLoad = availableWorkingCpus - fiveMinLoad freeLoadPercentage = Math.round((freeLoad / availableWorkingCpus) * 100) + if freeLoadPercentage < 0 + freeLoadPercentage = 1 # when its 0 the server is set to drain and will move projects about socket.write "up, #{freeLoadPercentage}%\n", "ASCII" socket.on "error", (err)-> console.log err, "error with socket" - socket.pipe socket + socket.destroy() return port = 4080 From 5832a091401ecbe5197596dbdf9a07342f951189 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 11 Apr 2016 13:47:06 +0100 Subject: [PATCH 146/754] server load endpoint uses settings for port --- services/clsi/app.coffee | 19 +++++++------------ services/clsi/config/settings.defaults.coffee | 1 + 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index b9bf18924c..0b5c47d50c 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -128,25 +128,20 @@ setInterval () -> net = require('net') os = require('os') - - server = net.createServer (socket) -> - fiveMinLoad = os.loadavg()[0] + socket.on "error", (err)-> + logger.err err:err, "error with socket on load check" + currentLoad = os.loadavg()[0] availableWorkingCpus = os.cpus().length - 1 - freeLoad = availableWorkingCpus - fiveMinLoad + freeLoad = availableWorkingCpus - currentLoad freeLoadPercentage = Math.round((freeLoad / availableWorkingCpus) * 100) if freeLoadPercentage < 0 - freeLoadPercentage = 1 # when its 0 the server is set to drain and will move projects about + freeLoadPercentage = 1 # when its 0 the server is set to drain and will move projects to different servers socket.write "up, #{freeLoadPercentage}%\n", "ASCII" - socket.on "error", (err)-> - console.log err, "error with socket" socket.destroy() - return -port = 4080 - -server.listen port, -> +server.listen port = (Settings.internal?.clsi?.load_port or 3044), -> console.log "listening on port #{port}" - # netcat 127.0.0.1 4080 + # netcat 127.0.0.1 3044 diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index 8e589a5550..c7d9e3703d 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -32,6 +32,7 @@ module.exports = internal: clsi: port: 3013 + load_port: 3044 host: "localhost" apis: From 42d44a707201e83fc1a2f9036fd606e99f243914 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 12 Apr 2016 10:49:45 +0100 Subject: [PATCH 147/754] use socket.end for tcp checks --- services/clsi/app.coffee | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 0b5c47d50c..984f09a208 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -131,17 +131,19 @@ os = require('os') server = net.createServer (socket) -> socket.on "error", (err)-> logger.err err:err, "error with socket on load check" + socket.destroy() + currentLoad = os.loadavg()[0] availableWorkingCpus = os.cpus().length - 1 freeLoad = availableWorkingCpus - currentLoad freeLoadPercentage = Math.round((freeLoad / availableWorkingCpus) * 100) - if freeLoadPercentage < 0 + if freeLoadPercentage <= 0 freeLoadPercentage = 1 # when its 0 the server is set to drain and will move projects to different servers - socket.write "up, #{freeLoadPercentage}%\n", "ASCII" - socket.destroy() + socket.write("up, #{freeLoadPercentage}%\n", "ASCII") + socket.end() server.listen port = (Settings.internal?.clsi?.load_port or 3044), -> - console.log "listening on port #{port}" - # netcat 127.0.0.1 3044 + logger.info "tcp load endpoint listening on port #{port}" + # telnet 127.0.0.1 3044 From 67d3ebf4dccef1615efe385d31c502aab5d993ff Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 12 Apr 2016 13:32:58 +0100 Subject: [PATCH 148/754] ignore ECONNRESET --- services/clsi/app.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 984f09a208..f94685aa26 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -130,6 +130,9 @@ os = require('os') server = net.createServer (socket) -> socket.on "error", (err)-> + if err.code == "ECONNRESET" + # this always comes up, we don't know why + return logger.err err:err, "error with socket on load check" socket.destroy() From d416f75c2c7c17b25e8e3d8e6f6094698e220c1f Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Wed, 13 Apr 2016 09:29:57 +0100 Subject: [PATCH 149/754] increased clsi cache to 3.5 days --- services/clsi/app/coffee/ProjectPersistenceManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.coffee b/services/clsi/app/coffee/ProjectPersistenceManager.coffee index 9f11cf312d..26542b8942 100644 --- a/services/clsi/app/coffee/ProjectPersistenceManager.coffee +++ b/services/clsi/app/coffee/ProjectPersistenceManager.coffee @@ -7,7 +7,7 @@ oneDay = 24 * 60 * 60 * 1000 module.exports = ProjectPersistenceManager = - EXPIRY_TIMEOUT: oneDay * 2.5 + EXPIRY_TIMEOUT: oneDay * 3.5 markProjectAsJustAccessed: (project_id, callback = (error) ->) -> db.Project.findOrCreate(where: {project_id: project_id}) From e024d3f50359ad2086c3fec52086416534f34a9b Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Wed, 20 Apr 2016 15:38:05 +0100 Subject: [PATCH 150/754] added project status endpoint used for getting the server a project is on --- services/clsi/app.coffee | 1 + services/clsi/app/coffee/CompileController.coffee | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index f94685aa26..aaa2b7eef6 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -48,6 +48,7 @@ app.delete "/project/:project_id", CompileController.clearCache app.get "/project/:project_id/sync/code", CompileController.syncFromCode app.get "/project/:project_id/sync/pdf", CompileController.syncFromPdf app.get "/project/:project_id/wordcount", CompileController.wordcount +app.get "/project/:project_id/status", CompileController.status ForbidSymlinks = require "./app/js/StaticServerForbidSymlinks" diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index dd73040958..62b1020ad9 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -78,3 +78,7 @@ module.exports = CompileController = res.send JSON.stringify { texcount: result } + + status: (req, res, next = (error)-> )-> + res.send("OK") + From 7ab1654ebc9661318632fc180d2e0f6f9abc56b7 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 21 Apr 2016 17:40:09 +0100 Subject: [PATCH 151/754] move back to 2.5 days cache for moment --- services/clsi/app/coffee/ProjectPersistenceManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.coffee b/services/clsi/app/coffee/ProjectPersistenceManager.coffee index 26542b8942..9f11cf312d 100644 --- a/services/clsi/app/coffee/ProjectPersistenceManager.coffee +++ b/services/clsi/app/coffee/ProjectPersistenceManager.coffee @@ -7,7 +7,7 @@ oneDay = 24 * 60 * 60 * 1000 module.exports = ProjectPersistenceManager = - EXPIRY_TIMEOUT: oneDay * 3.5 + EXPIRY_TIMEOUT: oneDay * 2.5 markProjectAsJustAccessed: (project_id, callback = (error) ->) -> db.Project.findOrCreate(where: {project_id: project_id}) From ebcf9a75ae121a59a7b47c489015981cdf68b1ef Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Mon, 9 May 2016 15:36:11 +0100 Subject: [PATCH 152/754] add a metric for the TeXLive image used on each compile --- services/clsi/app/coffee/CompileManager.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 6f71c321a5..ca84303230 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -32,7 +32,11 @@ module.exports = CompileManager = injectDraftModeIfRequired (error) -> return callback(error) if error? timer = new Metrics.Timer("run-compile") + # find the image tag to log it as a metric + tag = request.imageName?.match(/:(.*)/)?[1] or "default" + tag = "other" if project_id?.match(/^[0-9a-f]{24}$/) # exclude smoke test Metrics.inc("compiles") + Metrics.inc("compiles-with-image.#{tag}") LatexRunner.runLatex request.project_id, { directory: compileDir mainFile: request.rootResourcePath From 3eebca4c016591f41851cded2752b98b766e42fb Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Mon, 9 May 2016 16:00:24 +0100 Subject: [PATCH 153/754] add timing information from /usr/bin/time --- services/clsi/app/coffee/CompileManager.coffee | 10 ++++++---- services/clsi/app/coffee/LatexRunner.coffee | 8 ++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index ca84303230..5c70c07e47 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -43,17 +43,19 @@ module.exports = CompileManager = compiler: request.compiler timeout: request.timeout image: request.imageName - }, (error, output, stats) -> + }, (error, output, stats, timings) -> return callback(error) if error? Metrics.inc("compiles-succeeded") for metric_key, metric_value of stats or {} Metrics.count(metric_key, metric_value) + for metric_key, metric_value of timings or {} + Metrics.timing(metric_key, metric_value) loadavg = os.loadavg?() Metrics.gauge("load-avg", loadavg[0]) if loadavg? ts = timer.done() - logger.log {project_id: request.project_id, time_taken: ts, stats:stats, loadavg:loadavg}, "done compile" - if stats?["latex-runs"] > 0 - Metrics.timing("run-compile-per-pass", ts / stats["latex-runs"]) + logger.log {project_id: request.project_id, time_taken: ts, stats:stats, timings:timings, loadavg:loadavg}, "done compile" + if stats?["latex-runs"] > 0 and timings?["cpu-time"] > 0 + Metrics.timing("run-compile-per-pass", timings["cpu-time"] / stats["latex-runs"]) OutputFileFinder.findOutputFiles request.resources, compileDir, (error, outputFiles) -> return callback(error) if error? diff --git a/services/clsi/app/coffee/LatexRunner.coffee b/services/clsi/app/coffee/LatexRunner.coffee index 4280d95a67..e5861a5c8d 100644 --- a/services/clsi/app/coffee/LatexRunner.coffee +++ b/services/clsi/app/coffee/LatexRunner.coffee @@ -41,9 +41,13 @@ module.exports = LatexRunner = stats["latex-runs-with-errors"] = if failed then runs else 0 stats["latex-runs-#{runs}"] = 1 stats["latex-runs-with-errors-#{runs}"] = if failed then 1 else 0 - callback error, output, stats + # timing information from /usr/bin/time + timings = {} + timings["cpu-percent"] = output?.stderr?.match(/Percent of CPU this job got: (\d+)/m)?[1] or 0 + timings["cpu-time"] = output?.stderr?.match(/User time.*: (\d+.\d+)/m)?[1] or 0 + callback error, output, stats, timings - _latexmkBaseCommand: ["latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR"] + _latexmkBaseCommand: ["/usr/bin/time", "-v", "latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR"] _pdflatexCommand: (mainFile) -> LatexRunner._latexmkBaseCommand.concat [ From d2e4253e1eda3e45734576a3f7ee2321ac3e4fa5 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 10 May 2016 09:12:00 +0100 Subject: [PATCH 154/754] preserve existing metric name --- services/clsi/app/coffee/CompileManager.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 5c70c07e47..dc6c8337b0 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -54,8 +54,10 @@ module.exports = CompileManager = Metrics.gauge("load-avg", loadavg[0]) if loadavg? ts = timer.done() logger.log {project_id: request.project_id, time_taken: ts, stats:stats, timings:timings, loadavg:loadavg}, "done compile" + if stats?["latex-runs"] > 0 + Metrics.timing("run-compile-per-pass", ts / stats["latex-runs"]) if stats?["latex-runs"] > 0 and timings?["cpu-time"] > 0 - Metrics.timing("run-compile-per-pass", timings["cpu-time"] / stats["latex-runs"]) + Metrics.timing("run-compile-cpu-time-per-pass", timings["cpu-time"] / stats["latex-runs"]) OutputFileFinder.findOutputFiles request.resources, compileDir, (error, outputFiles) -> return callback(error) if error? From 7779f65869b3bac6ecc4f95c9eb96153a64c3159 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 10 May 2016 09:12:13 +0100 Subject: [PATCH 155/754] record system time --- services/clsi/app/coffee/LatexRunner.coffee | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/LatexRunner.coffee b/services/clsi/app/coffee/LatexRunner.coffee index e5861a5c8d..65a30464ee 100644 --- a/services/clsi/app/coffee/LatexRunner.coffee +++ b/services/clsi/app/coffee/LatexRunner.coffee @@ -43,8 +43,10 @@ module.exports = LatexRunner = stats["latex-runs-with-errors-#{runs}"] = if failed then 1 else 0 # timing information from /usr/bin/time timings = {} - timings["cpu-percent"] = output?.stderr?.match(/Percent of CPU this job got: (\d+)/m)?[1] or 0 - timings["cpu-time"] = output?.stderr?.match(/User time.*: (\d+.\d+)/m)?[1] or 0 + stderr = output?.stderr + timings["cpu-percent"] = stderr?.match(/Percent of CPU this job got: (\d+)/m)?[1] or 0 + timings["cpu-time"] = stderr?.match(/User time.*: (\d+.\d+)/m)?[1] or 0 + timings["sys-time"] = stderr?.match(/System time.*: (\d+.\d+)/m)?[1] or 0 callback error, output, stats, timings _latexmkBaseCommand: ["/usr/bin/time", "-v", "latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR"] From 00acfbdf86603af180400fb253f66768ebdffca2 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 10 May 2016 09:41:39 +0100 Subject: [PATCH 156/754] fix tagname for graphite --- services/clsi/app/coffee/CompileManager.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index dc6c8337b0..dcd1a98b78 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -32,8 +32,8 @@ module.exports = CompileManager = injectDraftModeIfRequired (error) -> return callback(error) if error? timer = new Metrics.Timer("run-compile") - # find the image tag to log it as a metric - tag = request.imageName?.match(/:(.*)/)?[1] or "default" + # find the image tag to log it as a metric, e.g. 2015.1 (convert . to - for graphite) + tag = request.imageName?.match(/:(.*)/)?[1]?.replace(/\./g,'-') or "default" tag = "other" if project_id?.match(/^[0-9a-f]{24}$/) # exclude smoke test Metrics.inc("compiles") Metrics.inc("compiles-with-image.#{tag}") From d5c1fb99978d37a7eec28e969fe1195e7f8a762f Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 10 May 2016 10:10:01 +0100 Subject: [PATCH 157/754] fix logic excluding smoke test in metric --- services/clsi/app/coffee/CompileManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index dcd1a98b78..54a6a7ebdc 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -34,7 +34,7 @@ module.exports = CompileManager = timer = new Metrics.Timer("run-compile") # find the image tag to log it as a metric, e.g. 2015.1 (convert . to - for graphite) tag = request.imageName?.match(/:(.*)/)?[1]?.replace(/\./g,'-') or "default" - tag = "other" if project_id?.match(/^[0-9a-f]{24}$/) # exclude smoke test + tag = "other" if not request.project_id.match(/^[0-9a-f]{24}$/) # exclude smoke test Metrics.inc("compiles") Metrics.inc("compiles-with-image.#{tag}") LatexRunner.runLatex request.project_id, { From 8a2665911dcf6a26c6a00013db2d7ae431357186 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 13 May 2016 10:10:48 +0100 Subject: [PATCH 158/754] allow direct path to output file /project/project_id/build/build_id/output/* this avoids use of the query string ?build=... and so we can match the url directly with the nginx location directive --- services/clsi/app.coffee | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index aaa2b7eef6..275c6bf0c0 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -42,6 +42,13 @@ app.param 'project_id', (req, res, next, project_id) -> else next new Error("invalid project id") +app.param 'build_id', (req, res, next, build_id) -> + if build_id?.match OutputCacheManager.BUILD_REGEX + next() + else + next new Error("invalid build id #{build_id}") + + app.post "/project/:project_id/compile", bodyParser.json(limit: "5mb"), CompileController.compile app.delete "/project/:project_id", CompileController.clearCache @@ -65,6 +72,11 @@ staticServer = ForbidSymlinks express.static, Settings.path.compilesDir, setHead res.set("Etag", etag(path, stat)) res.set("Content-Type", ContentTypeMapper.map(path)) +app.get "/project/:project_id/build/:build_id/output/*", (req, res, next) -> + # for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) + req.url = "/#{req.params.project_id}/" + OutputCacheManager.path(req.params.build_id, "/#{req.params[0]}") + staticServer(req, res, next) + app.get "/project/:project_id/output/*", (req, res, next) -> if req.query?.build? && req.query.build.match(OutputCacheManager.BUILD_REGEX) # for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) From 2494327c122a8917ff65a9fd82184907755fa1cd Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 13 May 2016 10:11:35 +0100 Subject: [PATCH 159/754] make the build id a secure random token we allow existing build ids to work for backwards compatibility this can be removed after some time --- .../clsi/app/coffee/OutputCacheManager.coffee | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee index aff332be58..7f11bc89b0 100644 --- a/services/clsi/app/coffee/OutputCacheManager.coffee +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -5,13 +5,16 @@ Path = require "path" logger = require "logger-sharelatex" _ = require "underscore" Settings = require "settings-sharelatex" +crypto = require "crypto" OutputFileOptimiser = require "./OutputFileOptimiser" module.exports = OutputCacheManager = CACHE_SUBDIR: '.cache/clsi' ARCHIVE_SUBDIR: '.archive/clsi' - BUILD_REGEX: /^[0-9a-f]+$/ # build id is Date.now() converted to hex + # build id is HEXDATE-HEXRANDOM from Date.now()and RandomBytes + # for backwards compatibility, make the randombytes part optional + BUILD_REGEX: /^[0-9a-f]+(-[0-9a-f]+)?$/ CACHE_LIMIT: 2 # maximum number of cache directories CACHE_AGE: 60*60*1000 # up to one hour old @@ -23,12 +26,24 @@ module.exports = OutputCacheManager = # for invalid build id, return top level return file + generateBuildId: (callback = (error, buildId) ->) -> + # generate a secure build id from Date.now() and 8 random bytes in hex + crypto.randomBytes 8, (err, buf) -> + return callback(err) if err? + random = buf.toString('hex') + date = Date.now().toString(16) + callback err, "#{date}-#{random}" + saveOutputFiles: (outputFiles, compileDir, callback = (error) ->) -> + OutputCacheManager.generateBuildId (err, buildId) -> + return callback(err) if err? + OutputCacheManager.saveOutputFilesInBuildDir outputFiles, compileDir, buildId, callback + + saveOutputFilesInBuildDir: (outputFiles, compileDir, buildId, callback = (error) ->) -> # make a compileDir/CACHE_SUBDIR/build_id directory and # copy all the output files into it cacheRoot = Path.join(compileDir, OutputCacheManager.CACHE_SUBDIR) # Put the files into a new cache subdirectory - buildId = Date.now().toString(16) cacheDir = Path.join(compileDir, OutputCacheManager.CACHE_SUBDIR, buildId) # let file expiry run in the background @@ -36,7 +51,7 @@ module.exports = OutputCacheManager = # Archive logs in background if Settings.clsi?.archive_logs or Settings.clsi?.strace - OutputCacheManager.archiveLogs outputFiles, compileDir, (err) -> + OutputCacheManager.archiveLogs outputFiles, compileDir, buildId, (err) -> if err? logger.warn err:err, "erroring archiving log files" @@ -71,9 +86,8 @@ module.exports = OutputCacheManager = else # pass back the list of new files in the cache callback(err, results) - - archiveLogs: (outputFiles, compileDir, callback = (error) ->) -> - buildId = Date.now().toString(16) + + archiveLogs: (outputFiles, compileDir, buildId, callback = (error) ->) -> archiveDir = Path.join(compileDir, OutputCacheManager.ARCHIVE_SUBDIR, buildId) logger.log {dir: archiveDir}, "archiving log files for project" fse.ensureDir archiveDir, (err) -> @@ -104,8 +118,9 @@ module.exports = OutputCacheManager = return false if options?.keep == dir # remove any directories over the hard limit return true if index > OutputCacheManager.CACHE_LIMIT - # we can get the build time from the directory name - dirTime = parseInt(dir, 16) + # we can get the build time from the first part of the directory name DDDD-RRRR + # DDDD is date and RRRR is random bytes + dirTime = parseInt(dir.split('-')?[0], 16) age = currentTime - dirTime return age > OutputCacheManager.CACHE_AGE From e286aede4e53eace00eab4b012f508094d9210ee Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 19 May 2016 16:23:07 +0100 Subject: [PATCH 160/754] return the file path in the output file list for easy lookup --- services/clsi/app/coffee/CompileController.coffee | 1 + services/clsi/test/unit/coffee/CompileControllerTests.coffee | 1 + 2 files changed, 2 insertions(+) diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 62b1020ad9..5cc9a5e2f4 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -34,6 +34,7 @@ module.exports = CompileController = error: error?.message or error outputFiles: outputFiles.map (file) -> url: "#{Settings.apis.clsi.url}/project/#{request.project_id}/output/#{file.path}" + path: file.path type: file.type build: file.build } diff --git a/services/clsi/test/unit/coffee/CompileControllerTests.coffee b/services/clsi/test/unit/coffee/CompileControllerTests.coffee index 6a18ce795f..d8d6e8620f 100644 --- a/services/clsi/test/unit/coffee/CompileControllerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileControllerTests.coffee @@ -76,6 +76,7 @@ describe "CompileController", -> error: null outputFiles: @output_files.map (file) => url: "#{@Settings.apis.clsi.url}/project/#{@project_id}/output/#{file.path}" + path: file.path type: file.type build: file.build ) From 07401c5a451c9b07e89fc19c9a81c340939a21f0 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 19 May 2016 16:23:33 +0100 Subject: [PATCH 161/754] avoid clobbering the existing port variable --- services/clsi/app.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 275c6bf0c0..2a8e7b5f37 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -158,8 +158,8 @@ server = net.createServer (socket) -> socket.write("up, #{freeLoadPercentage}%\n", "ASCII") socket.end() -server.listen port = (Settings.internal?.clsi?.load_port or 3044), -> - logger.info "tcp load endpoint listening on port #{port}" +server.listen load_port = (Settings.internal?.clsi?.load_port or 3044), -> + logger.info "tcp load endpoint listening on port #{load_port}" # telnet 127.0.0.1 3044 From 9ae37d1814a3613e367a298dd5864fe7a55c8c86 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 19 May 2016 16:51:50 +0100 Subject: [PATCH 162/754] make cached assets ttl set via config --- services/clsi/app/coffee/ProjectPersistenceManager.coffee | 4 +++- services/clsi/config/settings.defaults.coffee | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.coffee b/services/clsi/app/coffee/ProjectPersistenceManager.coffee index 9f11cf312d..b716d7391d 100644 --- a/services/clsi/app/coffee/ProjectPersistenceManager.coffee +++ b/services/clsi/app/coffee/ProjectPersistenceManager.coffee @@ -4,10 +4,11 @@ db = require "./db" async = require "async" logger = require "logger-sharelatex" oneDay = 24 * 60 * 60 * 1000 +Settings = require "settings-sharelatex" module.exports = ProjectPersistenceManager = - EXPIRY_TIMEOUT: oneDay * 2.5 + EXPIRY_TIMEOUT: Settings.project_cache_length_ms || oneDay * 2.5 markProjectAsJustAccessed: (project_id, callback = (error) ->) -> db.Project.findOrCreate(where: {project_id: project_id}) @@ -52,3 +53,4 @@ module.exports = ProjectPersistenceManager = .then((projects) -> callback null, projects.map((project) -> project.project_id) ).error callback + diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index c7d9e3703d..aa5780de77 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -35,8 +35,10 @@ module.exports = load_port: 3044 host: "localhost" + apis: clsi: url: "http://localhost:3013" smokeTest: false + project_cache_length_ms: 60 * 60 * 24 \ No newline at end of file From 9c873f99bac3170fe26f3a45f229458957bf146c Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 19 May 2016 16:57:14 +0100 Subject: [PATCH 163/754] log out EXPIRY_TIMEOUT --- services/clsi/app/coffee/ProjectPersistenceManager.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.coffee b/services/clsi/app/coffee/ProjectPersistenceManager.coffee index b716d7391d..f649f204a8 100644 --- a/services/clsi/app/coffee/ProjectPersistenceManager.coffee +++ b/services/clsi/app/coffee/ProjectPersistenceManager.coffee @@ -54,3 +54,5 @@ module.exports = ProjectPersistenceManager = callback null, projects.map((project) -> project.project_id) ).error callback + +logger.log EXPIRY_TIMEOUT:EXPIRY_TIMEOUT, "project assets kept timeout" \ No newline at end of file From 340fef38a9f5c2929cce02db70e5d3dbd029e922 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 20 May 2016 10:22:45 +0100 Subject: [PATCH 164/754] fix error in log for expiry timeout --- services/clsi/app/coffee/ProjectPersistenceManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.coffee b/services/clsi/app/coffee/ProjectPersistenceManager.coffee index f649f204a8..f70f43cab7 100644 --- a/services/clsi/app/coffee/ProjectPersistenceManager.coffee +++ b/services/clsi/app/coffee/ProjectPersistenceManager.coffee @@ -55,4 +55,4 @@ module.exports = ProjectPersistenceManager = ).error callback -logger.log EXPIRY_TIMEOUT:EXPIRY_TIMEOUT, "project assets kept timeout" \ No newline at end of file +logger.log {EXPIRY_TIMEOUT: ProjectPersistenceManager.EXPIRY_TIMEOUT}, "project assets kept timeout" From bd55e18d6b068befb5fe0f14c559e7104e682d12 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 19 May 2016 16:40:12 +0100 Subject: [PATCH 165/754] be ready to serve files from per-user containers --- services/clsi/app.coffee | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 2a8e7b5f37..9f54f4a016 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -42,6 +42,12 @@ app.param 'project_id', (req, res, next, project_id) -> else next new Error("invalid project id") +app.param 'user_id', (req, res, next, project_id) -> + if project_id?.match /^[a-zA-Z0-9_-]+$/ + next() + else + next new Error("invalid user id") + app.param 'build_id', (req, res, next, build_id) -> if build_id?.match OutputCacheManager.BUILD_REGEX next() @@ -72,6 +78,11 @@ staticServer = ForbidSymlinks express.static, Settings.path.compilesDir, setHead res.set("Etag", etag(path, stat)) res.set("Content-Type", ContentTypeMapper.map(path)) +app.get "/project/:project_id/user/:user_id/build/:build_id/output/*", (req, res, next) -> + # for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) + req.url = "/#{req.params.project_id}-#{req.params.user_id}/" + OutputCacheManager.path(req.params.build_id, "/#{req.params[0]}") + staticServer(req, res, next) + app.get "/project/:project_id/build/:build_id/output/*", (req, res, next) -> # for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) req.url = "/#{req.params.project_id}/" + OutputCacheManager.path(req.params.build_id, "/#{req.params[0]}") From d140964c5f3aa397b2cd22d326c31a8a467f5d52 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 23 May 2016 14:13:55 +0100 Subject: [PATCH 166/754] add error handler on CommandRunner --- services/clsi/app/coffee/CommandRunner.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/clsi/app/coffee/CommandRunner.coffee b/services/clsi/app/coffee/CommandRunner.coffee index 41cbfaa8ef..842eb9dbb8 100644 --- a/services/clsi/app/coffee/CommandRunner.coffee +++ b/services/clsi/app/coffee/CommandRunner.coffee @@ -8,5 +8,8 @@ module.exports = CommandRunner = logger.warn "timeouts and sandboxing are not enabled with CommandRunner" proc = spawn command[0], command.slice(1), stdio: "inherit", cwd: directory + proc.on "error", (err)-> + logger.err err:err, project_id:project_id, command: command, directory: directory, "error running command" + callback(err) proc.on "close", () -> callback() \ No newline at end of file From 98a07e07ed9035746b41f3def6bf570281052ba5 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 23 May 2016 14:31:27 +0100 Subject: [PATCH 167/754] parallelFileDownloads defaults to 1, sql can't take it --- services/clsi/app/coffee/ResourceWriter.coffee | 5 ++++- services/clsi/config/settings.defaults.coffee | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index 835d45e1a4..e3639006ee 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -6,6 +6,9 @@ mkdirp = require "mkdirp" OutputFileFinder = require "./OutputFileFinder" Metrics = require "./Metrics" logger = require "logger-sharelatex" +settings = require("settings-sharelatex") + +parallelFileDownloads = settings.parallelFileDownloads or 1 module.exports = ResourceWriter = syncResourcesToDisk: (project_id, resources, basePath, callback = (error) ->) -> @@ -16,7 +19,7 @@ module.exports = ResourceWriter = jobs = for resource in resources do (resource) => (callback) => @_writeResourceToDisk(project_id, resource, basePath, callback) - async.parallelLimit jobs, 5, callback + async.parallelLimit jobs, parallelFileDownloads, callback _createDirectory: (basePath, callback = (error) ->) -> fs.mkdir basePath, (err) -> diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index aa5780de77..ae8e132346 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -41,4 +41,5 @@ module.exports = url: "http://localhost:3013" smokeTest: false - project_cache_length_ms: 60 * 60 * 24 \ No newline at end of file + project_cache_length_ms: 60 * 60 * 24 + parallelFileDownloads:1 \ No newline at end of file From ea8134c43361954fb90c6706b9ddb416b9dc69ac Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 23 May 2016 15:45:39 +0100 Subject: [PATCH 168/754] log out if the command running is being used --- services/clsi/app/coffee/CommandRunner.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/clsi/app/coffee/CommandRunner.coffee b/services/clsi/app/coffee/CommandRunner.coffee index 842eb9dbb8..5ea6765c08 100644 --- a/services/clsi/app/coffee/CommandRunner.coffee +++ b/services/clsi/app/coffee/CommandRunner.coffee @@ -1,6 +1,8 @@ spawn = require("child_process").spawn logger = require "logger-sharelatex" +logger.info "using standard command runner" + module.exports = CommandRunner = run: (project_id, command, directory, image, timeout, callback = (error) ->) -> command = (arg.replace('$COMPILE_DIR', directory) for arg in command) From 448a30750b2226f766faa6c21d00fd43af4823ed Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 24 May 2016 14:08:39 +0100 Subject: [PATCH 169/754] log out which command logger is used --- services/clsi/app/coffee/CompileManager.coffee | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 54a6a7ebdc..ee2c407708 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -7,11 +7,14 @@ Path = require "path" logger = require "logger-sharelatex" Metrics = require "./Metrics" child_process = require "child_process" -CommandRunner = require(Settings.clsi?.commandRunner or "./CommandRunner") DraftModeManager = require "./DraftModeManager" fs = require("fs") os = require("os") +commandRunner = Settings.clsi?.commandRunner or "./CommandRunner" +logger.info commandRunner:commandRunner, "selecting command runner for clsi" +CommandRunner = require(commandRunner) + module.exports = CompileManager = doCompile: (request, callback = (error, outputFiles) ->) -> compileDir = Path.join(Settings.path.compilesDir, request.project_id) From 5ed6ef571feefc3380764724ccddd309fbc88e8b Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 24 May 2016 14:12:02 +0100 Subject: [PATCH 170/754] added logger.info to test setup --- services/clsi/test/unit/coffee/CompileManagerTests.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index aa9a04ab96..55f5cc5200 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -14,7 +14,7 @@ describe "CompileManager", -> "./OutputFileFinder": @OutputFileFinder = {} "./OutputCacheManager": @OutputCacheManager = {} "settings-sharelatex": @Settings = { path: compilesDir: "/compiles/dir" } - "logger-sharelatex": @logger = { log: sinon.stub() } + "logger-sharelatex": @logger = { log: sinon.stub() , info:->} "child_process": @child_process = {} "./CommandRunner": @CommandRunner = {} "./DraftModeManager": @DraftModeManager = {} From 41ca3a42229ce1f10a63222cd912bb047faba3c4 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 27 May 2016 14:45:39 +0100 Subject: [PATCH 171/754] log out errors more clearly --- services/clsi/app/coffee/CompileManager.coffee | 4 +++- services/clsi/app/coffee/OutputFileFinder.coffee | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index ee2c407708..d119cf6662 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -22,7 +22,9 @@ module.exports = CompileManager = timer = new Metrics.Timer("write-to-disk") logger.log project_id: request.project_id, "starting compile" ResourceWriter.syncResourcesToDisk request.project_id, request.resources, compileDir, (error) -> - return callback(error) if error? + if error? + logger.err err:error, project_id: request.project_id, "error writing resources to disk" + return callback(error) logger.log project_id: request.project_id, time_taken: Date.now() - timer.start, "written files to disk" timer.done() diff --git a/services/clsi/app/coffee/OutputFileFinder.coffee b/services/clsi/app/coffee/OutputFileFinder.coffee index b4a9162581..c89cc8ad7c 100644 --- a/services/clsi/app/coffee/OutputFileFinder.coffee +++ b/services/clsi/app/coffee/OutputFileFinder.coffee @@ -13,7 +13,9 @@ module.exports = OutputFileFinder = logger.log directory: directory, "getting output files" OutputFileFinder._getAllFiles directory, (error, allFiles = []) -> - return callback(error) if error? + if error? + logger.err err:error, "error finding all output files" + return callback(error) jobs = [] outputFiles = [] for file in allFiles From fdc8d8364612020b65bd6b78dffe0cdda83d9955 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 27 May 2016 16:18:18 +0100 Subject: [PATCH 172/754] log out error on synctex --- services/clsi/app/coffee/CompileManager.coffee | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index d119cf6662..41377abaf8 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -20,7 +20,7 @@ module.exports = CompileManager = compileDir = Path.join(Settings.path.compilesDir, request.project_id) timer = new Metrics.Timer("write-to-disk") - logger.log project_id: request.project_id, "starting compile" + logger.log project_id: request.project_id, "starting doCompile" ResourceWriter.syncResourcesToDisk request.project_id, request.resources, compileDir, (error) -> if error? logger.err err:error, project_id: request.project_id, "error writing resources to disk" @@ -130,7 +130,9 @@ module.exports = CompileManager = bin_path = Path.resolve(__dirname + "/../../bin/synctex") seconds = 1000 child_process.execFile bin_path, args, timeout: 10 * seconds, (error, stdout, stderr) -> - return callback(error) if error? + if error? + logger.err err:error, args:args, "error running synctex" + return callback(error) callback(null, stdout) _parseSynctexFromCodeOutput: (output) -> From bd22a6e02d4956bfa7261d2271ad379ef51bd389 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 27 May 2016 15:25:13 +0100 Subject: [PATCH 173/754] put the build id in the output file urls the url attribute will now give the preferred location for accessing the output file, without the url having to be constructed by the web client --- services/clsi/app/coffee/CompileController.coffee | 5 ++++- services/clsi/test/unit/coffee/CompileControllerTests.coffee | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 5cc9a5e2f4..478d91bfa7 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -33,7 +33,10 @@ module.exports = CompileController = status: status error: error?.message or error outputFiles: outputFiles.map (file) -> - url: "#{Settings.apis.clsi.url}/project/#{request.project_id}/output/#{file.path}" + url: + "#{Settings.apis.clsi.url}/project/#{request.project_id}" + + (if file.build? then "/build/#{file.build}" else "") + + "/output/#{file.path}" path: file.path type: file.type build: file.build diff --git a/services/clsi/test/unit/coffee/CompileControllerTests.coffee b/services/clsi/test/unit/coffee/CompileControllerTests.coffee index d8d6e8620f..7693799e4c 100644 --- a/services/clsi/test/unit/coffee/CompileControllerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileControllerTests.coffee @@ -75,7 +75,7 @@ describe "CompileController", -> status: "success" error: null outputFiles: @output_files.map (file) => - url: "#{@Settings.apis.clsi.url}/project/#{@project_id}/output/#{file.path}" + url: "#{@Settings.apis.clsi.url}/project/#{@project_id}/build/#{file.build}/output/#{file.path}" path: file.path type: file.type build: file.build From 1462e17f0cf952acb6bb0fff881b6879ec0a3646 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 27 May 2016 15:25:13 +0100 Subject: [PATCH 174/754] put the build id in the output file urls the url attribute will now give the preferred location for accessing the output file, without the url having to be constructed by the web client --- services/clsi/app/coffee/CompileController.coffee | 5 ++++- services/clsi/test/unit/coffee/CompileControllerTests.coffee | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 5cc9a5e2f4..478d91bfa7 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -33,7 +33,10 @@ module.exports = CompileController = status: status error: error?.message or error outputFiles: outputFiles.map (file) -> - url: "#{Settings.apis.clsi.url}/project/#{request.project_id}/output/#{file.path}" + url: + "#{Settings.apis.clsi.url}/project/#{request.project_id}" + + (if file.build? then "/build/#{file.build}" else "") + + "/output/#{file.path}" path: file.path type: file.type build: file.build diff --git a/services/clsi/test/unit/coffee/CompileControllerTests.coffee b/services/clsi/test/unit/coffee/CompileControllerTests.coffee index d8d6e8620f..7693799e4c 100644 --- a/services/clsi/test/unit/coffee/CompileControllerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileControllerTests.coffee @@ -75,7 +75,7 @@ describe "CompileController", -> status: "success" error: null outputFiles: @output_files.map (file) => - url: "#{@Settings.apis.clsi.url}/project/#{@project_id}/output/#{file.path}" + url: "#{@Settings.apis.clsi.url}/project/#{@project_id}/build/#{file.build}/output/#{file.path}" path: file.path type: file.type build: file.build From df641549c4e8828955e75dcb77855a1ce4cf9956 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 27 May 2016 15:29:26 +0100 Subject: [PATCH 175/754] add per-user routes and methods --- services/clsi/app.coffee | 12 ++++- .../clsi/app/coffee/CompileController.coffee | 11 ++-- .../clsi/app/coffee/CompileManager.coffee | 50 ++++++++++++------- .../unit/coffee/CompileControllerTests.coffee | 12 ++--- .../unit/coffee/CompileManagerTests.coffee | 25 +++++----- 5 files changed, 68 insertions(+), 42 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 9f54f4a016..cfe8ebccdc 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -42,8 +42,8 @@ app.param 'project_id', (req, res, next, project_id) -> else next new Error("invalid project id") -app.param 'user_id', (req, res, next, project_id) -> - if project_id?.match /^[a-zA-Z0-9_-]+$/ +app.param 'user_id', (req, res, next, user_id) -> + if user_id?.match /^[0-9a-f]{24}$/ next() else next new Error("invalid user id") @@ -63,6 +63,14 @@ app.get "/project/:project_id/sync/pdf", CompileController.syncFromPdf app.get "/project/:project_id/wordcount", CompileController.wordcount app.get "/project/:project_id/status", CompileController.status +# Per-user containers +app.post "/project/:project_id/user/:user_id/compile", bodyParser.json(limit: "5mb"), CompileController.compile +# app.delete "/project/:project_id/user/:user_id", CompileController.clearCache + +app.get "/project/:project_id/user/:user_id/sync/code", CompileController.syncFromCode +app.get "/project/:project_id/user/:user_id/sync/pdf", CompileController.syncFromPdf +app.get "/project/:project_id/user/:user_id/wordcount", CompileController.wordcount + ForbidSymlinks = require "./app/js/StaticServerForbidSymlinks" # create a static server which does not allow access to any symlinks diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 478d91bfa7..7e1f07890a 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -11,6 +11,7 @@ module.exports = CompileController = RequestParser.parse req.body, (error, request) -> return next(error) if error? request.project_id = req.params.project_id + request.user_id = req.params.user_id if req.params.user_id? ProjectPersistenceManager.markProjectAsJustAccessed request.project_id, (error) -> return next(error) if error? CompileManager.doCompile request, (error, outputFiles = []) -> @@ -35,6 +36,7 @@ module.exports = CompileController = outputFiles: outputFiles.map (file) -> url: "#{Settings.apis.clsi.url}/project/#{request.project_id}" + + (if request.user_id? then "/user/#{request.user_id}" else "") + (if file.build? then "/build/#{file.build}" else "") + "/output/#{file.path}" path: file.path @@ -52,8 +54,9 @@ module.exports = CompileController = line = parseInt(req.query.line, 10) column = parseInt(req.query.column, 10) project_id = req.params.project_id + user_id = req.params.user_id - CompileManager.syncFromCode project_id, file, line, column, (error, pdfPositions) -> + CompileManager.syncFromCode project_id, user_id, file, line, column, (error, pdfPositions) -> return next(error) if error? res.send JSON.stringify { pdf: pdfPositions @@ -64,8 +67,9 @@ module.exports = CompileController = h = parseFloat(req.query.h) v = parseFloat(req.query.v) project_id = req.params.project_id + user_id = req.params.user_id - CompileManager.syncFromPdf project_id, page, h, v, (error, codePositions) -> + CompileManager.syncFromPdf project_id, user_id, page, h, v, (error, codePositions) -> return next(error) if error? res.send JSON.stringify { code: codePositions @@ -74,10 +78,11 @@ module.exports = CompileController = wordcount: (req, res, next = (error) ->) -> file = req.query.file || "main.tex" project_id = req.params.project_id + user_id = req.params.user_id image = req.query.image logger.log {image, file, project_id}, "word count request" - CompileManager.wordcount project_id, file, image, (error, result) -> + CompileManager.wordcount project_id, user_id, file, image, (error, result) -> return next(error) if error? res.send JSON.stringify { texcount: result diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 41377abaf8..cd332a4f9f 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -15,17 +15,23 @@ commandRunner = Settings.clsi?.commandRunner or "./CommandRunner" logger.info commandRunner:commandRunner, "selecting command runner for clsi" CommandRunner = require(commandRunner) +getCompileName = (project_id, user_id) -> + if user_id? then "#{project_id}-#{user_id}" else project_id + +getCompileDir = (project_id, user_id) -> + Path.join(Settings.path.compilesDir, getCompileName(project_id, user_id)) + module.exports = CompileManager = doCompile: (request, callback = (error, outputFiles) ->) -> - compileDir = Path.join(Settings.path.compilesDir, request.project_id) + compileDir = getCompileDir(request.project_id, request.user_id) timer = new Metrics.Timer("write-to-disk") - logger.log project_id: request.project_id, "starting doCompile" + logger.log project_id: request.project_id, user_id: request.user_id, "starting compile" ResourceWriter.syncResourcesToDisk request.project_id, request.resources, compileDir, (error) -> if error? - logger.err err:error, project_id: request.project_id, "error writing resources to disk" + logger.err err:error, project_id: request.project_id, user_id: request.user_id, "error writing resources to disk" return callback(error) - logger.log project_id: request.project_id, time_taken: Date.now() - timer.start, "written files to disk" + logger.log project_id: request.project_id, user_id: request.user_id, time_taken: Date.now() - timer.start, "written files to disk" timer.done() injectDraftModeIfRequired = (callback) -> @@ -42,7 +48,8 @@ module.exports = CompileManager = tag = "other" if not request.project_id.match(/^[0-9a-f]{24}$/) # exclude smoke test Metrics.inc("compiles") Metrics.inc("compiles-with-image.#{tag}") - LatexRunner.runLatex request.project_id, { + compileName = getCompileName(request.project_id, request.user_id) + LatexRunner.runLatex compileName, { directory: compileDir mainFile: request.rootResourcePath compiler: request.compiler @@ -58,7 +65,7 @@ module.exports = CompileManager = loadavg = os.loadavg?() Metrics.gauge("load-avg", loadavg[0]) if loadavg? ts = timer.done() - logger.log {project_id: request.project_id, time_taken: ts, stats:stats, timings:timings, loadavg:loadavg}, "done compile" + logger.log {project_id: request.project_id, user_id: request.user_id, time_taken: ts, stats:stats, timings:timings, loadavg:loadavg}, "done compile" if stats?["latex-runs"] > 0 Metrics.timing("run-compile-per-pass", ts / stats["latex-runs"]) if stats?["latex-runs"] > 0 and timings?["cpu-time"] > 0 @@ -106,24 +113,28 @@ module.exports = CompileManager = else callback(null, true) # directory exists - syncFromCode: (project_id, file_name, line, column, callback = (error, pdfPositions) ->) -> + syncFromCode: (project_id, user_id, file_name, line, column, callback = (error, pdfPositions) ->) -> # If LaTeX was run in a virtual environment, the file path that synctex expects # might not match the file path on the host. The .synctex.gz file however, will be accessed # wherever it is on the host. - base_dir = Settings.path.synctexBaseDir(project_id) + compileName = getCompileName(project_id, user_id) + base_dir = Settings.path.synctexBaseDir(compileName) file_path = base_dir + "/" + file_name - synctex_path = Path.join(Settings.path.compilesDir, project_id, "output.pdf") + compileDir = getCompileDir(project_id, user_id) + synctex_path = Path.join(compileDir, "output.pdf") CompileManager._runSynctex ["code", synctex_path, file_path, line, column], (error, stdout) -> return callback(error) if error? - logger.log project_id: project_id, file_name: file_name, line: line, column: column, stdout: stdout, "synctex code output" + logger.log project_id: project_id, user_id:user_id, file_name: file_name, line: line, column: column, stdout: stdout, "synctex code output" callback null, CompileManager._parseSynctexFromCodeOutput(stdout) - syncFromPdf: (project_id, page, h, v, callback = (error, filePositions) ->) -> - base_dir = Settings.path.synctexBaseDir(project_id) - synctex_path = Path.join(Settings.path.compilesDir, project_id, "output.pdf") + syncFromPdf: (project_id, user_id, page, h, v, callback = (error, filePositions) ->) -> + compileName = getCompileName(project_id, user_id) + base_dir = Settings.path.synctexBaseDir(compileName) + compileDir = getCompileDir(project_id, user_id) + synctex_path = Path.join(compileDir, "output.pdf") CompileManager._runSynctex ["pdf", synctex_path, page, h, v], (error, stdout) -> return callback(error) if error? - logger.log project_id: project_id, page: page, h: h, v:v, stdout: stdout, "synctex pdf output" + logger.log project_id: project_id, user_id:user_id, page: page, h: h, v:v, stdout: stdout, "synctex pdf output" callback null, CompileManager._parseSynctexFromPdfOutput(stdout, base_dir) _runSynctex: (args, callback = (error, stdout) ->) -> @@ -162,19 +173,20 @@ module.exports = CompileManager = } return results - wordcount: (project_id, file_name, image, callback = (error, pdfPositions) ->) -> - logger.log project_id:project_id, file_name:file_name, image:image, "running wordcount" + wordcount: (project_id, user_id, file_name, image, callback = (error, pdfPositions) ->) -> + logger.log project_id:project_id, user_id:user_id, file_name:file_name, image:image, "running wordcount" file_path = "$COMPILE_DIR/" + file_name command = [ "texcount", '-inc', file_path, "-out=" + file_path + ".wc"] - directory = Path.join(Settings.path.compilesDir, project_id) + directory = getCompileDir(project_id, user_id) timeout = 10 * 1000 + compileName = getCompileName(project_id, user_id) - CommandRunner.run project_id, command, directory, image, timeout, (error) -> + CommandRunner.run compileName, command, directory, image, timeout, (error) -> return callback(error) if error? try stdout = fs.readFileSync(directory + "/" + file_name + ".wc", "utf-8") catch err - logger.err err:err, command:command, directory:directory, project_id:project_id, "error reading word count output" + logger.err err:err, command:command, directory:directory, project_id:project_id, user_id:user_id, "error reading word count output" return callback(err) callback null, CompileManager._parseWordcountFromOutput(stdout) diff --git a/services/clsi/test/unit/coffee/CompileControllerTests.coffee b/services/clsi/test/unit/coffee/CompileControllerTests.coffee index 7693799e4c..1fc6a99bd5 100644 --- a/services/clsi/test/unit/coffee/CompileControllerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileControllerTests.coffee @@ -146,12 +146,12 @@ describe "CompileController", -> column: @column.toString() @res.send = sinon.stub() - @CompileManager.syncFromCode = sinon.stub().callsArgWith(4, null, @pdfPositions = ["mock-positions"]) + @CompileManager.syncFromCode = sinon.stub().callsArgWith(5, null, @pdfPositions = ["mock-positions"]) @CompileController.syncFromCode @req, @res, @next it "should find the corresponding location in the PDF", -> @CompileManager.syncFromCode - .calledWith(@project_id, @file, @line, @column) + .calledWith(@project_id, undefined, @file, @line, @column) .should.equal true it "should return the positions", -> @@ -175,12 +175,12 @@ describe "CompileController", -> v: @v.toString() @res.send = sinon.stub() - @CompileManager.syncFromPdf = sinon.stub().callsArgWith(4, null, @codePositions = ["mock-positions"]) + @CompileManager.syncFromPdf = sinon.stub().callsArgWith(5, null, @codePositions = ["mock-positions"]) @CompileController.syncFromPdf @req, @res, @next it "should find the corresponding location in the code", -> @CompileManager.syncFromPdf - .calledWith(@project_id, @page, @h, @v) + .calledWith(@project_id, undefined, @page, @h, @v) .should.equal true it "should return the positions", -> @@ -201,12 +201,12 @@ describe "CompileController", -> image: @image = "example.com/image" @res.send = sinon.stub() - @CompileManager.wordcount = sinon.stub().callsArgWith(3, null, @texcount = ["mock-texcount"]) + @CompileManager.wordcount = sinon.stub().callsArgWith(4, null, @texcount = ["mock-texcount"]) @CompileController.wordcount @req, @res, @next it "should return the word count of a file", -> @CompileManager.wordcount - .calledWith(@project_id, @file, @image) + .calledWith(@project_id, undefined, @file, @image) .should.equal true it "should return the texcount info", -> diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 55f5cc5200..0ba62b95c1 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -43,11 +43,12 @@ describe "CompileManager", -> resources: @resources = "mock-resources" rootResourcePath: @rootResourcePath = "main.tex" project_id: @project_id = "project-id-123" + user_id: @user_id = "1234" compiler: @compiler = "pdflatex" timeout: @timeout = 42000 imageName: @image = "example.com/image" @Settings.compileDir = "compiles" - @compileDir = "#{@Settings.path.compilesDir}/#{@project_id}" + @compileDir = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}" @ResourceWriter.syncResourcesToDisk = sinon.stub().callsArg(3) @LatexRunner.runLatex = sinon.stub().callsArg(2) @OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, @output_files) @@ -65,7 +66,7 @@ describe "CompileManager", -> it "should run LaTeX", -> @LatexRunner.runLatex - .calledWith(@project_id, { + .calledWith("#{@project_id}-#{@user_id}", { directory: @compileDir mainFile: @rootResourcePath compiler: @compiler @@ -150,17 +151,17 @@ describe "CompileManager", -> @column = 3 @file_name = "main.tex" @child_process.execFile = sinon.stub() - @Settings.path.synctexBaseDir = (project_id) => "#{@Settings.path.compilesDir}/#{@project_id}" + @Settings.path.synctexBaseDir = (project_id) => "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}" describe "syncFromCode", -> beforeEach -> @child_process.execFile.callsArgWith(3, null, @stdout = "NODE\t#{@page}\t#{@h}\t#{@v}\t#{@width}\t#{@height}\n", "") - @CompileManager.syncFromCode @project_id, @file_name, @line, @column, @callback + @CompileManager.syncFromCode @project_id, @user_id, @file_name, @line, @column, @callback it "should execute the synctex binary", -> bin_path = Path.resolve(__dirname + "/../../../bin/synctex") - synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}/output.pdf" - file_path = "#{@Settings.path.compilesDir}/#{@project_id}/#{@file_name}" + synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/output.pdf" + file_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/#{@file_name}" @child_process.execFile .calledWith(bin_path, ["code", synctex_path, file_path, @line, @column], timeout: 10000) .should.equal true @@ -178,12 +179,12 @@ describe "CompileManager", -> describe "syncFromPdf", -> beforeEach -> - @child_process.execFile.callsArgWith(3, null, @stdout = "NODE\t#{@Settings.path.compilesDir}/#{@project_id}/#{@file_name}\t#{@line}\t#{@column}\n", "") - @CompileManager.syncFromPdf @project_id, @page, @h, @v, @callback + @child_process.execFile.callsArgWith(3, null, @stdout = "NODE\t#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/#{@file_name}\t#{@line}\t#{@column}\n", "") + @CompileManager.syncFromPdf @project_id, @user_id, @page, @h, @v, @callback it "should execute the synctex binary", -> bin_path = Path.resolve(__dirname + "/../../../bin/synctex") - synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}/output.pdf" + synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/output.pdf" @child_process.execFile .calledWith(bin_path, ["pdf", synctex_path, @page, @h, @v], timeout: 10000) .should.equal true @@ -209,15 +210,15 @@ describe "CompileManager", -> @Settings.path.compilesDir = "/local/compile/directory" @image = "example.com/image" - @CompileManager.wordcount @project_id, @file_name, @image, @callback + @CompileManager.wordcount @project_id, @user_id, @file_name, @image, @callback it "should run the texcount command", -> - @directory = "#{@Settings.path.compilesDir}/#{@project_id}" + @directory = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}" @file_path = "$COMPILE_DIR/#{@file_name}" @command =[ "texcount", "-inc", @file_path, "-out=" + @file_path + ".wc"] @CommandRunner.run - .calledWith(@project_id, @command, @directory, @image, @timeout) + .calledWith("#{@project_id}-#{@user_id}", @command, @directory, @image, @timeout) .should.equal true it "should call the callback with the parsed output", -> From 5367bc22e579091d9df96abe86747429f35cf1ee Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 27 May 2016 15:31:44 +0100 Subject: [PATCH 176/754] add per-user routes for clearing cache and extend expiry methods this adds separate functionality for clearing the cache (assets and database) and the project compile directory for a specific user --- services/clsi/app.coffee | 2 +- .../clsi/app/coffee/CompileController.coffee | 2 +- .../clsi/app/coffee/CompileManager.coffee | 27 ++++++++++++++-- .../coffee/ProjectPersistenceManager.coffee | 32 ++++++++++++------- .../unit/coffee/CompileManagerTests.coffee | 10 +++--- .../ProjectPersistenceManagerTests.coffee | 12 ++++--- 6 files changed, 59 insertions(+), 26 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index cfe8ebccdc..6a8d73fe1c 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -65,7 +65,7 @@ app.get "/project/:project_id/status", CompileController.status # Per-user containers app.post "/project/:project_id/user/:user_id/compile", bodyParser.json(limit: "5mb"), CompileController.compile -# app.delete "/project/:project_id/user/:user_id", CompileController.clearCache +app.delete "/project/:project_id/user/:user_id", CompileController.clearCache app.get "/project/:project_id/user/:user_id/sync/code", CompileController.syncFromCode app.get "/project/:project_id/user/:user_id/sync/pdf", CompileController.syncFromPdf diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 7e1f07890a..d4dddd4d82 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -45,7 +45,7 @@ module.exports = CompileController = } clearCache: (req, res, next = (error) ->) -> - ProjectPersistenceManager.clearProject req.params.project_id, (error) -> + ProjectPersistenceManager.clearProject req.params.project_id, req.params.user_id, (error) -> return next(error) if error? res.sendStatus(204) # No content diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index cd332a4f9f..bb93dbdb9b 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -9,7 +9,9 @@ Metrics = require "./Metrics" child_process = require "child_process" DraftModeManager = require "./DraftModeManager" fs = require("fs") +fse = require "fs-extra" os = require("os") +async = require "async" commandRunner = Settings.clsi?.commandRunner or "./CommandRunner" logger.info commandRunner:commandRunner, "selecting command runner for clsi" @@ -76,12 +78,12 @@ module.exports = CompileManager = OutputCacheManager.saveOutputFiles outputFiles, compileDir, (error, newOutputFiles) -> callback null, newOutputFiles - clearProject: (project_id, _callback = (error) ->) -> + clearProject: (project_id, user_id, _callback = (error) ->) -> callback = (error) -> _callback(error) _callback = () -> - compileDir = Path.join(Settings.path.compilesDir, project_id) + compileDir = getCompileDir(project_id, user_id) CompileManager._checkDirectory compileDir, (err, exists) -> return callback(err) if err? @@ -100,6 +102,27 @@ module.exports = CompileManager = else return callback(new Error("rm -r #{compileDir} failed: #{stderr}")) + _findAllDirs: (callback = (error, allDirs) ->) -> + root = Settings.path.compilesDir + fs.readdir root, (err, files) -> + return callback(err) if err? + allDirs = (Path.join(root, file) for file in files) + callback(null, allDirs) + + clearExpiredProjects: (max_cache_age_ms, callback = (error) ->) -> + now = Date.now() + # action for each directory + expireIfNeeded = (checkDir, cb) -> + fs.stat checkDir, (err, stats) -> + return cb() if err? # ignore errors checking directory + age = now - stats.mtime + hasExpired = (age > max_cache_age_ms) + if hasExpired then fse.remove(checkDir, cb) else cb() + # iterate over all project directories + CompileManager._findAllDirs (error, allDirs) -> + return callback() if error? + async.eachSeries allDirs, expireIfNeeded, callback + _checkDirectory: (compileDir, callback = (error, exists) ->) -> fs.lstat compileDir, (err, stats) -> if err?.code is 'ENOENT' diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.coffee b/services/clsi/app/coffee/ProjectPersistenceManager.coffee index f70f43cab7..200d977fc6 100644 --- a/services/clsi/app/coffee/ProjectPersistenceManager.coffee +++ b/services/clsi/app/coffee/ProjectPersistenceManager.coffee @@ -27,21 +27,30 @@ module.exports = ProjectPersistenceManager = jobs = for project_id in (project_ids or []) do (project_id) -> (callback) -> - ProjectPersistenceManager.clearProject project_id, (err) -> + ProjectPersistenceManager.clearProjectFromCache project_id, (err) -> if err? logger.error err: err, project_id: project_id, "error clearing project" callback() - async.series jobs, callback - - clearProject: (project_id, callback = (error) ->) -> - logger.log project_id: project_id, "clearing project" - CompileManager.clearProject project_id, (error) -> - return callback(error) if error? - UrlCache.clearProject project_id, (error) -> + async.series jobs, (error) -> return callback(error) if error? - ProjectPersistenceManager._clearProjectFromDatabase project_id, (error) -> - return callback(error) if error? - callback() + CompileManager.clearExpiredProjects ProjectPersistenceManager.EXPIRY_TIMEOUT, (error) -> + callback() # ignore any errors from deleting directories + + clearProject: (project_id, user_id, callback = (error) ->) -> + logger.log project_id: project_id, "clearing project for user" + CompileManager.clearProject project_id, user_id, (error) -> + return callback(error) if error? + ProjectPersistenceManager.clearProjectFromCache project_id, (error) -> + return callback(error) if error? + callback() + + clearProjectFromCache: (project_id, callback = (error) ->) -> + logger.log project_id: project_id, "clearing project from cache" + UrlCache.clearProject project_id, (error) -> + return callback(error) if error? + ProjectPersistenceManager._clearProjectFromDatabase project_id, (error) -> + return callback(error) if error? + callback() _clearProjectFromDatabase: (project_id, callback = (error) ->) -> db.Project.destroy(where: {project_id: project_id}) @@ -54,5 +63,4 @@ module.exports = ProjectPersistenceManager = callback null, projects.map((project) -> project.project_id) ).error callback - logger.log {EXPIRY_TIMEOUT: ProjectPersistenceManager.EXPIRY_TIMEOUT}, "project assets kept timeout" diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 0ba62b95c1..611ed11b01 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -105,12 +105,12 @@ describe "CompileManager", -> @proc.stdout = new EventEmitter() @proc.stderr = new EventEmitter() @child_process.spawn = sinon.stub().returns(@proc) - @CompileManager.clearProject @project_id, @callback + @CompileManager.clearProject @project_id, @user_id, @callback @proc.emit "close", 0 it "should remove the project directory", -> @child_process.spawn - .calledWith("rm", ["-r", "#{@Settings.path.compilesDir}/#{@project_id}"]) + .calledWith("rm", ["-r", "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}"]) .should.equal true it "should call the callback", -> @@ -124,13 +124,13 @@ describe "CompileManager", -> @proc.stdout = new EventEmitter() @proc.stderr = new EventEmitter() @child_process.spawn = sinon.stub().returns(@proc) - @CompileManager.clearProject @project_id, @callback + @CompileManager.clearProject @project_id, @user_id, @callback @proc.stderr.emit "data", @error = "oops" @proc.emit "close", 1 it "should remove the project directory", -> @child_process.spawn - .calledWith("rm", ["-r", "#{@Settings.path.compilesDir}/#{@project_id}"]) + .calledWith("rm", ["-r", "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}"]) .should.equal true it "should call the callback with an error from the stderr", -> @@ -138,7 +138,7 @@ describe "CompileManager", -> .calledWith(new Error()) .should.equal true - @callback.args[0][0].message.should.equal "rm -r #{@Settings.path.compilesDir}/#{@project_id} failed: #{@error}" + @callback.args[0][0].message.should.equal "rm -r #{@Settings.path.compilesDir}/#{@project_id}-#{@user_id} failed: #{@error}" describe "syncing", -> beforeEach -> diff --git a/services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.coffee b/services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.coffee index f8c78ef783..69bfd4fa5e 100644 --- a/services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.coffee +++ b/services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.coffee @@ -13,6 +13,7 @@ describe "ProjectPersistenceManager", -> "./db": @db = {} @callback = sinon.stub() @project_id = "project-id-123" + @user_id = "1234" describe "clearExpiredProjects", -> beforeEach -> @@ -21,12 +22,13 @@ describe "ProjectPersistenceManager", -> "project-id-2" ] @ProjectPersistenceManager._findExpiredProjectIds = sinon.stub().callsArgWith(0, null, @project_ids) - @ProjectPersistenceManager.clearProject = sinon.stub().callsArg(1) + @ProjectPersistenceManager.clearProjectFromCache = sinon.stub().callsArg(1) + @CompileManager.clearExpiredProjects = sinon.stub().callsArg(1) @ProjectPersistenceManager.clearExpiredProjects @callback it "should clear each expired project", -> for project_id in @project_ids - @ProjectPersistenceManager.clearProject + @ProjectPersistenceManager.clearProjectFromCache .calledWith(project_id) .should.equal true @@ -37,8 +39,8 @@ describe "ProjectPersistenceManager", -> beforeEach -> @ProjectPersistenceManager._clearProjectFromDatabase = sinon.stub().callsArg(1) @UrlCache.clearProject = sinon.stub().callsArg(1) - @CompileManager.clearProject = sinon.stub().callsArg(1) - @ProjectPersistenceManager.clearProject @project_id, @callback + @CompileManager.clearProject = sinon.stub().callsArg(2) + @ProjectPersistenceManager.clearProject @project_id, @user_id, @callback it "should clear the project from the database", -> @ProjectPersistenceManager._clearProjectFromDatabase @@ -52,7 +54,7 @@ describe "ProjectPersistenceManager", -> it "should clear the project compile folder", -> @CompileManager.clearProject - .calledWith(@project_id) + .calledWith(@project_id, @user_id) .should.equal true it "should call the callback", -> From d3395ef763bcc659bcec38528d5aad61a572b474 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 31 May 2016 16:12:55 +0100 Subject: [PATCH 177/754] log user_id when clearing project --- services/clsi/app/coffee/ProjectPersistenceManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.coffee b/services/clsi/app/coffee/ProjectPersistenceManager.coffee index 200d977fc6..403043fac9 100644 --- a/services/clsi/app/coffee/ProjectPersistenceManager.coffee +++ b/services/clsi/app/coffee/ProjectPersistenceManager.coffee @@ -37,7 +37,7 @@ module.exports = ProjectPersistenceManager = callback() # ignore any errors from deleting directories clearProject: (project_id, user_id, callback = (error) ->) -> - logger.log project_id: project_id, "clearing project for user" + logger.log project_id: project_id, user_id:user_id, "clearing project for user" CompileManager.clearProject project_id, user_id, (error) -> return callback(error) if error? ProjectPersistenceManager.clearProjectFromCache project_id, (error) -> From f0dc5d6f861da48c28fb4bedf39fb0c26dfa2174 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 7 Jun 2016 14:38:17 +0100 Subject: [PATCH 178/754] add random string to smoke tests to avoid collision --- services/clsi/test/smoke/coffee/SmokeTests.coffee | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/services/clsi/test/smoke/coffee/SmokeTests.coffee b/services/clsi/test/smoke/coffee/SmokeTests.coffee index 48c50d6681..ca3082c969 100644 --- a/services/clsi/test/smoke/coffee/SmokeTests.coffee +++ b/services/clsi/test/smoke/coffee/SmokeTests.coffee @@ -6,10 +6,14 @@ Settings = require "settings-sharelatex" buildUrl = (path) -> "http://#{Settings.internal.clsi.host}:#{Settings.internal.clsi.port}/#{path}" +random = require("crypto").randomBytes(4).toString("hex") + +url = buildUrl("project/smoketest-#{random}/compile") + describe "Running a compile", -> before (done) -> request.post { - url: buildUrl("project/smoketest/compile") + url: url json: compile: resources: [ From 989c17854633efa071763d75fd06673d9554e351 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 7 Jun 2016 14:47:51 +0100 Subject: [PATCH 179/754] use process id so link process to smoke test --- services/clsi/test/smoke/coffee/SmokeTests.coffee | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/services/clsi/test/smoke/coffee/SmokeTests.coffee b/services/clsi/test/smoke/coffee/SmokeTests.coffee index ca3082c969..372ca69d40 100644 --- a/services/clsi/test/smoke/coffee/SmokeTests.coffee +++ b/services/clsi/test/smoke/coffee/SmokeTests.coffee @@ -6,9 +6,7 @@ Settings = require "settings-sharelatex" buildUrl = (path) -> "http://#{Settings.internal.clsi.host}:#{Settings.internal.clsi.port}/#{path}" -random = require("crypto").randomBytes(4).toString("hex") - -url = buildUrl("project/smoketest-#{random}/compile") +url = buildUrl("project/smoketest-#{process.pid}/compile") describe "Running a compile", -> before (done) -> From a6f6392e409eec2d370705ee87b508064e77088b Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Wed, 15 Jun 2016 16:12:19 +0100 Subject: [PATCH 180/754] add route to serve files from top level of per user containers --- services/clsi/app.coffee | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 6a8d73fe1c..fb3224e59d 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -96,6 +96,11 @@ app.get "/project/:project_id/build/:build_id/output/*", (req, res, next) -> req.url = "/#{req.params.project_id}/" + OutputCacheManager.path(req.params.build_id, "/#{req.params[0]}") staticServer(req, res, next) +app.get "/project/:project_id/user/:user_id/output/*", (req, res, next) -> + # for specific user get the path to the top level file + req.url = "/#{req.params.project_id}-#{req.params.user_id}/#{req.params[0]}" + staticServer(req, res, next) + app.get "/project/:project_id/output/*", (req, res, next) -> if req.query?.build? && req.query.build.match(OutputCacheManager.BUILD_REGEX) # for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) From 638ac52e4053a8b8e5bedeb2726532643274f4ca Mon Sep 17 00:00:00 2001 From: Shane Kilkelly <shane@kilkelly.me> Date: Fri, 17 Jun 2016 14:38:08 +0100 Subject: [PATCH 181/754] Move the latexmk timing command into a configurable `latexmkCommandPrefix`. By default, no timing information will be taken. On Linux with GNU user land, this value should be configured to `["/usr/bin/time", "-v"]`. On Mac, gnu-time should be installed and configured to `["/usr/local/bin/gtime", "-v"]`. --- services/clsi/app/coffee/LatexRunner.coffee | 4 +++- services/clsi/config/settings.defaults.coffee | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/LatexRunner.coffee b/services/clsi/app/coffee/LatexRunner.coffee index 65a30464ee..748c277ff4 100644 --- a/services/clsi/app/coffee/LatexRunner.coffee +++ b/services/clsi/app/coffee/LatexRunner.coffee @@ -49,7 +49,9 @@ module.exports = LatexRunner = timings["sys-time"] = stderr?.match(/System time.*: (\d+.\d+)/m)?[1] or 0 callback error, output, stats, timings - _latexmkBaseCommand: ["/usr/bin/time", "-v", "latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR"] + _latexmkBaseCommand: (Settings?.clsi?.latexmkCommandPrefix || []).concat( + ["latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR"] + ) _pdflatexCommand: (mainFile) -> LatexRunner._latexmkBaseCommand.concat [ diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index ae8e132346..f77df86b1a 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -28,6 +28,9 @@ module.exports = # modem: # socketPath: false # user: "tex" + # latexmkCommandPrefix: [] + # # latexmkCommandPrefix: ["/usr/bin/time", "-v"] # on Linux + # # latexmkCommandPrefix: ["/usr/local/bin/gtime", "-v"] # on Mac OSX, installed with `brew install gnu-time` internal: clsi: From 6314dda836f110f39b367d332d3504f0252a6b11 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly <shane@kilkelly.me> Date: Mon, 20 Jun 2016 09:31:30 +0100 Subject: [PATCH 182/754] Upgrade to node 4.2 --- services/clsi/.nvmrc | 1 + services/clsi/package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 services/clsi/.nvmrc diff --git a/services/clsi/.nvmrc b/services/clsi/.nvmrc new file mode 100644 index 0000000000..bf77d54968 --- /dev/null +++ b/services/clsi/.nvmrc @@ -0,0 +1 @@ +4.2 diff --git a/services/clsi/package.json b/services/clsi/package.json index eaa9be8cd1..748256b9ed 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -19,7 +19,7 @@ "sequelize": "^2.1.3", "wrench": "~1.5.4", "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", - "sqlite3": "~2.2.0", + "sqlite3": "~3.1.4", "express": "^4.2.0", "body-parser": "^1.2.0", "fs-extra": "^0.16.3", From 03188d2a57070aa3f85e17c84d2ca137841b47a0 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Wed, 8 Jun 2016 16:58:08 +0100 Subject: [PATCH 183/754] only keep a single cached output directory in per-user containers --- services/clsi/app/coffee/OutputCacheManager.coffee | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee index 7f11bc89b0..25c24b885a 100644 --- a/services/clsi/app/coffee/OutputCacheManager.coffee +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -45,9 +45,8 @@ module.exports = OutputCacheManager = cacheRoot = Path.join(compileDir, OutputCacheManager.CACHE_SUBDIR) # Put the files into a new cache subdirectory cacheDir = Path.join(compileDir, OutputCacheManager.CACHE_SUBDIR, buildId) - - # let file expiry run in the background - OutputCacheManager.expireOutputFiles cacheRoot, {keep: buildId} + # Is it a per-user compile? check if compile directory is PROJECTID-USERID + perUser = Path.basename(compileDir).match(/^[0-9a-f]{24}-[0-9a-f]{24}$/) # Archive logs in background if Settings.clsi?.archive_logs or Settings.clsi?.strace @@ -83,9 +82,15 @@ module.exports = OutputCacheManager = if err? # pass back the original files if we encountered *any* error callback(err, outputFiles) + # clean up the directory we just created + fse.remove cacheDir, (err) -> + if err? + logger.error err: err, dir: dir, "error removing cache dir after failure" else # pass back the list of new files in the cache callback(err, results) + # let file expiry run in the background, expire all previous files if per-user + OutputCacheManager.expireOutputFiles cacheRoot, {keep: buildId, limit: if perUser then 0 else null} archiveLogs: (outputFiles, compileDir, buildId, callback = (error) ->) -> archiveDir = Path.join(compileDir, OutputCacheManager.ARCHIVE_SUBDIR, buildId) @@ -116,6 +121,8 @@ module.exports = OutputCacheManager = isExpired = (dir, index) -> return false if options?.keep == dir + # remove any directories over the requested (non-null) limit + return true if options?.limit? and index > options.limit # remove any directories over the hard limit return true if index > OutputCacheManager.CACHE_LIMIT # we can get the build time from the first part of the directory name DDDD-RRRR From dd4e1e17b588ab5bcb304d79ff8a40f560b0b0df Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Wed, 29 Jun 2016 16:31:08 +0100 Subject: [PATCH 184/754] keep one extra build until per-page pdf serving is enabled --- services/clsi/app/coffee/OutputCacheManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee index 25c24b885a..76692b3b71 100644 --- a/services/clsi/app/coffee/OutputCacheManager.coffee +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -90,7 +90,7 @@ module.exports = OutputCacheManager = # pass back the list of new files in the cache callback(err, results) # let file expiry run in the background, expire all previous files if per-user - OutputCacheManager.expireOutputFiles cacheRoot, {keep: buildId, limit: if perUser then 0 else null} + OutputCacheManager.expireOutputFiles cacheRoot, {keep: buildId, limit: if perUser then 1 else null} archiveLogs: (outputFiles, compileDir, buildId, callback = (error) ->) -> archiveDir = Path.join(compileDir, OutputCacheManager.ARCHIVE_SUBDIR, buildId) From 92461742d0cc305bbc4513495067261d50a53bbe Mon Sep 17 00:00:00 2001 From: WaeCo <aaqqws@googlemail.com> Date: Wed, 13 Jul 2016 13:26:32 -0700 Subject: [PATCH 185/754] Set default project_cache_length_ms to 1 day `project_cache_length_ms` was only `60*60*24 = 1.5 min` which is a little bit short. Default of one day seams more reasonable. --- services/clsi/config/settings.defaults.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index f77df86b1a..f1f7492a63 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -44,5 +44,5 @@ module.exports = url: "http://localhost:3013" smokeTest: false - project_cache_length_ms: 60 * 60 * 24 - parallelFileDownloads:1 \ No newline at end of file + project_cache_length_ms: 1000 * 60 * 60 * 24 + parallelFileDownloads:1 From 9b58621fa8b0dadb124c4236186999c7919ed6ac Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 14 Jul 2016 14:47:36 +0100 Subject: [PATCH 186/754] add support for stopping compile --- services/clsi/app.coffee | 2 ++ services/clsi/app/coffee/CommandRunner.coffee | 24 ++++++++++++++++--- .../clsi/app/coffee/CompileController.coffee | 12 ++++++++-- .../clsi/app/coffee/CompileManager.coffee | 11 +++++++++ services/clsi/app/coffee/LatexRunner.coffee | 15 +++++++++++- 5 files changed, 58 insertions(+), 6 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index fb3224e59d..d13798c4ea 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -56,6 +56,7 @@ app.param 'build_id', (req, res, next, build_id) -> app.post "/project/:project_id/compile", bodyParser.json(limit: "5mb"), CompileController.compile +app.post "/project/:project_id/compile/stop", CompileController.stopCompile app.delete "/project/:project_id", CompileController.clearCache app.get "/project/:project_id/sync/code", CompileController.syncFromCode @@ -65,6 +66,7 @@ app.get "/project/:project_id/status", CompileController.status # Per-user containers app.post "/project/:project_id/user/:user_id/compile", bodyParser.json(limit: "5mb"), CompileController.compile +app.post "/project/:project_id/user/:user_id/compile/stop", CompileController.stopCompile app.delete "/project/:project_id/user/:user_id", CompileController.clearCache app.get "/project/:project_id/user/:user_id/sync/code", CompileController.syncFromCode diff --git a/services/clsi/app/coffee/CommandRunner.coffee b/services/clsi/app/coffee/CommandRunner.coffee index 5ea6765c08..1d0b9e418b 100644 --- a/services/clsi/app/coffee/CommandRunner.coffee +++ b/services/clsi/app/coffee/CommandRunner.coffee @@ -9,9 +9,27 @@ module.exports = CommandRunner = logger.log project_id: project_id, command: command, directory: directory, "running command" logger.warn "timeouts and sandboxing are not enabled with CommandRunner" - proc = spawn command[0], command.slice(1), stdio: "inherit", cwd: directory + # run command as detached process so it has its own process group (which can be killed if needed) + proc = spawn command[0], command.slice(1), stdio: "inherit", cwd: directory, detached: true + proc.on "error", (err)-> logger.err err:err, project_id:project_id, command: command, directory: directory, "error running command" callback(err) - proc.on "close", () -> - callback() \ No newline at end of file + + proc.on "close", (code, signal) -> + logger.info code:code, signal:signal, project_id:project_id, "command exited" + if signal is 'SIGTERM' # signal from kill method below + err = new Error("terminated") + err.terminated = true + return callback(err) + else + callback() + + return proc.pid # return process id to allow job to be killed if necessary + + kill: (pid, callback = (error) ->) -> + try + process.kill -pid # kill all processes in group + catch err + return callback(err) + callback() diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index d4dddd4d82..610baac22e 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -15,7 +15,9 @@ module.exports = CompileController = ProjectPersistenceManager.markProjectAsJustAccessed request.project_id, (error) -> return next(error) if error? CompileManager.doCompile request, (error, outputFiles = []) -> - if error? + if error?.terminated + status = "terminated" + else if error? logger.error err: error, project_id: request.project_id, "error running compile" if error.timedout status = "timedout" @@ -43,7 +45,13 @@ module.exports = CompileController = type: file.type build: file.build } - + + stopCompile: (req, res, next) -> + {project_id, user_id, session_id} = req.params + CompileManager.stopCompile project_id, user_id, (error) -> + return next(error) if error? + res.sendStatus(204) + clearCache: (req, res, next = (error) ->) -> ProjectPersistenceManager.clearProject req.params.project_id, req.params.user_id, (error) -> return next(error) if error? diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index bb93dbdb9b..87e8734549 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -58,6 +58,13 @@ module.exports = CompileManager = timeout: request.timeout image: request.imageName }, (error, output, stats, timings) -> + # compile was killed by user + if error?.terminated + OutputFileFinder.findOutputFiles request.resources, compileDir, (err, outputFiles) -> + return callback(err) if err? + callback(error, outputFiles) # return output files so user can check logs + return + # compile completed normally return callback(error) if error? Metrics.inc("compiles-succeeded") for metric_key, metric_value of stats or {} @@ -78,6 +85,10 @@ module.exports = CompileManager = OutputCacheManager.saveOutputFiles outputFiles, compileDir, (error, newOutputFiles) -> callback null, newOutputFiles + stopCompile: (project_id, user_id, callback = (error) ->) -> + compileName = getCompileName(project_id, user_id) + LatexRunner.killLatex compileName, callback + clearProject: (project_id, user_id, _callback = (error) ->) -> callback = (error) -> _callback(error) diff --git a/services/clsi/app/coffee/LatexRunner.coffee b/services/clsi/app/coffee/LatexRunner.coffee index 748c277ff4..8619e9baca 100644 --- a/services/clsi/app/coffee/LatexRunner.coffee +++ b/services/clsi/app/coffee/LatexRunner.coffee @@ -4,6 +4,8 @@ logger = require "logger-sharelatex" Metrics = require "./Metrics" CommandRunner = require(Settings.clsi?.commandRunner or "./CommandRunner") +ProcessTable = {} # table of currently running jobs (pids or docker container names) + module.exports = LatexRunner = runLatex: (project_id, options, callback = (error) ->) -> {directory, mainFile, compiler, timeout, image} = options @@ -30,7 +32,10 @@ module.exports = LatexRunner = if Settings.clsi?.strace command = ["strace", "-o", "strace", "-ff"].concat(command) - CommandRunner.run project_id, command, directory, image, timeout, (error, output) -> + id = "#{project_id}" # record running project under this id + + ProcessTable[id] = CommandRunner.run project_id, command, directory, image, timeout, (error, output) -> + delete ProcessTable[id] return callback(error) if error? runs = output?.stderr?.match(/^Run number \d+ of .*latex/mg)?.length or 0 failed = if output?.stdout?.match(/^Latexmk: Errors/m)? then 1 else 0 @@ -49,6 +54,14 @@ module.exports = LatexRunner = timings["sys-time"] = stderr?.match(/System time.*: (\d+.\d+)/m)?[1] or 0 callback error, output, stats, timings + killLatex: (project_id, callback = (error) ->) -> + id = "#{project_id}" + logger.log {id:id}, "killing running compile" + if not ProcessTable[id]? + return callback new Error("no such project to kill") + else + CommandRunner.kill ProcessTable[id], callback + _latexmkBaseCommand: (Settings?.clsi?.latexmkCommandPrefix || []).concat( ["latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR"] ) From bd1e1de504ce06572f4069eaa54a245849a8f2cf Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Mon, 18 Jul 2016 11:05:45 +0100 Subject: [PATCH 187/754] remove dead code --- services/clsi/app/coffee/CompileController.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 610baac22e..38d53f934b 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -47,7 +47,7 @@ module.exports = CompileController = } stopCompile: (req, res, next) -> - {project_id, user_id, session_id} = req.params + {project_id, user_id} = req.params CompileManager.stopCompile project_id, user_id, (error) -> return next(error) if error? res.sendStatus(204) From 3135ff2842643cf631cfc1b27fc8304e9641baa7 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 26 Jul 2016 12:30:29 +0100 Subject: [PATCH 188/754] add support for passing additional environment parameters to command runner includes an example of passing environment variables to chktex --- services/clsi/app/coffee/CommandRunner.coffee | 9 +++++++-- services/clsi/app/coffee/CompileManager.coffee | 10 +++++++++- services/clsi/app/coffee/LatexRunner.coffee | 6 +++--- .../clsi/test/unit/coffee/CompileManagerTests.coffee | 2 ++ services/clsi/test/unit/coffee/LatexRunnerTests.coffee | 6 ++++-- 5 files changed, 25 insertions(+), 8 deletions(-) diff --git a/services/clsi/app/coffee/CommandRunner.coffee b/services/clsi/app/coffee/CommandRunner.coffee index 1d0b9e418b..298a495051 100644 --- a/services/clsi/app/coffee/CommandRunner.coffee +++ b/services/clsi/app/coffee/CommandRunner.coffee @@ -4,13 +4,18 @@ logger = require "logger-sharelatex" logger.info "using standard command runner" module.exports = CommandRunner = - run: (project_id, command, directory, image, timeout, callback = (error) ->) -> + run: (project_id, command, directory, image, timeout, environment, callback = (error) ->) -> command = (arg.replace('$COMPILE_DIR', directory) for arg in command) logger.log project_id: project_id, command: command, directory: directory, "running command" logger.warn "timeouts and sandboxing are not enabled with CommandRunner" + # merge environment settings + env = {} + env[key] = value for key, value of process.env + env[key] = value for key, value of environment + # run command as detached process so it has its own process group (which can be killed if needed) - proc = spawn command[0], command.slice(1), stdio: "inherit", cwd: directory, detached: true + proc = spawn command[0], command.slice(1), stdio: "inherit", cwd: directory, detached: true, env: env proc.on "error", (err)-> logger.err err:err, project_id:project_id, command: command, directory: directory, "error running command" diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 87e8734549..81c2c03975 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -41,7 +41,14 @@ module.exports = CompileManager = DraftModeManager.injectDraftMode Path.join(compileDir, request.rootResourcePath), callback else callback() - + + # set up environment variables for chktex + env = {} + if request.chktex? + env['CHKTEX_OPTIONS'] = '-nall -e9 -e10 -e15 -e16 -e27' + if request.chktex is 'error' + env['CHKTEX_EXIT_ON_ERROR'] = 1 + injectDraftModeIfRequired (error) -> return callback(error) if error? timer = new Metrics.Timer("run-compile") @@ -57,6 +64,7 @@ module.exports = CompileManager = compiler: request.compiler timeout: request.timeout image: request.imageName + environment: env }, (error, output, stats, timings) -> # compile was killed by user if error?.terminated diff --git a/services/clsi/app/coffee/LatexRunner.coffee b/services/clsi/app/coffee/LatexRunner.coffee index 8619e9baca..e743cf0174 100644 --- a/services/clsi/app/coffee/LatexRunner.coffee +++ b/services/clsi/app/coffee/LatexRunner.coffee @@ -8,11 +8,11 @@ ProcessTable = {} # table of currently running jobs (pids or docker container n module.exports = LatexRunner = runLatex: (project_id, options, callback = (error) ->) -> - {directory, mainFile, compiler, timeout, image} = options + {directory, mainFile, compiler, timeout, image, environment} = options compiler ||= "pdflatex" timeout ||= 60000 # milliseconds - logger.log directory: directory, compiler: compiler, timeout: timeout, mainFile: mainFile, "starting compile" + logger.log directory: directory, compiler: compiler, timeout: timeout, mainFile: mainFile, environment: environment, "starting compile" # We want to run latexmk on the tex file which we will automatically # generate from the Rtex/Rmd/md file. @@ -34,7 +34,7 @@ module.exports = LatexRunner = id = "#{project_id}" # record running project under this id - ProcessTable[id] = CommandRunner.run project_id, command, directory, image, timeout, (error, output) -> + ProcessTable[id] = CommandRunner.run project_id, command, directory, image, timeout, environment, (error, output) -> delete ProcessTable[id] return callback(error) if error? runs = output?.stderr?.match(/^Run number \d+ of .*latex/mg)?.length or 0 diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 611ed11b01..62132f89e4 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -47,6 +47,7 @@ describe "CompileManager", -> compiler: @compiler = "pdflatex" timeout: @timeout = 42000 imageName: @image = "example.com/image" + @env = {} @Settings.compileDir = "compiles" @compileDir = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}" @ResourceWriter.syncResourcesToDisk = sinon.stub().callsArg(3) @@ -72,6 +73,7 @@ describe "CompileManager", -> compiler: @compiler timeout: @timeout image: @image + environment: @env }) .should.equal true diff --git a/services/clsi/test/unit/coffee/LatexRunnerTests.coffee b/services/clsi/test/unit/coffee/LatexRunnerTests.coffee index ace3d18ab7..c26fa642b2 100644 --- a/services/clsi/test/unit/coffee/LatexRunnerTests.coffee +++ b/services/clsi/test/unit/coffee/LatexRunnerTests.coffee @@ -22,10 +22,11 @@ describe "LatexRunner", -> @image = "example.com/image" @callback = sinon.stub() @project_id = "project-id-123" + @env = {'foo': '123'} describe "runLatex", -> beforeEach -> - @CommandRunner.run = sinon.stub().callsArg(5) + @CommandRunner.run = sinon.stub().callsArg(6) describe "normally", -> beforeEach -> @@ -35,11 +36,12 @@ describe "LatexRunner", -> compiler: @compiler timeout: @timeout = 42000 image: @image + environment: @env @callback it "should run the latex command", -> @CommandRunner.run - .calledWith(@project_id, sinon.match.any, @directory, @image, @timeout) + .calledWith(@project_id, sinon.match.any, @directory, @image, @timeout, @env) .should.equal true describe "with an .Rtex main file", -> From d6d6e18b895b88285959b0fc723fe383d10307b2 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 26 Jul 2016 16:22:38 +0100 Subject: [PATCH 189/754] run chktex when request has check:true --- services/clsi/app/coffee/CommandRunner.coffee | 4 ++++ services/clsi/app/coffee/CompileController.coffee | 2 ++ services/clsi/app/coffee/CompileManager.coffee | 8 ++++---- services/clsi/app/coffee/RequestParser.coffee | 3 +++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/services/clsi/app/coffee/CommandRunner.coffee b/services/clsi/app/coffee/CommandRunner.coffee index 298a495051..11bbff82d3 100644 --- a/services/clsi/app/coffee/CommandRunner.coffee +++ b/services/clsi/app/coffee/CommandRunner.coffee @@ -27,6 +27,10 @@ module.exports = CommandRunner = err = new Error("terminated") err.terminated = true return callback(err) + else if code + err = new Error("exit") + err.code = code + return callback(err) else callback() diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 38d53f934b..3c9af677ae 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -17,6 +17,8 @@ module.exports = CompileController = CompileManager.doCompile request, (error, outputFiles = []) -> if error?.terminated status = "terminated" + else if error?.code is 1 + status = "exited" else if error? logger.error err: error, project_id: request.project_id, "error running compile" if error.timedout diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 81c2c03975..3b19cecaba 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -44,9 +44,9 @@ module.exports = CompileManager = # set up environment variables for chktex env = {} - if request.chktex? - env['CHKTEX_OPTIONS'] = '-nall -e9 -e10 -e15 -e16 -e27' - if request.chktex is 'error' + if request.check? + env['CHKTEX_OPTIONS'] = '-nall -e9 -e10 -w15 -w16 -w27' + if request.check is 'error' env['CHKTEX_EXIT_ON_ERROR'] = 1 injectDraftModeIfRequired (error) -> @@ -67,7 +67,7 @@ module.exports = CompileManager = environment: env }, (error, output, stats, timings) -> # compile was killed by user - if error?.terminated + if error?.terminated or error?.code is 1 OutputFileFinder.findOutputFiles request.resources, compileDir, (err, outputFiles) -> return callback(err) if err? callback(error, outputFiles) # return output files so user can check logs diff --git a/services/clsi/app/coffee/RequestParser.coffee b/services/clsi/app/coffee/RequestParser.coffee index bd081fd011..5979c75c20 100644 --- a/services/clsi/app/coffee/RequestParser.coffee +++ b/services/clsi/app/coffee/RequestParser.coffee @@ -28,6 +28,9 @@ module.exports = RequestParser = compile.options.draft, default: false, type: "boolean" + response.check = @_parseAttribute "check", + compile.options.check, + type: "string" if response.timeout > RequestParser.MAX_TIMEOUT response.timeout = RequestParser.MAX_TIMEOUT From ffea361d12a3af97cd830ac35bf7fa5a1e415ac5 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Wed, 27 Jul 2016 16:54:27 +0100 Subject: [PATCH 190/754] provide validation mode where compilation always exits after chktex --- services/clsi/app/coffee/CompileController.coffee | 4 ++-- services/clsi/app/coffee/CompileManager.coffee | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 3c9af677ae..56237bf205 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -17,8 +17,8 @@ module.exports = CompileController = CompileManager.doCompile request, (error, outputFiles = []) -> if error?.terminated status = "terminated" - else if error?.code is 1 - status = "exited" + else if error?.validate + status = "validation-#{error.validate}" else if error? logger.error err: error, project_id: request.project_id, "error running compile" if error.timedout diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 3b19cecaba..4c89a0ba3d 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -48,6 +48,8 @@ module.exports = CompileManager = env['CHKTEX_OPTIONS'] = '-nall -e9 -e10 -w15 -w16 -w27' if request.check is 'error' env['CHKTEX_EXIT_ON_ERROR'] = 1 + if request.check is 'validate' + env['CHKTEX_VALIDATE'] = 1 injectDraftModeIfRequired (error) -> return callback(error) if error? @@ -66,8 +68,12 @@ module.exports = CompileManager = image: request.imageName environment: env }, (error, output, stats, timings) -> + if request.check is "validate" + result = if error?.code then "fail" else "pass" + error = new Error("validation") + error.validate = result # compile was killed by user - if error?.terminated or error?.code is 1 + if error?.terminated or error?.validate OutputFileFinder.findOutputFiles request.resources, compileDir, (err, outputFiles) -> return callback(err) if err? callback(error, outputFiles) # return output files so user can check logs From f0c7b455a59605183c9bd08b8027851be5208111 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 29 Jul 2016 14:54:24 +0100 Subject: [PATCH 191/754] provide setting to override child_process.execFile for synctex --- services/clsi/app/coffee/CompileManager.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 87e8734549..16c36471e3 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -174,7 +174,8 @@ module.exports = CompileManager = _runSynctex: (args, callback = (error, stdout) ->) -> bin_path = Path.resolve(__dirname + "/../../bin/synctex") seconds = 1000 - child_process.execFile bin_path, args, timeout: 10 * seconds, (error, stdout, stderr) -> + synctex = Settings.clsi?.synctex?.command?(__dirname, child_process) || child_process + synctex.execFile bin_path, args, timeout: 10 * seconds, (error, stdout, stderr) -> if error? logger.err err:error, args:args, "error running synctex" return callback(error) From 08a446f03d974d5e794ffbc3e8d9058b09579220 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 4 Aug 2016 16:07:36 +0100 Subject: [PATCH 192/754] change logging message to be different from LatexRunner --- services/clsi/app/coffee/CompileManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index bf71477c0f..5d14bdf01d 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -28,7 +28,7 @@ module.exports = CompileManager = compileDir = getCompileDir(request.project_id, request.user_id) timer = new Metrics.Timer("write-to-disk") - logger.log project_id: request.project_id, user_id: request.user_id, "starting compile" + logger.log project_id: request.project_id, user_id: request.user_id, "syncing resources to disk" ResourceWriter.syncResourcesToDisk request.project_id, request.resources, compileDir, (error) -> if error? logger.err err:error, project_id: request.project_id, user_id: request.user_id, "error writing resources to disk" From c5917d4a757d1a74d508e5bd3eae62ec9a2dc44c Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 4 Aug 2016 16:08:14 +0100 Subject: [PATCH 193/754] use a command wrapper for synctex instead of an alternative child_process object --- services/clsi/app/coffee/CompileManager.coffee | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 5d14bdf01d..34a3a57b89 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -188,8 +188,9 @@ module.exports = CompileManager = _runSynctex: (args, callback = (error, stdout) ->) -> bin_path = Path.resolve(__dirname + "/../../bin/synctex") seconds = 1000 - synctex = Settings.clsi?.synctex?.command?(__dirname, child_process) || child_process - synctex.execFile bin_path, args, timeout: 10 * seconds, (error, stdout, stderr) -> + if Settings.clsi?.synctexCommandWrapper? + [bin_path, args] = Settings.clsi?.synctexCommandWrapper bin_path, args + child_process.execFile bin_path, args, timeout: 10 * seconds, (error, stdout, stderr) -> if error? logger.err err:error, args:args, "error running synctex" return callback(error) From 872dbc5215a3c2043e96967dbb6d4b623316bef2 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 11 Aug 2016 09:29:03 +0100 Subject: [PATCH 194/754] add missing argument parameter to wordcount call --- services/clsi/app/coffee/CompileManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 34a3a57b89..39cff29d40 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -231,7 +231,7 @@ module.exports = CompileManager = timeout = 10 * 1000 compileName = getCompileName(project_id, user_id) - CommandRunner.run compileName, command, directory, image, timeout, (error) -> + CommandRunner.run compileName, command, directory, image, timeout, {}, (error) -> return callback(error) if error? try stdout = fs.readFileSync(directory + "/" + file_name + ".wc", "utf-8") From 400590b3ad353b50b846b849ef54ee9b8942b469 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 11 Aug 2016 09:32:47 +0100 Subject: [PATCH 195/754] read wordcount output asynchronously --- services/clsi/app/coffee/CompileManager.coffee | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 39cff29d40..e3a218be6f 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -233,12 +233,11 @@ module.exports = CompileManager = CommandRunner.run compileName, command, directory, image, timeout, {}, (error) -> return callback(error) if error? - try - stdout = fs.readFileSync(directory + "/" + file_name + ".wc", "utf-8") - catch err - logger.err err:err, command:command, directory:directory, project_id:project_id, user_id:user_id, "error reading word count output" - return callback(err) - callback null, CompileManager._parseWordcountFromOutput(stdout) + fs.readFile directory + "/" + file_name + ".wc", "utf-8", (err, stdout) -> + if err? + logger.err err:err, command:command, directory:directory, project_id:project_id, user_id:user_id, "error reading word count output" + return callback(err) + callback null, CompileManager._parseWordcountFromOutput(stdout) _parseWordcountFromOutput: (output) -> results = { From ed5acc9f93bd28cf711fccc445881196ba1398c6 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 11 Aug 2016 09:40:08 +0100 Subject: [PATCH 196/754] capture texcount error output --- services/clsi/app/coffee/CompileManager.coffee | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index e3a218be6f..5192b7fab7 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -226,7 +226,7 @@ module.exports = CompileManager = wordcount: (project_id, user_id, file_name, image, callback = (error, pdfPositions) ->) -> logger.log project_id:project_id, user_id:user_id, file_name:file_name, image:image, "running wordcount" file_path = "$COMPILE_DIR/" + file_name - command = [ "texcount", '-inc', file_path, "-out=" + file_path + ".wc"] + command = [ "texcount", '-nocol', '-inc', file_path, "-out=" + file_path + ".wc"] directory = getCompileDir(project_id, user_id) timeout = 10 * 1000 compileName = getCompileName(project_id, user_id) @@ -237,7 +237,9 @@ module.exports = CompileManager = if err? logger.err err:err, command:command, directory:directory, project_id:project_id, user_id:user_id, "error reading word count output" return callback(err) - callback null, CompileManager._parseWordcountFromOutput(stdout) + results = CompileManager._parseWordcountFromOutput(stdout) + logger.log project_id:project_id, user_id:user_id, wordcount: results, "word count results" + callback null, results _parseWordcountFromOutput: (output) -> results = { @@ -249,6 +251,8 @@ module.exports = CompileManager = elements: 0 mathInline: 0 mathDisplay: 0 + errors: 0 + messages: "" } for line in output.split("\n") [data, info] = line.split(":") @@ -268,4 +272,8 @@ module.exports = CompileManager = results['mathInline'] = parseInt(info, 10) if data.indexOf("Number of math displayed") > -1 results['mathDisplay'] = parseInt(info, 10) + if data is "(errors" # errors reported as (errors:123) + results['errors'] = parseInt(info, 10) + if line.indexOf("!!! ") > -1 # errors logged as !!! message !!! + results['messages'] += line + "\n" return results From a26971ba2e7c36adabaf21d6a52119af58813602 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 11 Aug 2016 10:31:37 +0100 Subject: [PATCH 197/754] update tests --- .../clsi/test/unit/coffee/CompileManagerTests.coffee | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 62132f89e4..d2b6a100a8 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -202,8 +202,8 @@ describe "CompileManager", -> describe "wordcount", -> beforeEach -> - @CommandRunner.run = sinon.stub().callsArg(5) - @fs.readFileSync = sinon.stub().returns @stdout = "Encoding: ascii\nWords in text: 2" + @CommandRunner.run = sinon.stub().callsArg(6) + @fs.readFile = sinon.stub().callsArgWith(2, null, @stdout = "Encoding: ascii\nWords in text: 2") @callback = sinon.stub() @project_id = "project-id-123" @@ -217,10 +217,10 @@ describe "CompileManager", -> it "should run the texcount command", -> @directory = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}" @file_path = "$COMPILE_DIR/#{@file_name}" - @command =[ "texcount", "-inc", @file_path, "-out=" + @file_path + ".wc"] + @command =[ "texcount", "-nocol", "-inc", @file_path, "-out=" + @file_path + ".wc"] @CommandRunner.run - .calledWith("#{@project_id}-#{@user_id}", @command, @directory, @image, @timeout) + .calledWith("#{@project_id}-#{@user_id}", @command, @directory, @image, @timeout, {}) .should.equal true it "should call the callback with the parsed output", -> @@ -234,5 +234,7 @@ describe "CompileManager", -> elements: 0 mathInline: 0 mathDisplay: 0 + errors: 0 + messages: "" }) .should.equal true From 1af44b32b33bd1b4269094ecf726fc085e95e864 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Mon, 22 Aug 2016 15:11:39 +0100 Subject: [PATCH 198/754] remove chktex error too many false positives from 'unable to execute latex command' --- services/clsi/app/coffee/CompileManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 5192b7fab7..356346d308 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -45,7 +45,7 @@ module.exports = CompileManager = # set up environment variables for chktex env = {} if request.check? - env['CHKTEX_OPTIONS'] = '-nall -e9 -e10 -w15 -w16 -w27' + env['CHKTEX_OPTIONS'] = '-nall -e9 -e10 -w15 -w16' if request.check is 'error' env['CHKTEX_EXIT_ON_ERROR'] = 1 if request.check is 'validate' From 481e842f3325c16cf16fd29ce51658ae697ba9f5 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Wed, 24 Aug 2016 15:45:26 +0100 Subject: [PATCH 199/754] fix commandRunner error to match dockerRunner --- services/clsi/app/coffee/CommandRunner.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/CommandRunner.coffee b/services/clsi/app/coffee/CommandRunner.coffee index 11bbff82d3..f47af00177 100644 --- a/services/clsi/app/coffee/CommandRunner.coffee +++ b/services/clsi/app/coffee/CommandRunner.coffee @@ -27,8 +27,8 @@ module.exports = CommandRunner = err = new Error("terminated") err.terminated = true return callback(err) - else if code - err = new Error("exit") + else if code is 1 # exit status from chktex + err = new Error("exited") err.code = code return callback(err) else From e23512f0e0d90c5a5f3642c8dbb894420f2e0f21 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Wed, 24 Aug 2016 15:46:47 +0100 Subject: [PATCH 200/754] handle failed compile due to validation error --- services/clsi/app/coffee/CompileManager.coffee | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 356346d308..5d2bebb3bc 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -68,11 +68,16 @@ module.exports = CompileManager = image: request.imageName environment: env }, (error, output, stats, timings) -> + # request was for validation only if request.check is "validate" result = if error?.code then "fail" else "pass" error = new Error("validation") error.validate = result - # compile was killed by user + # request was for compile, and failed on validation + if request.check is "error" and error?.message is 'exited' + error = new Error("compilation") + error.validate = "fail" + # compile was killed by user, was a validation, or a compile which failed validation if error?.terminated or error?.validate OutputFileFinder.findOutputFiles request.resources, compileDir, (err, outputFiles) -> return callback(err) if err? From e27be3625fe67fec8138cbb5ff33c2d4dc65bf09 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly <shane@kilkelly.me> Date: Thu, 1 Sep 2016 09:53:12 +0100 Subject: [PATCH 201/754] Revert "Upgrade to node 4.2" This reverts commit 8bb12f4d996cdbd1c2f096ac82e464af231f7bfc. --- services/clsi/.nvmrc | 1 - services/clsi/package.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 services/clsi/.nvmrc diff --git a/services/clsi/.nvmrc b/services/clsi/.nvmrc deleted file mode 100644 index bf77d54968..0000000000 --- a/services/clsi/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -4.2 diff --git a/services/clsi/package.json b/services/clsi/package.json index 748256b9ed..eaa9be8cd1 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -19,7 +19,7 @@ "sequelize": "^2.1.3", "wrench": "~1.5.4", "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", - "sqlite3": "~3.1.4", + "sqlite3": "~2.2.0", "express": "^4.2.0", "body-parser": "^1.2.0", "fs-extra": "^0.16.3", From 94dfd452ea033b0453635af7ac94ca4f520d036e Mon Sep 17 00:00:00 2001 From: Shane Kilkelly <shane@kilkelly.me> Date: Thu, 1 Sep 2016 11:22:11 +0100 Subject: [PATCH 202/754] Revert "Revert "Upgrade to node 4.2"" This reverts commit 4128dc6fddfe26f576bd0f765ab80bdb0bbea874. --- services/clsi/.nvmrc | 1 + services/clsi/package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 services/clsi/.nvmrc diff --git a/services/clsi/.nvmrc b/services/clsi/.nvmrc new file mode 100644 index 0000000000..bf77d54968 --- /dev/null +++ b/services/clsi/.nvmrc @@ -0,0 +1 @@ +4.2 diff --git a/services/clsi/package.json b/services/clsi/package.json index eaa9be8cd1..748256b9ed 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -19,7 +19,7 @@ "sequelize": "^2.1.3", "wrench": "~1.5.4", "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", - "sqlite3": "~2.2.0", + "sqlite3": "~3.1.4", "express": "^4.2.0", "body-parser": "^1.2.0", "fs-extra": "^0.16.3", From aed1917b17f5688b6621d1c3f43395c0ebe221a2 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly <shane@kilkelly.me> Date: Thu, 1 Sep 2016 12:47:13 +0100 Subject: [PATCH 203/754] Revert "Revert "Revert "Upgrade to node 4.2""" This reverts commit 98fb2cab99d11df3dae3e27e8b75ff1f41b10890. --- services/clsi/.nvmrc | 1 - services/clsi/package.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 services/clsi/.nvmrc diff --git a/services/clsi/.nvmrc b/services/clsi/.nvmrc deleted file mode 100644 index bf77d54968..0000000000 --- a/services/clsi/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -4.2 diff --git a/services/clsi/package.json b/services/clsi/package.json index 748256b9ed..eaa9be8cd1 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -19,7 +19,7 @@ "sequelize": "^2.1.3", "wrench": "~1.5.4", "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", - "sqlite3": "~3.1.4", + "sqlite3": "~2.2.0", "express": "^4.2.0", "body-parser": "^1.2.0", "fs-extra": "^0.16.3", From 56429178dfbe6c40dc9ff2c8fd0a33c9eba90852 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Mon, 12 Sep 2016 16:28:52 +0100 Subject: [PATCH 204/754] only run chktex on .tex files, not .Rtex files the .tex files produced from knitr have macros which confuse chktex --- .../clsi/app/coffee/CompileManager.coffee | 4 ++- .../unit/coffee/CompileManagerTests.coffee | 35 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 5d2bebb3bc..a47f0f8e88 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -44,7 +44,9 @@ module.exports = CompileManager = # set up environment variables for chktex env = {} - if request.check? + # only run chktex on LaTeX files (not knitr .Rtex files or any others) + isLaTeXFile = request.rootResourcePath?.match(/\.tex$/i) + if request.check? and isLaTeXFile env['CHKTEX_OPTIONS'] = '-nall -e9 -e10 -w15 -w16' if request.check is 'error' env['CHKTEX_EXIT_ON_ERROR'] = 1 diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index d2b6a100a8..5b07124cf9 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -98,6 +98,41 @@ describe "CompileManager", -> .calledWith(@compileDir + "/" + @rootResourcePath) .should.equal true + describe "with a check option", -> + beforeEach -> + @request.check = "error" + @CompileManager.doCompile @request, @callback + + it "should run chktex", -> + @LatexRunner.runLatex + .calledWith("#{@project_id}-#{@user_id}", { + directory: @compileDir + mainFile: @rootResourcePath + compiler: @compiler + timeout: @timeout + image: @image + environment: {'CHKTEX_OPTIONS': '-nall -e9 -e10 -w15 -w16', 'CHKTEX_EXIT_ON_ERROR':1} + }) + .should.equal true + + describe "with a knitr file and check options", -> + beforeEach -> + @request.rootResourcePath = "main.Rtex" + @request.check = "error" + @CompileManager.doCompile @request, @callback + + it "should not run chktex", -> + @LatexRunner.runLatex + .calledWith("#{@project_id}-#{@user_id}", { + directory: @compileDir + mainFile: "main.Rtex" + compiler: @compiler + timeout: @timeout + image: @image + environment: @env + }) + .should.equal true + describe "clearProject", -> describe "succesfully", -> beforeEach -> From 4c04a5df3f086ccc689848781e7db4b6b51054b4 Mon Sep 17 00:00:00 2001 From: James Allen <james@sharelatex.com> Date: Wed, 21 Sep 2016 15:09:01 +0100 Subject: [PATCH 205/754] Sanitize resource path along with rootResourcePath --- services/clsi/app/coffee/RequestParser.coffee | 8 +++++++- .../test/unit/coffee/RequestParserTests.coffee | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/services/clsi/app/coffee/RequestParser.coffee b/services/clsi/app/coffee/RequestParser.coffee index 5979c75c20..90bc739f5c 100644 --- a/services/clsi/app/coffee/RequestParser.coffee +++ b/services/clsi/app/coffee/RequestParser.coffee @@ -42,7 +42,13 @@ module.exports = RequestParser = compile.rootResourcePath default: "main.tex" type: "string" - response.rootResourcePath = RequestParser._sanitizePath(rootResourcePath) + originalRootResourcePath = rootResourcePath + sanitizedRootResourcePath = RequestParser._sanitizePath(rootResourcePath) + response.rootResourcePath = sanitizedRootResourcePath + + for resource in response.resources + if resource.path == originalRootResourcePath + resource.path = sanitizedRootResourcePath catch error return callback error diff --git a/services/clsi/test/unit/coffee/RequestParserTests.coffee b/services/clsi/test/unit/coffee/RequestParserTests.coffee index 8545ff22a2..4cf6119831 100644 --- a/services/clsi/test/unit/coffee/RequestParserTests.coffee +++ b/services/clsi/test/unit/coffee/RequestParserTests.coffee @@ -206,11 +206,21 @@ describe "RequestParser", -> describe "with a root resource path that needs escaping", -> beforeEach -> - @validRequest.compile.rootResourcePath = "`rm -rf foo`.tex" + @badPath = "`rm -rf foo`.tex" + @goodPath = "rm -rf foo.tex" + @validRequest.compile.rootResourcePath = @badPath + @validRequest.compile.resources.push { + path: @badPath + date: "12:00 01/02/03" + content: "Hello world" + } @RequestParser.parse @validRequest, @callback @data = @callback.args[0][1] it "should return the escaped resource", -> - @data.rootResourcePath.should.equal "rm -rf foo.tex" + @data.rootResourcePath.should.equal @goodPath + + it "should also escape the resource path", -> + @data.resources[0].path.should.equal @goodPath From 638359e052bc1cb812311c9fea53a480889fea7d Mon Sep 17 00:00:00 2001 From: James Allen <james@sharelatex.com> Date: Thu, 22 Sep 2016 14:14:29 +0100 Subject: [PATCH 206/754] Don't delete knitr cache files --- services/clsi/app/coffee/ResourceWriter.coffee | 2 +- services/clsi/test/unit/coffee/ResourceWriterTests.coffee | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index e3639006ee..16000cbbff 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -46,7 +46,7 @@ module.exports = ResourceWriter = do (file) -> path = file.path should_delete = true - if path.match(/^output\./) or path.match(/\.aux$/) + if path.match(/^output\./) or path.match(/\.aux$/) or path.match(/^cache\//) # knitr cache should_delete = false if path == "output.pdf" or path == "output.dvi" or path == "output.log" should_delete = true diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee index 5480bd6b7c..c1e72a8789 100644 --- a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee +++ b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee @@ -55,6 +55,8 @@ describe "ResourceWriter", -> }, { path: "extra.aux" type: "aux" + }, { + path: "cache/_chunk1" }] @resources = "mock-resources" @OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, @output_files) @@ -80,6 +82,11 @@ describe "ResourceWriter", -> @ResourceWriter._deleteFileIfNotDirectory .calledWith(path.join(@basePath, "extra.aux")) .should.equal false + + it "should not delete the knitr cache file", -> + @ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(@basePath, "cache/_chunk1")) + .should.equal false it "should call the callback", -> @callback.called.should.equal true From 4efdddbbac56313e32d2f9a5c49bc7b0ab4981c5 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly <shane@kilkelly.me> Date: Fri, 23 Sep 2016 15:32:37 +0100 Subject: [PATCH 207/754] Add CHKTEX_ULIMIT_OPTIONS --- services/clsi/app/coffee/CompileManager.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index a47f0f8e88..fa8c9fafee 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -48,6 +48,7 @@ module.exports = CompileManager = isLaTeXFile = request.rootResourcePath?.match(/\.tex$/i) if request.check? and isLaTeXFile env['CHKTEX_OPTIONS'] = '-nall -e9 -e10 -w15 -w16' + env['CHKTEX_ULIMIT_OPTIONS'] = '-t 5 -v 16000' if request.check is 'error' env['CHKTEX_EXIT_ON_ERROR'] = 1 if request.check is 'validate' From 2bedd08ff9f241d6095721fca9672c9ad356f60e Mon Sep 17 00:00:00 2001 From: Shane Kilkelly <shane@kilkelly.me> Date: Fri, 23 Sep 2016 15:34:29 +0100 Subject: [PATCH 208/754] Add test for new ulimit options --- services/clsi/test/unit/coffee/CompileManagerTests.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 5b07124cf9..3ddf73b1ae 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -111,7 +111,7 @@ describe "CompileManager", -> compiler: @compiler timeout: @timeout image: @image - environment: {'CHKTEX_OPTIONS': '-nall -e9 -e10 -w15 -w16', 'CHKTEX_EXIT_ON_ERROR':1} + environment: {'CHKTEX_OPTIONS': '-nall -e9 -e10 -w15 -w16', 'CHKTEX_EXIT_ON_ERROR':1, 'CHKTEX_ULIMIT_OPTIONS': '-t 5 -v 16000'} }) .should.equal true From 34f47178fa0f36f64cbdf364ae6678056cfabe44 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly <shane@kilkelly.me> Date: Wed, 28 Sep 2016 11:02:58 +0100 Subject: [PATCH 209/754] Increase memory limit to 64mb --- services/clsi/app/coffee/CompileManager.coffee | 2 +- services/clsi/test/unit/coffee/CompileManagerTests.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index fa8c9fafee..0a1ea9d191 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -48,7 +48,7 @@ module.exports = CompileManager = isLaTeXFile = request.rootResourcePath?.match(/\.tex$/i) if request.check? and isLaTeXFile env['CHKTEX_OPTIONS'] = '-nall -e9 -e10 -w15 -w16' - env['CHKTEX_ULIMIT_OPTIONS'] = '-t 5 -v 16000' + env['CHKTEX_ULIMIT_OPTIONS'] = '-t 5 -v 64000' if request.check is 'error' env['CHKTEX_EXIT_ON_ERROR'] = 1 if request.check is 'validate' diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 3ddf73b1ae..e3f0705dd3 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -111,7 +111,7 @@ describe "CompileManager", -> compiler: @compiler timeout: @timeout image: @image - environment: {'CHKTEX_OPTIONS': '-nall -e9 -e10 -w15 -w16', 'CHKTEX_EXIT_ON_ERROR':1, 'CHKTEX_ULIMIT_OPTIONS': '-t 5 -v 16000'} + environment: {'CHKTEX_OPTIONS': '-nall -e9 -e10 -w15 -w16', 'CHKTEX_EXIT_ON_ERROR':1, 'CHKTEX_ULIMIT_OPTIONS': '-t 5 -v 64000'} }) .should.equal true From 8add9ff3eabea069ebdb7bf0c77a94c1bd18c1ab Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 14 Oct 2016 10:23:13 +0100 Subject: [PATCH 210/754] fix exception in error log --- services/clsi/app/coffee/OutputCacheManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee index 76692b3b71..91e085ae4d 100644 --- a/services/clsi/app/coffee/OutputCacheManager.coffee +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -85,7 +85,7 @@ module.exports = OutputCacheManager = # clean up the directory we just created fse.remove cacheDir, (err) -> if err? - logger.error err: err, dir: dir, "error removing cache dir after failure" + logger.error err: err, dir: cacheDir, "error removing cache dir after failure" else # pass back the list of new files in the cache callback(err, results) From 08dc3bd7442ea66896c93b216b440b4e58943c8c Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 24 Jan 2017 11:07:54 +0000 Subject: [PATCH 211/754] fix acceptance tests --- services/clsi/test/acceptance/coffee/TimeoutTests.coffee | 3 ++- services/clsi/test/acceptance/coffee/WordcountTests.coffee | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/services/clsi/test/acceptance/coffee/TimeoutTests.coffee b/services/clsi/test/acceptance/coffee/TimeoutTests.coffee index 66ac7a427a..5e0058d3c5 100644 --- a/services/clsi/test/acceptance/coffee/TimeoutTests.coffee +++ b/services/clsi/test/acceptance/coffee/TimeoutTests.coffee @@ -6,13 +6,14 @@ describe "Timed out compile", -> before (done) -> @request = options: - timeout: 0.01 #seconds + timeout: 1 #seconds resources: [ path: "main.tex" content: ''' \\documentclass{article} \\begin{document} Hello world + \\input{|"sleep 10"} \\end{document} ''' ] diff --git a/services/clsi/test/acceptance/coffee/WordcountTests.coffee b/services/clsi/test/acceptance/coffee/WordcountTests.coffee index 1789cfcdd0..d84ecba323 100644 --- a/services/clsi/test/acceptance/coffee/WordcountTests.coffee +++ b/services/clsi/test/acceptance/coffee/WordcountTests.coffee @@ -29,6 +29,8 @@ describe "Syncing", -> elements: 0 mathInline: 6 mathDisplay: 0 + errors: 0 + messages: "" } ) done() From 0f92ef104ae0d8de7b2bdcc428dbd06e4001ca52 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 24 Jan 2017 12:18:30 +0000 Subject: [PATCH 212/754] added docker script for acceptance tests --- .../clsi/test/acceptance/scripts/full-test.sh | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100755 services/clsi/test/acceptance/scripts/full-test.sh diff --git a/services/clsi/test/acceptance/scripts/full-test.sh b/services/clsi/test/acceptance/scripts/full-test.sh new file mode 100755 index 0000000000..f11ca3c690 --- /dev/null +++ b/services/clsi/test/acceptance/scripts/full-test.sh @@ -0,0 +1,23 @@ +#! /usr/bin/env bash + +npm rebuild + +echo ">> Starting server..." + +grunt --no-color & + +echo ">> Server started" + +sleep 5 + +echo ">> Running acceptance tests..." +grunt --no-color test:acceptance +_test_exit_code=$? + +echo ">> Killing server" + +kill %1 + +echo ">> Done" + +exit $_test_exit_code From 0a3bd5fb0797b40216431bbeede306e5931d3582 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 24 Jan 2017 16:06:32 +0000 Subject: [PATCH 213/754] upgrade to latest sqlite3 --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index eaa9be8cd1..65367df1f2 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -19,7 +19,7 @@ "sequelize": "^2.1.3", "wrench": "~1.5.4", "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", - "sqlite3": "~2.2.0", + "sqlite3": "~3.1.8", "express": "^4.2.0", "body-parser": "^1.2.0", "fs-extra": "^0.16.3", From 9461b287071895fc0b985980610cd1524f3f9c69 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Wed, 25 Jan 2017 14:08:39 +0000 Subject: [PATCH 214/754] run tests outside container, add settings file --- .../clsi/test/acceptance/scripts/full-test.sh | 2 +- .../acceptance/scripts/settings.test.coffee | 46 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 services/clsi/test/acceptance/scripts/settings.test.coffee diff --git a/services/clsi/test/acceptance/scripts/full-test.sh b/services/clsi/test/acceptance/scripts/full-test.sh index f11ca3c690..fa277e1e1d 100755 --- a/services/clsi/test/acceptance/scripts/full-test.sh +++ b/services/clsi/test/acceptance/scripts/full-test.sh @@ -1,6 +1,6 @@ #! /usr/bin/env bash -npm rebuild +export SHARELATEX_CONFIG=`pwd`/test/acceptance/scripts/settings.test.coffee echo ">> Starting server..." diff --git a/services/clsi/test/acceptance/scripts/settings.test.coffee b/services/clsi/test/acceptance/scripts/settings.test.coffee new file mode 100644 index 0000000000..1c94c40eef --- /dev/null +++ b/services/clsi/test/acceptance/scripts/settings.test.coffee @@ -0,0 +1,46 @@ +Path = require "path" + +module.exports = + # Options are passed to Sequelize. + # See http://sequelizejs.com/documentation#usage-options for details + mysql: + clsi: + database: "clsi" + username: "clsi" + password: null + dialect: "sqlite" + storage: Path.resolve("db.sqlite") + + path: + compilesDir: Path.resolve(__dirname + "/../../../compiles") + clsiCacheDir: Path.resolve(__dirname + "/../../../cache") + #synctexBaseDir: (project_id) -> Path.join(@compilesDir, project_id) + synctexBaseDir: () -> "/compile" + + clsi: + #strace: true + #archive_logs: true + commandRunner: "docker-runner-sharelatex" + docker: + image: "quay.io/sharelatex/texlive-full:2015.1" + env: + PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2015/bin/x86_64-linux/" + HOME: "/tmp" + modem: + socketPath: false + user: "tex" + latexmkCommandPrefix: ["/usr/bin/time", "-v"] # on Linux + + internal: + clsi: + port: 3013 + load_port: 3044 + host: "localhost" + + apis: + clsi: + url: "http://localhost:3013" + + smokeTest: false + project_cache_length_ms: 1000 * 60 * 60 * 24 + parallelFileDownloads:1 From b4afaf9ea867d1f136b784ef2a10410451c43bd8 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Wed, 25 Jan 2017 14:09:44 +0000 Subject: [PATCH 215/754] use local docker image for clsi test --- services/clsi/test/acceptance/scripts/settings.test.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/test/acceptance/scripts/settings.test.coffee b/services/clsi/test/acceptance/scripts/settings.test.coffee index 1c94c40eef..257019f46b 100644 --- a/services/clsi/test/acceptance/scripts/settings.test.coffee +++ b/services/clsi/test/acceptance/scripts/settings.test.coffee @@ -22,9 +22,9 @@ module.exports = #archive_logs: true commandRunner: "docker-runner-sharelatex" docker: - image: "quay.io/sharelatex/texlive-full:2015.1" + image: "texlive-full:2016.1" env: - PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2015/bin/x86_64-linux/" + PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2016/bin/x86_64-linux/" HOME: "/tmp" modem: socketPath: false From 5a6f54e6b22784a24a2ab07de16a4bff9defd4ce Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Wed, 25 Jan 2017 14:12:19 +0000 Subject: [PATCH 216/754] update image for docker tests --- services/clsi/test/acceptance/scripts/settings.test.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/test/acceptance/scripts/settings.test.coffee b/services/clsi/test/acceptance/scripts/settings.test.coffee index 257019f46b..b886279c1f 100644 --- a/services/clsi/test/acceptance/scripts/settings.test.coffee +++ b/services/clsi/test/acceptance/scripts/settings.test.coffee @@ -22,7 +22,7 @@ module.exports = #archive_logs: true commandRunner: "docker-runner-sharelatex" docker: - image: "texlive-full:2016.1" + image: "texlive-full:2016.1-opt" env: PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2016/bin/x86_64-linux/" HOME: "/tmp" From 07e536677c04e0a46c56f4b02af467af0199de8a Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 26 Jan 2017 12:06:38 +0000 Subject: [PATCH 217/754] try running user as jenkins --- services/clsi/test/acceptance/scripts/settings.test.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/test/acceptance/scripts/settings.test.coffee b/services/clsi/test/acceptance/scripts/settings.test.coffee index b886279c1f..a1f51ddb74 100644 --- a/services/clsi/test/acceptance/scripts/settings.test.coffee +++ b/services/clsi/test/acceptance/scripts/settings.test.coffee @@ -28,7 +28,7 @@ module.exports = HOME: "/tmp" modem: socketPath: false - user: "tex" + user: "111" latexmkCommandPrefix: ["/usr/bin/time", "-v"] # on Linux internal: From a802620d11b24d7a22100adf70efc7d39165b537 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 26 Jan 2017 12:20:41 +0000 Subject: [PATCH 218/754] log acceptance test server output to file --- services/clsi/test/acceptance/scripts/full-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/test/acceptance/scripts/full-test.sh b/services/clsi/test/acceptance/scripts/full-test.sh index fa277e1e1d..4b164532a3 100755 --- a/services/clsi/test/acceptance/scripts/full-test.sh +++ b/services/clsi/test/acceptance/scripts/full-test.sh @@ -4,7 +4,7 @@ export SHARELATEX_CONFIG=`pwd`/test/acceptance/scripts/settings.test.coffee echo ">> Starting server..." -grunt --no-color & +grunt --no-color >server.log 2>&1 & echo ">> Server started" From 354a88e67a61eee8452ea8c581e7b6ffc067c306 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 27 Jan 2017 12:21:57 +0000 Subject: [PATCH 219/754] specify papersize explicitly in latex test --- .../test/acceptance/fixtures/examples/latex_compiler/main.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/test/acceptance/fixtures/examples/latex_compiler/main.tex b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/main.tex index 97b6888628..76fd8e5f4c 100644 --- a/services/clsi/test/acceptance/fixtures/examples/latex_compiler/main.tex +++ b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/main.tex @@ -1,4 +1,4 @@ -\documentclass{article} +\documentclass[a4paper]{article} \usepackage{graphicx} From 00d8bc78c3d9a2041f148a1e24e5f1418ca912c6 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 27 Jan 2017 12:32:14 +0000 Subject: [PATCH 220/754] update latex_compiler test pdf --- .../examples/latex_compiler/output.pdf | Bin 23194 -> 30826 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/services/clsi/test/acceptance/fixtures/examples/latex_compiler/output.pdf b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/output.pdf index e5d3d1f242362990415d146536fda6f27ebce473..af1f6aafd6b30803823cf29346090c7034ba22a4 100644 GIT binary patch delta 29965 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\d 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 O%Y#5gC8Z>d@V@}h_+p{} literal 23194 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 Xw81FT8>v9UaTXhe92~@6Q=$I=GIJf; From b04713b23487ac1c0fb5f6a760691f7d7bd5880b Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Mon, 30 Jan 2017 15:37:26 +0000 Subject: [PATCH 221/754] try output.pdf generated with texlive 2016 --- .../examples/latex_compiler/output.pdf | Bin 30826 -> 24239 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/services/clsi/test/acceptance/fixtures/examples/latex_compiler/output.pdf b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/output.pdf index af1f6aafd6b30803823cf29346090c7034ba22a4..b87319f7d40b8c57b3e3153253f0cc6533375cf4 100644 GIT binary patch delta 23242 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 Wr?0z>Ek3`1n1CQY7_6YFi2q+5hO`U- delta 29837 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 H)Ae<2HmABHFk$~4bA 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 From 2c26c995095cde6733a5580eb51c2f864fd7c438 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 31 Jan 2017 09:53:36 +0000 Subject: [PATCH 222/754] update asymptote pdf to a4 size for texlive 2016 --- .../fixtures/examples/asymptote/output.pdf | Bin 121369 -> 122452 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/services/clsi/test/acceptance/fixtures/examples/asymptote/output.pdf b/services/clsi/test/acceptance/fixtures/examples/asymptote/output.pdf index 0a7fb42c99125edefec74cb4b2c5217690998d39..942b9154967cd1f79d9ad467cc431d0244d29b45 100644 GIT binary patch delta 115713 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%0D)pmou}eCCEI$3V7co zxhqG{XmfeMzEjk0e;uODi$c8cia8{9im9tXioJ(^&3!(JnBT2zz_b<!pNt8S&w4#d z<hvOs&CsXMOf=qjGg#kLEWRiZ_N`dOY0N00m%f0l=KGIAxKb1YXP1$)*yRYfaAs3V zXTE>9X-+>TuiS}^cujOx<CzU&wbV=U9@r&g<D|btk+2q4Ev^N<`=mKvj>Tjr?n&Sl z4l{wKExCTU?Cxot6151=*<$>CK8SQAa~2h2?1G#mZuQa6zO^|^s;k*3u|XVV5#*0L z1=p0dZ@8D5MdamEy4z~DB%_(HtPHXy>u-N`JK2AK4_z{4IWB2?z_+Wx3|L|mHfF(7 zWXo%~PbOnVa&smIKhuT!6kJfLwBO~zsqCVTWiM`E+sZT7H4afNeJhKo<E;f>@J}fu zyVzVqr8)LGyz)P?+vs>42#FZ(FUcKJMpm2hiFOm`P5<x;BKG%3yj-d||1suhcO-wa zRDL3QdSV8IP7xZ&lirWcPb(9Pk1P=lPx{v2m$4ov+ls4?Fs}WS7HlcR6nEvO=ry+) z&HRIncHZ%zAiKw)18zW~X423sn$bq57898ELr^aIz;®u_v@$ew0ko+ET8F!!}9 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 F{y$AYS^EG0 literal 121369 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 w3nx#|kz{Z7rQ+kO6Fc`WooZc& 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 JthyZbe*lzx1>67t From a1503a8791c7a7e975996e1f11dbeea7565dc58a Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 31 Jan 2017 10:21:00 +0000 Subject: [PATCH 223/754] update feymp test image minor pixel change in position of labels in texlive 2016 --- .../fixtures/examples/feynmp/output.pdf | Bin 6290 -> 3668 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/services/clsi/test/acceptance/fixtures/examples/feynmp/output.pdf b/services/clsi/test/acceptance/fixtures/examples/feynmp/output.pdf index 5a93452b0e3cb6664f792fc971a3011709c9d187..9428ca938518bd1cad467bb61106db1084b6ff9f 100644 GIT binary patch literal 3668 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 literal 6290 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 H;mm&nd$@)w From c83102c6650b1e9dfdd3586a0ec76db07c950e83 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 31 Jan 2017 10:40:05 +0000 Subject: [PATCH 224/754] add debugging to acceptance tests --- .../clsi/test/acceptance/coffee/ExampleDocumentTests.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee index f7f82ac12c..23c3407bf3 100644 --- a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee +++ b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee @@ -23,7 +23,7 @@ compare = (originalPath, generatedPath, callback = (error, same) ->) -> if stderr.trim() == "0 (0)" callback null, true else - console.log stderr + console.log "compare result", stderr callback null, false compareMultiplePages = (project_id, callback = (error) ->) -> @@ -72,11 +72,13 @@ describe "Example Documents", -> it "should generate the correct pdf", (done) -> Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => + console.log "DEBUG: error", error, "body", body pdf = Client.getOutputFile body, "pdf" downloadAndComparePdf(@project_id, example_dir, pdf.url, done) it "should generate the correct pdf on the second run as well", (done) -> Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => + console.log "DEBUG: error", error, "body", body pdf = Client.getOutputFile body, "pdf" downloadAndComparePdf(@project_id, example_dir, pdf.url, done) From c031ec7a2e54d7cc58583a0c7d2db6e56d4e0324 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 31 Jan 2017 10:47:49 +0000 Subject: [PATCH 225/754] increase debugging in acceptance tests --- .../clsi/test/acceptance/coffee/ExampleDocumentTests.coffee | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee index 23c3407bf3..6c9e96bf8f 100644 --- a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee +++ b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee @@ -72,13 +72,15 @@ describe "Example Documents", -> it "should generate the correct pdf", (done) -> Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => - console.log "DEBUG: error", error, "body", body + if body?.compile?.status is "failure" + console.log "DEBUG: error", error, "body", JSON.stringify(body) pdf = Client.getOutputFile body, "pdf" downloadAndComparePdf(@project_id, example_dir, pdf.url, done) it "should generate the correct pdf on the second run as well", (done) -> Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => - console.log "DEBUG: error", error, "body", body + if body?.compile?.status is "failure" + console.log "DEBUG: error", error, "body", JSON.stringify(body) pdf = Client.getOutputFile body, "pdf" downloadAndComparePdf(@project_id, example_dir, pdf.url, done) From 3434f365bbe1b6114dfa507a762a9b88a27ffd24 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 31 Jan 2017 16:04:59 +0000 Subject: [PATCH 226/754] update acceptance test image for lualatex small pixel-level change in output --- .../examples/lualatex_compiler/output.pdf | Bin 11405 -> 3183 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/output.pdf b/services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/output.pdf index 38ce5418e0b8df9a51b75fd412138a058e8d7be3..3dae1ad270e019a55f1fa151ee8b024fbca9c69e 100644 GIT binary patch literal 3183 zcma)8c{mhY7tfM1X_B>t-Vw^!W-&92-89C&dm~cD%$Tt>!x-VUG+7fNYa|j<WXojD zk~Jh`$)2r{k`SrzP2b!5^*mqS_q^|Y?jPr#d!KuL_ndRj`AM5&aSAX+BtW{SzhwxZ z1cHKSPOboTb%-&EN@h?%Fc=*4=YZf`+!-V~1m})tkgy~IjY!he1b8v%B>V+{?}$dU z8n6k<cgiOdBYMx!AX!U$GD{my928-^AN*SEeKZF&(Wtk4aG@^JYVI71cvX}akJG2m z$QlYK>E4<)Y2zGHwE$HJrg$oqx#R;cbXITxObbSRTQiExLP~F3w<Ukt$y=m7!hP9= zRT7T{kf_8TrrFYW$SS`;hr@m#2mK9ng5xeu-0l<M;oRXoJiB-8Y3IoK^?vlv`>nj4 z7=9ik$SDIXjz(ozvt{V1|2PbeQ2s{_3$(U37qmGvAuS~*(1(e(dRXc2dcS3#L62&! zx=OI*?kJY*Gm2UZwoJ+eRdd4xE7l^DbV3YuR;c7$0<Wz@D7TKTeT1EV=?s+cn|t8= z(ES;LH)}AXg`_&((|hr5%Ib2a(yPtqJKY;w<RG!mXU!gB-J`Y^Wm(m9i`Qx1kGr1; zk6s;w6@eC)^B|qG0e*`8R`9TPgklKarr$Mnh*fIw%jkKfJi{cf&5@@BT{Y3B<)-eO z84B?D+G>A7Ip%yEkLlw1W@n<KN^*q7P=Ncv^+VmQ<e4`+vi8%n%1Xkf;ro0!uNlds zMmyy;zu@ZIUIUrZsL>ZK3!jh3JYo*SD4eU)S^k3=;uSm~Qqszq|CvYl6BCocb@OmV zNFi5%_&sM7^LE4M-IFV;PyWDP-fdl@!AY6iI(K_1D4Zn5Im4~<y#2FILl;xob<idR zp0ST<*ewz+c*xwEo7n(u5Z~KhPkqD_thn!k&zVK*7)R2M6z||#&7Fr8`T0?@a;J;( z?dm%OhK}v>e*BwCDzm8xkbFrd2w?p&C^Mv`pVX4<*jY+<S11RT5rVF{W>!BedBD1! zdZ_u>B!7LpMch<ZW!CUD)1$N9n2&J`T})=1#jWOyF7d}<F(!u&qhWf-Pa}N-4Lc9; z&fs%FIiyJ1#-(LoVF@<AY~y8I$1}fuLJ3Zf?XNIiw({)xG7o61<V=Mh#Y?f+Pw{(c zbCi|vsAwC1c|!Hbwo`w)K)og!XHYuTm(re9rsgJHP2LlYNZY?Mnx#1#mGwep`=bKO z2O|m59lpEdTuP!5&wGW+dWbe@%(g1zS#peQ@TTjh^)eNYg_ZgvwdBwpd~TPIbk!?j zx#7_gU9f7-*Qtpbs;t|qZFM>;Dbz5=i}3bZbIdD!ig}~Owm2e4Pst@ylDBz9-z+{^ z5Nr9a%0ODmVaS2=aQqpLY-*Ybzj8}l)akTD&;GUg#QWS+S3{GnI18eH<IJYnWgn<I zRtwAoW_iBuskh|Q4}Kw)vHqLOiD)qEym!4icN=BXr_pzdwqzzQ;d-hh-HA6R(se&w zrT^sUT@y8LBT4_>uO|>+bM-pprO8>r1sOMvDPqo02DfUS>ep7knhi!iF|oejAy{#p zMZ4;7FK=sZY9MEm?{T_{nS+Wa428+i5@l`lZC$KPwm2#n9-kgxgq*y$9gYhk#mq`y zZPEbSZO7|t!?bIwoWX1H@}A%1hO|u0?tf{li2~E?UR_YZ#EfMOo}4RiVOh##e5FkH zn&ChMdW;KG!v*4<H0om^BUu=~y%^h)k>9WTNKzE<8=72xP`VFgx0f(gTjo>uK>%N% zA7+2_A`Q7(dh26$U@cc7{$Y{-&1j<tsc-YdNN0NOS;zP*`f+ai<CE{DX$>Et%|83; zn3Eg%5WEM_%Uo}oI9=KlFwLiKYS6q7Ul{8q7>F>+0qUhyFT@0D)D^w&OiZ2=$(|WW zJ||PIH6`oot2&oEQBskDtLmVQIEeSY37OR?UCjo5(KOUFw;VD5iV)K188<Dc{X8`< zy)bERpCO@fGBe3wE5LgM-}xfpL+zO3*>ir}o*K72#UE9tJ$hSqThl(xPeX0uM0uo> zeTodO`0b~_>|WDSr6boaGRhapTO2KVX82$0Ck*zpfd1$$2<SiTEn$`pZjDf(!0zHo zfBv;BRH3PEPRT-OW4hD0M=Y|cnrUJ<qWSql$^kXJ@#*=t1@y+7*)hg3)S2bx0ge!z z%tjHp0ZRCjfTWb1zB4j|LNbfW*qFzp0<b&F>F{9Bt-L7ftJUP@`^33$LctgNwdj)) zG=Ul*!>96qX5Yrv64KY<!td8RukhHbBj?WU=}&%-4MeS6N_3=D>V>$iU9J`|G?jR3 zW!M!f+CG<Ga`S<{FA{)v+mexbX;a}*GGQRZCHkQ5_y#e|pi_jLlHYnfNl({VX>V7@ zo%~NNn@iU{3em0-W<9m!7grwMmG7qP=a4qw6pOuE(Y~Mn;m6$6?$|H)wsTyDs#{|e z$*(5n*J<qepd&r&`D@<xXU*DzHx#;(_!Ga0ngtF_&1W5CWhNx!&sUWPPUkM1TA<vp zMO@MNV{N`AYpPjdl|D9>)%G+ySFeuBk4D@9UL!=bW*m(bq%{}1Ym9PyBQ_r?G|{FS zDstBZN_-^8<;q@2su|~{9LUjKl-}ft&|OXaAJIkpjIKGIM)W3-=pY$mZ@e|hRspJr zMk+$VkW*}`z@V}9Y~_!Y3yq3p(+Q9aRuv9~DMMjOFc=C7Lm=(I5EB~l|L$4S@g5!| zA_Rwb_aZ^et#xe`ao+Ck7(A8w-LN&q#S6rqe*%yPohH$}*rq^#BKbD}kg>}}5{PXS z1qQQu%nufU`uU9SZ5?9f<Z8vZ05JurusH<XivdEY{MG0Y%KtcfJE@~k{>K|~>o;zD zB<IK&adL76^<U}DLAvLe=qxwrKakZH*?*m9y1v^ovTv<t9mIR8Yv~=I%`PsWAMo&U zDA!4>HU>je1YJ82?WJl8ni-B*bQ`+`6nS~KT*b$s#9=wkJY_%iflpf<D?=3*KrU!~ zHkRPT-FLiJVE7Qdy?6y<6_R0i9}PR1Fyvz}J{^&lqm@2g?elt&Cv34rz-Jvyxg(o; zA0fV&2nOdK+n1DUOvXgYzeL-BC6TQR*l7a^#GEAwL)k+43)$J8U+<Er3}T*tHZdJL zu+_A*KJ$LO^}R{kFom#4aJ$l*HWDIsql`k466BGzFqe<FqMs!m$*YTwIo?o(9viy5 zEi6;Roo)L~Sk6P(cr>xEh^hb`<Eh!<(G5OO9FV%KLmp<KE<5d|*V&l^;&z_D?b<_) zjg<)YZCtjpsm@<;J^Q=VP-x#g54m;p@)w&+kk#MAtt01z9aO<CV^v>^vci*%w~TJS ziTX?m-Ray)y);%uSy3yT9DdhsC10?VCyCB?4VX9cfEA8q*ESailKfZpijosj5baYv zBY}LWCMQ0;iS~bc8TIK<F^`sVk_}{O;`*ELrRIU+*;PYLE<(fmxc~77kpGoDww5Gk zhyj&I^0kM8U?>NOmCGL_wg9oQhk@WAm;=O;Mq_|r-&waM`=B!o1pEGv*fQ2$9g09A zbx<&6EEa}Az|l%bB_%8zjzsIAF&Kmr4yy_J`y&p2B`fj=Ck6c-7Zz@PBCM^=-_S#Z zz4N*aOj5Z=G#U_6ir5jDbPMH0KYb2<M-EI7t$llvV4cFM9<S8;cCn5t<yUWbG4OPT TFP-EJfUBV40BLDmGaTSwQUHgt delta 11123 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 LC@3V9B;o!qKy58} From 491528f5a53432888ce43d944bd1464cf29b0c11 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 3 Feb 2017 15:16:47 +0000 Subject: [PATCH 227/754] add acceptance test for hebrew --- .../fixtures/examples/hebrew/main.tex | 14 ++++++++++++++ .../fixtures/examples/hebrew/output.pdf | Bin 0 -> 24169 bytes 2 files changed, 14 insertions(+) create mode 100644 services/clsi/test/acceptance/fixtures/examples/hebrew/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/hebrew/output.pdf diff --git a/services/clsi/test/acceptance/fixtures/examples/hebrew/main.tex b/services/clsi/test/acceptance/fixtures/examples/hebrew/main.tex new file mode 100644 index 0000000000..0eb48d9116 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/hebrew/main.tex @@ -0,0 +1,14 @@ +\documentclass{article} +\usepackage[utf8x]{inputenc} +\usepackage[hebrew,english]{babel} + +\begin{document} +\selectlanguage{hebrew} + + כדי לכתוב משהו ב×נגלית ×—×™×™×‘×™× ×œ×”×©×ª×ž×© במקרו ×”×‘× ×•×›×ן + + ×ž×ž×©×™×›×™× ×œ×›×ª×•×‘ בעברית. טקסט נוסח×ות תמיד ×™×”×™×” בכיוון שמ×ל-לימין + +\selectlanguage{english} +This is a test. +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/hebrew/output.pdf b/services/clsi/test/acceptance/fixtures/examples/hebrew/output.pdf new file mode 100644 index 0000000000000000000000000000000000000000..09767bd88d4b00752d79ca5a5fd7acd828004416 GIT binary patch literal 24169 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 V)%k5x*x0!_LC928;))W;{|DH$sn7rb literal 0 HcmV?d00001 From 3acf15de5b5d1c9fd4a6d9a786e921977131d18b Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 3 Feb 2017 15:38:06 +0000 Subject: [PATCH 228/754] add knitr utf8 acceptance test --- .../fixtures/examples/knitr_utf8/main.Rtex | 35 ++++++++++++++++++ .../fixtures/examples/knitr_utf8/output.pdf | Bin 0 -> 62993 bytes 2 files changed, 35 insertions(+) create mode 100644 services/clsi/test/acceptance/fixtures/examples/knitr_utf8/main.Rtex create mode 100644 services/clsi/test/acceptance/fixtures/examples/knitr_utf8/output.pdf diff --git a/services/clsi/test/acceptance/fixtures/examples/knitr_utf8/main.Rtex b/services/clsi/test/acceptance/fixtures/examples/knitr_utf8/main.Rtex new file mode 100644 index 0000000000..29d575e949 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/knitr_utf8/main.Rtex @@ -0,0 +1,35 @@ +\documentclass{article} +\usepackage[utf8]{inputenc} +\usepackage[spanish]{babel} + +\begin{document} + +\tableofcontents + +\vspace{2cm} %Add a 2cm space + +\begin{abstract} +Este es un breve resumen del contenido del +documento escrito en español. +\end{abstract} + +\section{Sección Introductoria} +Esta es la primera sección, podemos agregar +algunos elementos adicionales y todo será +escrito correctamente. Más aún, si una palabra +es demaciado larga y tiene que ser truncada, +babel tratará de truncarla correctamente +dependiendo del idioma. + +\section{Sección con teoremas} +Esta sección es para ver que pasa con los comandos +que definen texto + +%% chunk options: cache this chunk +%% begin.rcode my-cache, cache=TRUE +% set.seed(123) +% x = runif(10) +% sd(x) # standard deviation +%% end.rcode + +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/knitr_utf8/output.pdf b/services/clsi/test/acceptance/fixtures/examples/knitr_utf8/output.pdf new file mode 100644 index 0000000000000000000000000000000000000000..49261377fac66bb2c12242dbb450f0cfcf8380cf GIT binary patch literal 62993 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 RorRs14UU{#OhFv({{hobEV=*y literal 0 HcmV?d00001 From 8b22b6c6508161e325dfb268f907694e1c20eb32 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Mon, 6 Feb 2017 16:27:47 +0000 Subject: [PATCH 229/754] update knitr_utf acceptance test output needs to include table of contents from multiple latexmk runs --- .../fixtures/examples/knitr_utf8/output.pdf | Bin 62993 -> 75114 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/services/clsi/test/acceptance/fixtures/examples/knitr_utf8/output.pdf b/services/clsi/test/acceptance/fixtures/examples/knitr_utf8/output.pdf index 49261377fac66bb2c12242dbb450f0cfcf8380cf..015941cc2019cf51de6b6405eb5cea68829d06ef 100644 GIT binary patch literal 75114 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 literal 62993 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 RorRs14UU{#OhFv({{hobEV=*y From 91fa3364f70c6cacab37b9bde373bdc53734d6e7 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 7 Feb 2017 08:12:39 +0000 Subject: [PATCH 230/754] added acceptance test for tikz-feynman --- .../fixtures/examples/tikz_feynman/main.tex | 65 ++++++++++++++++++ .../examples/tikz_feynman/options.json | 3 + .../fixtures/examples/tikz_feynman/output.pdf | Bin 0 -> 34780 bytes 3 files changed, 68 insertions(+) create mode 100644 services/clsi/test/acceptance/fixtures/examples/tikz_feynman/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/tikz_feynman/options.json create mode 100644 services/clsi/test/acceptance/fixtures/examples/tikz_feynman/output.pdf diff --git a/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/main.tex b/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/main.tex new file mode 100644 index 0000000000..c752068542 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/main.tex @@ -0,0 +1,65 @@ +\documentclass[tikz]{standalone} + +\usepackage[compat=1.1.0]{tikz-feynman} + +\begin{document} +\feynmandiagram [horizontal=a to b] { + i1 -- [fermion] a -- [fermion] i2, + a -- [photon] b, + f1 -- [fermion] b -- [fermion] f2, +}; + +\feynmandiagram [horizontal=a to b] { + i1 [particle=\(e^{-}\)] -- [fermion] a -- [fermion] i2 [particle=\(e^{+}\)], + a -- [photon, edge label=\(\gamma\), momentum'=\(k\)] b, + f1 [particle=\(\mu^{+}\)] -- [fermion] b -- [fermion] f2 [particle=\(\mu^{-}\)], +}; + +\feynmandiagram [large, vertical=e to f] { + a -- [fermion] b -- [photon, momentum=\(k\)] c -- [fermion] d, + b -- [fermion, momentum'=\(p_{1}\)] e -- [fermion, momentum'=\(p_{2}\)] c, + e -- [gluon] f, + h -- [fermion] f -- [fermion] i, +}; + +\begin{tikzpicture} + \begin{feynman} + \vertex (a1) {\(\overline b\)}; + \vertex[right=1cm of a1] (a2); + \vertex[right=1cm of a2] (a3); + \vertex[right=1cm of a3] (a4) {\(b\)}; + \vertex[right=1cm of a4] (a5); + \vertex[right=2cm of a5] (a6) {\(u\)}; + + \vertex[below=2em of a1] (b1) {\(d\)}; + \vertex[right=1cm of b1] (b2); + \vertex[right=1cm of b2] (b3); + \vertex[right=1cm of b3] (b4) {\(\overline d\)}; + \vertex[below=2em of a6] (b5) {\(\overline d\)}; + + \vertex[above=of a6] (c1) {\(\overline u\)}; + \vertex[above=2em of c1] (c3) {\(d\)}; + \vertex at ($(c1)!0.5!(c3) - (1cm, 0)$) (c2); + + \diagram* { + {[edges=fermion] + (b1) -- (b2) -- (a2) -- (a1), + (b5) -- (b4) -- (b3) -- (a3) -- (a4) -- (a5) -- (a6), + }, + (a2) -- [boson, edge label=\(W\)] (a3), + (b2) -- [boson, edge label'=\(W\)] (b3), + + (c1) -- [fermion, out=180, in=-45] (c2) -- [fermion, out=45, in=180] (c3), + (a5) -- [boson, bend left, edge label=\(W^{-}\)] (c2), + }; + + \draw [decoration={brace}, decorate] (b1.south west) -- (a1.north west) + node [pos=0.5, left] {\(B^{0}\)}; + \draw [decoration={brace}, decorate] (c3.north east) -- (c1.south east) + node [pos=0.5, right] {\(\pi^{-}\)}; + \draw [decoration={brace}, decorate] (a6.north east) -- (b5.south east) + node [pos=0.5, right] {\(\pi^{+}\)}; + \end{feynman} +\end{tikzpicture} + +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/options.json b/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/options.json new file mode 100644 index 0000000000..96a05433b3 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/options.json @@ -0,0 +1,3 @@ +{ + "compiler": "lualatex" +} diff --git a/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/output.pdf b/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/output.pdf new file mode 100644 index 0000000000000000000000000000000000000000..367b0d0cae0da7e68989354c333dfc5323f1149d GIT binary patch literal 34780 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 literal 0 HcmV?d00001 From 83a06b1fe1964982c150ce6501f3463e1d63b0c6 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 7 Feb 2017 08:49:19 +0000 Subject: [PATCH 231/754] add luatex85 package to tikz feynman test --- .../clsi/test/acceptance/fixtures/examples/tikz_feynman/main.tex | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/main.tex b/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/main.tex index c752068542..8e8860cd98 100644 --- a/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/main.tex +++ b/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/main.tex @@ -1,3 +1,4 @@ +\usepackage{luatex85} \documentclass[tikz]{standalone} \usepackage[compat=1.1.0]{tikz-feynman} From 58bb705555c1f0dfeb0f7311da2d9a45a769502b Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 7 Feb 2017 08:59:45 +0000 Subject: [PATCH 232/754] fix lualatex require --- .../test/acceptance/fixtures/examples/tikz_feynman/main.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/main.tex b/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/main.tex index 8e8860cd98..6071bc26f0 100644 --- a/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/main.tex +++ b/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/main.tex @@ -1,4 +1,4 @@ -\usepackage{luatex85} +\RequirePackage{luatex85} \documentclass[tikz]{standalone} \usepackage[compat=1.1.0]{tikz-feynman} From 42d20cede03337a6fc9682fb339c39f4e04b1dc7 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 7 Feb 2017 10:00:41 +0000 Subject: [PATCH 233/754] added fontawesome acceptance test --- .../fixtures/examples/fontawesome/main.tex | 12 ++++++++++++ .../fixtures/examples/fontawesome/output.pdf | Bin 0 -> 26833 bytes 2 files changed, 12 insertions(+) create mode 100644 services/clsi/test/acceptance/fixtures/examples/fontawesome/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/fontawesome/output.pdf diff --git a/services/clsi/test/acceptance/fixtures/examples/fontawesome/main.tex b/services/clsi/test/acceptance/fixtures/examples/fontawesome/main.tex new file mode 100644 index 0000000000..42bfa8e55c --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/fontawesome/main.tex @@ -0,0 +1,12 @@ +\documentclass{article} +\usepackage{fontawesome} + +\begin{document} +Cloud \faCloud + +Cog \faCog + +Database \faDatabase + +Leaf \faLeaf +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/fontawesome/output.pdf b/services/clsi/test/acceptance/fixtures/examples/fontawesome/output.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4032f42b566542cedc5733c1e446ceed628b0d16 GIT binary patch literal 26833 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≫Y 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 literal 0 HcmV?d00001 From 11e4d01e1e12c5d6a130d59ca7f7bdd5729b035b Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 7 Feb 2017 11:51:21 +0000 Subject: [PATCH 234/754] update acceptance test output for fontawesome --- .../fixtures/examples/fontawesome/output.pdf | Bin 26833 -> 30768 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/services/clsi/test/acceptance/fixtures/examples/fontawesome/output.pdf b/services/clsi/test/acceptance/fixtures/examples/fontawesome/output.pdf index 4032f42b566542cedc5733c1e446ceed628b0d16..327d317b29bc22807b26c78d64c1bb933f4a55b4 100644 GIT binary patch delta 30347 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 delta 26343 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 From 83dd366eb472fd8855c4c78561786584b53c7b9d Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 9 Feb 2017 14:16:53 +0000 Subject: [PATCH 235/754] show debug info for acceptance tests --- services/clsi/test/acceptance/scripts/full-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/test/acceptance/scripts/full-test.sh b/services/clsi/test/acceptance/scripts/full-test.sh index 4b164532a3..cdbfa972ca 100755 --- a/services/clsi/test/acceptance/scripts/full-test.sh +++ b/services/clsi/test/acceptance/scripts/full-test.sh @@ -1,4 +1,4 @@ -#! /usr/bin/env bash +#! /usr/bin/env bash -x export SHARELATEX_CONFIG=`pwd`/test/acceptance/scripts/settings.test.coffee From 29d36399a25b4600f6fe3145b38ab53761cf0311 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 9 Feb 2017 15:38:25 +0000 Subject: [PATCH 236/754] fix #! in test script --- services/clsi/test/acceptance/scripts/full-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/test/acceptance/scripts/full-test.sh b/services/clsi/test/acceptance/scripts/full-test.sh index cdbfa972ca..0295eb5a40 100755 --- a/services/clsi/test/acceptance/scripts/full-test.sh +++ b/services/clsi/test/acceptance/scripts/full-test.sh @@ -1,4 +1,4 @@ -#! /usr/bin/env bash -x +#!/bin/bash -x export SHARELATEX_CONFIG=`pwd`/test/acceptance/scripts/settings.test.coffee From 1a7d8477a31d4334baf919f049d493c99ad3a4a4 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Mon, 13 Feb 2017 13:42:44 +0000 Subject: [PATCH 237/754] update acceptance tests for reversion to dvipdf --- .../fixtures/examples/feynmp/output.pdf | Bin 3668 -> 6268 bytes .../examples/latex_compiler/output.pdf | Bin 24239 -> 26325 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/services/clsi/test/acceptance/fixtures/examples/feynmp/output.pdf b/services/clsi/test/acceptance/fixtures/examples/feynmp/output.pdf index 9428ca938518bd1cad467bb61106db1084b6ff9f..66268b2b7eac844487745c89f4e90e121a591753 100644 GIT binary patch literal 6268 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 literal 3668 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 diff --git a/services/clsi/test/acceptance/fixtures/examples/latex_compiler/output.pdf b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/output.pdf index b87319f7d40b8c57b3e3153253f0cc6533375cf4..78e2440fa99c3d313ef91071d8d560984704ba5a 100644 GIT binary patch literal 26325 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%0A1){xm;=h{pf{6YSX+ilw{kV z_ZT2N`=ou4qPzePa}O1SinC7`=jvm$-qZ`lY8?{Csox==<nJc?r}<^MJjj2JFUh=` zziR$$Sr&Y&Wf>dse~>#0zK4nB7W?{*gUxaJH;U~}C5GUTm&HD^6Uy9fnSE0?bkjvj z>wfgARfdAgx7b@&4>#*=Bmeq}T-mg${ti)}1{V%(7_yO(pXlVA4@r^olG%6u^7wgo z+$dj)@wiU2GTaaaS!YRXOVnE)gil8+sZnPn9JS%GWVdleH|dTEgw={RW79kQ9C&;2 zq{DeasGR8x8&!RN*$4a)JaAunk8VQ9Wy>CB3yN0=roA08446pf^&~^>?l8*ImA*$8 zlr{WXzukzknf-~s>_3i(%cM8V7uxIC1>~-_kK#0p%h*fCZ2haXR0q`a+H)giuM8sk zu{+0kg<KnjmN=Ces*mNFV<_h9*U9cED;7?ggI1LI3()7?TShc}cIosS?xp}NHyxmx zAW^Ow>@L^M6)AhX{$(zC%*$Zq^&;}3+Vp8UTSQiRyi%)OL`|%*(dFNidlwfYL#zUr zS^qfY;u4#k_S!HNZDI~gX8fqcflQTivWbmtY%6HW<Hm8REwp5xnWeoSk!CgaDvo47 zVM=YYYa4!FNcTiUpmd~8M!YgJADZG0&$6;vN~NP!-_y!L+^>0)p9vK1uI4_0R=ap- zYRKJTF{h9Jp8f_iau%7pYy=Wh|Mxg9Mc1zPb7e#tsV6>=70YQ`9W7SrJ8!h{+g^S* z{oG6}H6${1GQ1Q8Jo=t;L0L^ZoDqYc0krrecdu>Kn|Koy$?#i8OsB!62U9^|JBKB* zt$s$1Wo?sj>w^i|+NjGnGtP?7x&?ubb*}!BNO9qRz+@S#_Xupag2FL?P~y8~LS~Ep z^TN!V_ghNMKBvYOrV4YQrMRx^&p!p1>~%|cBo@q8Ug7&L4?LzdpP`Q>hx58J%AnA; ze)B?Tb&u**vGv{T7`p6m0?V6>smZMERc2_d2wJ^aXw1gnE`+R(d&enIU_4^H2wJ%8 zyF&dSQqL|`w53cqR{Ahn0WIH@F3x5uc{7todaBd;SN!&}ul`TAxijhXBFrH)o!CR1 zvHurH@CzCC$DEm<ryTv`T=Ui5XV@RFBU|)u4W?OPK!m4bgirM>6KHQzb7mwj3cQ~! zpN4tl?!Dp#eZFl&oIido;K>Y>$OB@64f{Y}nADL2O)}ldxc);lHrj^l<Stj|@j%;q z#D52iH`w?c&~Myl#w>Cp{xX8yt$nb|78<I5`4A}Ga<KA>S+qN9`%#tx$<~JCpXAVh zeM9uWyu@0{DAPqGrBk-7njsEm1asrMXxWh(P>PBePg7Elv<$3x8$t*x&1(Yf>(h6~ zK1;eJMLbw@+Ok3z3%B%FCOKZe<f^kmXbb%X?9l#+(|sXEO*kC*wkt|FS_(qNtGh=< z$XqU5%X7)9LE?BzL%rx*7H!PLgKdy%_B5|T)>z!^bmo~+6Jqe}Cud(m0Ng8>IM=Az z<5Iyk=`X|eFF}!U?ioc-WCWB~T(AePV4DeLp3Dw~Y6r+K&~8uZRzTBR7&|B}!G4AQ z49gs^Nboz_r_XrzoAoc~@Dzm8clL}HX`w{zOs^#&pPtmz7$)mqD5z}NV{ae``7L%E zMWGY<z$2=Zi<;+TEZsLxoXN7LBRMZaKSAr42y=xG+Fft?Rg+Pt7q{PcXoj|Lex$8M z2NTkmnD)PE{8UMnhj$=hvt<|(qlHwfkhfd4Or2h;*!4KeTrzn6R;|IsuOtw1h(}s2 z-@O4^&n??3dn<eCuRa%_b4~IBzgi!^Dh`DuyalfjiB34e`0xyA0QJNvoeo0spa4JB zJ_KX5zi$ZH9$!_E@RvKOI)}hxu{w;sOtv8o24PmVVGh3J-@T9r$H!g>Ku-`WI^<>m zE4t_-l?Z!E0C;x@LKpxr1aaS^B}x#ZzXC#w!fDV#hk8afh*PR<0}V-5>4{T{Y*`1c zuaIj^vPTgNw%df?5$a6g8T8SKz@;eu*oOyMv@`T_bYp`;TDD|rC)g0tQB3U-(e3W( zAcSTL8`y-ilmWFS+(VKOW@tfFEf<F@ks*n@nh<tMgy1oP;YxvBaP55X&#@pwCjW1& z{W}dY_Na0r6}JJk^17Z>AYeEF2Z+#mlO#p6_7G*De>w_Q>r#AxU}Si`D-MdfVTP-_ z9YkC9hwyVI5E<3A>=K0+abo&W6DoYs3paJdrJ_nn;B6tphCNzs)1exX$ny(Q5h_r= z=^Qd%-w5!C;OgW*02x!#`Orx5Ve`=n${=C1m&afTGCeliK!IhF0YsA~1qTB*9w;$b z3o_GT8!)OP-IHSneYf=_AymLY8Lodk_x_ZkV8_HrNT$%K2I{yWMXZB}BYUBRhxiCl z5yEn}Z4m2tZQ(^ju%o)HN5}?^2GM>aii3o%kO;Bm6d#b$!s*QQeo&=UHp34ZeRuS& zhpRJ5P=c$=s`Za(&VvA?1yhZfGCrb57I7)|ArGmBK9Hz@BoURBK^iRrj44{(s-O~U zgA_i-6g(utBRmz5YorU#kgDV-#G%=66TQeHobV2Oxzn%7I*~<|Q>=_BHy-bDK#VD@ zDU6JSJ{5YAbj5W+M#ot#Sm8rX2!mRvb<v`ph;_t#;vv|ut%^V5pb|f0k+;hFan3rz z!O4&qWSp*X7S5Zpva%LvjOH#+4)c)5W(qXsQv$v#+=AG!`>)3vLv_8;_)_R%WyxX3 zZ0@{+bvAjQ0wGgsV+}+@^)jT0gGOwwStQhi2;#$UDKT#PgIoux=k5{%eROOkz=nOz z@Fo~ELergLG=WG}|KJ|1#hBDW9x%E^;im343KC^c45-CFKw;~5+Igoag&5UkF;)?8 zz}NMGM+7zHMq)b!u}>9=45?4GWJ(E9jzAR$Q4Z*hhS)bNF^beT%bV+rmeM@tjnrrK zVE_pZ#DL%QEwE_BPNMd<>I<5oSf;lyF(Hhmn-lZ}Rh1nS#Mh2qH$lTVw4KP5sDcf{ zf6owUN;jO42hk*GqmJCal|ww}g%BPjvJKVWpz}sjrOZ{}Tp$>&kgvVPxiu^>$bpAq z0ar)d3KuAg4V%}2h=SB0F3O;rUt)G&B25n>5}cGlDOQJdU+b3BX{I8KuJ!~FML}*S z68c~3ApE|{a6=pwp_&B{v$-fSe3(tLyp1wQff({;r0Z1z@uWk>!I#U$a1pDE{$VRo z7hU`kZbXvxsq3w#PiYxbm`dM@;|8^W3~|V48OxV0a;SU4QMUlXEm9X~v?5=MJXD{+ zNjPYvFHdX=FoGV(7}QB2)-VN{t|SR9W8s3HBmZ_H!s-!m?!E+?CQlMm1)6HaWJ=Z& ze-3c}7#K2#l$&Z$rv&_^hXbkrcWX%2R(2B;O4LHW#PI|Uk!FBp15GE3Pigv$Fk>XT zPzkM%B#;ncG&ADfLm4ET`2FC@e6gf>y-%n{2^w%+^$lhR5;YI%0qDWtX!l;nvFbC~ zV5Vh)X**9Ty|c)}>As?P-8#*|U)jq9Pe!Yv8l%;a%!EklP9x`Uv<|2>%V#@eog8P* z(`!1v$A8<i=Y8-8hx!q>ZJmwRF7X$K^j2edMH#mkfI~5t$BKD7IJ_sw-&AeJ@ElXs z7znXr&HlP1+!&d(8d<s|cYr<a|Bz(tQ4(&Z?!`k$dBA}U!?a!6cBc(&g*1$r<WJ5E zV{p18bM)a34tYRh%SND!>BBlo2Aqp2{REO9ayv(~XRj_zJ4fU`na{KYIP`-UdsHFN z{79J(Q^{ptCz-xGqB`0pcWW7ICOLuA2U{E*y8cr!E#zo?IP)P92z<}#WcuDMg9?n8 ztLtRyK<qP?d~*~i+qNf57l6GNIVt#V1-otbD6D-?ws3zZ5qPKo*DSUd`4cf%OthL< zUX(U;FR~BOfLEgWN5QdVKM<|3W7;2#@j+CXsxxs4`#poS+fWb^Vjhr*Xv!8{SH|sw zx_|W#33QD-ATri(uml)`BJZ8k(2gcTrI^#u*MLV_aJwnDgCs3D3!hlYX&;U)LB~<G z_hb1e0Rs{Mni6pgcS^_0KT2>2kDBJMbG01UD&|z=9D2n$Lx6w~4#3uRBz`v+BLOBy zhNu(L?9Ep4{Uiv3qbL552YlC(_wGOScHj|Uhx#i)9#Hrm%L3sj5<P+))*m~)j71B- z@&=yy_;`Gx_AeNUe82X!_sxli?=5^HuzJfZLB~V>1wr>J-4~vH!=1AM{p~n>8V@da zYwmeBqB<>5gy;YTnbRL0%svwLUAnfPT*UpoKTx=EGj}U;dh??Wo{r;wwl+x?V#lv> z1IG6@`x2Ng+r65|hxx3{9H@)>Hv5dfq?X6grdk&Qs>p}J$cVK0bRD{!fnP9;9e`32 zVpMIo-|yjST;3nDcFszUAa~9ZZMwqMEJujrbj}`v-HWiIyWTWmcOXx?gOeb=<B1?0 z&mzuEpJRgk53F5A>6GNUx3XB8lDCnQGsYqXU{6!dC_T99-yX*VFc0@Q{H|FTk!O+h zFB;FH)L!V<8Y+apk9lDJ^b5Hf3)a_JV0IqhXR>10uq4>VMpilGD6+^3tpQut^6Z^h zHCGHnppIqZosLtHZ5@aPOgcB6s$(gK7(JTY+(?-KjNw#C)#0n_S7PW*mU?~-1lnGg z!|PZ+$M$T=s!anN=;HU^fiozs-hn6D0))`tsCslAid;jRsK7R=*?_U+MLuSLlcePz zxoWO$QL*Z*jR~PBow`>{Maex`Yku<?tl;w?orvQbDlmHq$+B!kmIr?G><L+O!QR%8 z=7ihb)elY5u835v2#@PLO)e#hnMlkH2VyaxwB@qYOk`*ok(Q<8@2I?FDZk4i%`?sI ztpZI_eVSCQI$FkNoF=2WojeJNsz&)ihEf8u;=l{+CZq3LTaZ948B{4OtB}nFEGs&s z5gMxytb-t}e7YU6DhJJdu_{E)k5-c0v2s;2RWA9-?Gc8Ejks!?a8>g$d2aaxnSWp1 zIe^^Ln5<tWWY-H61}m2r0?4Tw3NAZgmCIZ_GejJ~<eEeSO=gbZfXd}yZel`eAa}k( z$f&F?KOZ&F^>{D1QhnBh2pWNQE~?2a(!yF#Bd{!~$^7;|tv(QE7Y|y;rwPw2p~<EH zEg^aQas*bXPG8H`pvq_Mm7gqo@K+q@8vhli(djkfB@$W}hw_doP!ps5evNEKwn*qn z9GLv@9HJT9Z;jok%7^5cPoO58GZC3hK)?Y^Q}Gc8W-PbP$ez>iB33QqGcsa{0h2X8 zvNqE2Y=&rB5k1pb6uH&&!d0nzFY8<EqnaqAR?Wv=vF9g`mxcLj&M1mimUi~iX@YJr zo@w6_Z*d_Gj4|952WF9ai2@aP6-0sJ<*bdf?qhG)hWMRG9>IYo#SB11oN^{|gZ51( zt_!cVEx3)KRfZ8yq*Z1UgI<NY**+sHN!dLC@c4()&`-M@1T8bUWNlD=w9qpv>PD?Y zg_fNi5<$zN+1`c%CHS}LW9r-RF%`N!GWmw4O=WH9ul(~J%9qQKIRWSm<mdhrC|8Gl zJ(aWOM%Ms8wy(w8k}I^PurJq@GWN~q?wRK??M|;BNobznTWA}+8?8kepG)YRzFR3_ zkWf@yx=QU(_dG`#X;wpcdYcCMaQAxn%DXhQ;82;;vvgMn9uOs-yuuKX+nu^VaL56! z$v~&Z-Mm0=megqI{G@wAf8FKk4G%FP4X?G-Ix%IiK=WMRTsRC$PHQeV&rsr!Xaz$o z`yM#&7`ha-IKlSE{K#@5$~{CO5Z<tj3oCch=J-X*)S2u`NY|@b28lyd&>UApb0zf9 zMj5$`%7|ys|HKT3jGdGsUt}XMPlA287!|oM{|*U{`5_dBF2k88q2HSynd-m9${ZXu zOq5h=Jep=})E5vf-9_Ff`Mqm`V=D>}wT;Td@l7DMTd?lnD=uJ6=N(g-ts9)3;r;7w z%va`}{5$v+YRcb1D&^weR1r=S_<SDnQXU-ReJBb8jK`Oi9OQ#|mKk`wGb02X`{;cg z_F1lJ?H3L_)KB4F4sIxeb7%CX{|b?z)DU3H<o9h=6<|3s!U2npA#h(^KU947rw`Fr zv)AzBwtl9qWspoIz*rE^n$}K|Te0dx(HMgNLO85r82LB`-Buc8AE;g=x`=e<UpX{_ zFruoQ%NS|cH3(g39p?qwQrHllaM~h&Qd=&-(n7~E<&6BCKxY3;s5n!QhEDROo+l3- zqUMC+f1^{|CxTWpX$z4l{+vwLK0!pk8vo1n0R1_MxR!~loEF<){{z~ZcpKzf@lW&n z;qW>LE)@j=UAQe0mu5Krio7ZTS11CK)FQ;F>~Swr%_J?dONXSSEpmUAhk#ow-t5SP zjvBa^M<V}9B(&E5E7Y%>&vUo|jhF>JB}n^)agdPGJ!6I-vuS*APV`R4psK83Hp`z4 z#TkGV9FtzRWA+tHf2qoD`>f6^BZk3n^_cg|Ox24iQnD%x)>YOUqLAahwV=Es)~`Xp zFN+ZHEpo5jhP>*64j4Hp7P&X;bfU!_vnVrSD0>U<qHUTI&bN~HD5uLvCiSI`eNk-2 z@ZztkTL?Cp3?r;+nr%7Ty;`1ooT}p$X;2LgGq>2f_X2STZBkpsp`7MA1B+-xm3S?} zMx9<|LB0QC$4lV5)F{9>Gg+K<dL&wL3}Di6yn(nqQd6>n=U<JXYa!}ao|-@~%6(W) zLA<3I1FF+vI(|*SXe>j~Y`GrYouM?K$qy8AG;1_Lu8$SGKE$KI-_k&Iw;VqTz`K5G z#i!QKu<(2rTK;dVA&z9f{3|+uZ=yUyZ7^iNk_H@%U4^o1?_|`53r1yDA&*sp1oH18 z|G-Dt)uxtCDH-=g)298fKbLNRD@OCqSiP*v=zC^ta56|!)nK>nU2W)F`)TWblI{a~ z4@#7RztM<p@?fA3xe+1Bh-Z4gKv3w9kFCqJWlPe`0{GVn1dDq{4aIuwGM_yHQ!6C{ z@!H-Ct5S(TNp8@MA%{dyK_z>)>UEZyHMO)TeL=POA(nVGmVwEG8Ys>YOeEl}A!pCT z&n*&$+}sWteI>=I>IUgmpT<{gs)($t$XoT48!$O~x&5?}<K>dc@Y=`XqE$8M%h8n3 z(;B{Kv1x=9^0#&72^4bYP5yoFl(Agnpd_!K(f`hDiyl1!?cF(fIYA~VYc*)`ru}Pa zZ_@a?Md`m<CpHGey37b^rf}l|pZUu#2)ZF%%EauaTVGM=spL$b>&Fclk?~B9m03#0 z9~KH{f0`<^jCbPz8q=kM<MWglMjnfp{S_4d>QK0H8x|s>VA7gqb0F20k%q@G=I0lM zxAj#jVoG?6cldK>?`cKUt#(WyMd`ER!-M;p#^v=$9|yWK7&RqphZ0GK%=>gzf&q6L zO=+a^{Oe{KKh+QiE`obRL7oR8|FUf=kDMs!+Le*Y04eK$Y~eUNct_{HhRi<(QA!mE zE|d0L=NlMs!jtcR|B+dW{c&Yx{Op8%2j#K*K_9r>zl2N@Rn3IDBwMsCbJsV33>4Ui zgh6Q{0#o&GS1pfN;az=H(~nq<(lpdgFOTf1%lA__B0cusxgy<|d5WTMhWLb!wz=DD zmRtt1Uu`^jzw2R8b`;s+K(~!1f@5_Qb`Q#9UK7A4zr>7s)?=6`Z#5Fx2}Z|kFRGx0 zN`BZzfB&5rNa)v<2-%-u5jB#N!ez-}WYJv64q~bPf^_n>ZdmWqUl0SVK*W#I)$^^C z%on&CQE>1z$KVtuir1qWQz*Iw`MSq&D16ZS+NN-qJw;_!wEde4sri?sKPX#eg4q8` zFEUO8EmZ{JJ?;(e;AiPBDQODQ$PpYL`lDZ|cIY3|eh#401PG(Q1Aft1L`KQ&6UWjZ zHI5UGjQ!wpJCx9nO8upek0@Af1UyDHXGL&?oL~h%uSTbW!UPUlMzxMA%dv7kWC8G+ z&HgN?EE)gwKC$-8K{rgd2Db^(C+@uf0v{Hx7ZW!|=%+7??6_FmUdK@rZJ3*874CT! z=L$<cj)?=wt*TdR9d%0Yr5YydRf&!^)PD08rZ4yoJt~-&KCZf6BNSbR2M0ZHCk6Sf zA*%8OXWk<swi5Jz#8njKDbuTocR;ATN-!=BCkp2r#TorfeMPcsB2bVTEg*)mi9I;1 zv<Lh#2zN)3?^r1n|IEl7%_H{Y`G<OuPy$0>{9tP|91r1V{jZsV$v7JLCE}!=$Zo;W zTuReg{=yJ;q5>lt@#R`95!{pa;*exUxGk#rxPozayhZn#JoT@20u8=NbhBL@UYCdu zgTDYwcL`w|cX(nLm9()))h~>dk7|N7mup9`@E=bqZj5*Y*=B^8Sa&|lC_gM7B1x4v ziT!4NORE?TL{Pcu@g8T#QQpbO6mL?HZAz&`jXtaJ0-^NXL18{7^@{XVe-pkL(e$f5 z(MsbbV>-E@#OILn^N!<7#EEi0ci`~~LXHeKv+a>+;|iYkm1BWHhhK>QF$&1a@^c8u zYD!A%JWBsW`3o=UgTMX_{z=+ULMPmW0pr?{uGcTPoTYfFte(c`7uwEpt&$oA>fWEX zAMecgE6O|DahL?TK%`FUkJq?@n&%K+ZdtVm-M{Z*`>z>>n5e<U8TeEFc&+<FTH(gh z3BX9bfI`NPjo72H^%{J57Xh5bGlhFL2|!&}fo%rfNYa_Y6%yC@(!?GE-Z8n6fS|1b z`bUon!MyByfVuHF4T&D1`M{AFZ$EDIC7kQBG}DHi10BW_qa@L~CQp^kK8~d-Z%}r* zXPW33CYtE!Y^#HwdHUhWm=D-wCO>(bas1rFX){T66XqOCDp@7eW<MTLxo`Q6co=g_ za%;>-Ah)=GgiDlMa)4jy!t1!L@}(E|6FKqlBZQrED66d%kVYHob4|x=w?N4)6Jf8j zN-rf(o-%200F)99NHAKKAXRjD=v?~U#==XpRY`29$(FPEO>*O_CrhY8d9CO1?PeUL zO!OjZTMeC;&=?x^3;u<8faBy00k(Vp+l40<DQx&REf=EFzxQ$!#<Zfa;o2Ye``o>! z=i0@>u=v=}`37K)?uQgOlr2T=k>4Kh^QRNS#_Sn7Zr7d|`VWDTbKQp<80k#J<Df$S zewm0g-=ae$-*`h<tO>Jl$w3^wAT4`%R%@Otgh`x)v$H-r>$kL$kwPh|6a4M$3VX`q zX#h9wV7;Km$oFAfzw@hdVp#n(aeW4c+^VMCF1I)({cnCl;j1eY_T5293VPJ`c&yWK zM>$&xG0chHk1A;V@j@-XNrF-gSw^mB{C83ZdbF`6I5|Efz?4=2v^4lNMcqXwC8aQY zSILQDqKwd#QSSSfKPMPj!1TQq8#Xk3&T6hSRH>-;YWDsgV&<Nc*T5~<F=ywevN^8! zef=Yio&e~u9aei?kjsGw6*<*?^twsyd^&R2@FUWSWg@|9e)cfnzSX|qr-SiGY2(V$ z1p5IufYIR>B+jG@y4GReANac6SLcgS62fq=z?21g4~~VhxcfvKm6iT2_2xJ0&f5=F znIJqhw1x10n}~yZzufSI-t@uI-1I<(Ps@rQkMj(z_$K_0!rAr+>|(H|D4`ijSpy$o z`<rf8@%=>@AypKm%ezO~gwzo4d&Kk=gmvaZ;Vy6_VkK*8{EEm{4cf;MgSdQK3kVki z+S%9(oCqobX+&k3NpZp`5szaO>XrjE3fG*n{O}H?Lau+jkfZHRDs%>J)OLz}v%O85 z<-bZE?)ZS#jQiFdP6K+f7B*iTAUgfQ=mF1G=~tZFRYxjEojG=tTG`KUPOd81i{^Zb zB<;crPAdLxl@MZASCEjUXr}}>rDsy^Tz<e=sdNDjI2|tg3Jx6yd<<{vC&Tego{|o{ zW4v#)VRqU<(=0Xwuo7vN?pYcdYVeo-y6gACqbD*m-YawZh+%#Gw(Ul8ShT%T$I=Bl zSqqr`9?W|HI1~J{9V6CffXG3#)jl4TlOn^3l?|<~O|rLiX=8U*tVu!V3JA%jKU2;d zHmD8w4D;A8<FDm%ZrettFOHmDqMyM%xM00s+ZyJ3K!clxKhwMD=~F1LjIot1;ZV>1 zo>Lbld1#5|jKs6({9Nt8t;zaH{cLb$3bE61_7t?4rtL9PaysxU$l(h^I`BcI)o1j2 z^lkMP>wObp3_tUdY(qMkuK`{;mKrupa=`BZ@Z$WSC9$paFpNE_V|czZZ~1%s{_0OD zRB8e#${62)tiO?)pJRH#`I+ne&l7sGwLF$K&qVzN@v@m^?r=>_rE4`Y9zexft7ePT zA@&q_s->^Vv%KV?EKe?s$gULZo}oW9;)v!n5(8q;n^wsqzl{71i>XAv{7wf$i_}3` zSFT$sX6J_d<^{P1nRx%P=_yZar;5vVk6l%6T1@_X(H_N76pJ@=+<4Sph*iV3IApt> zX0)AmI&m76-x?AY*_={)@~5o2k*G=u&}q1FT1gJ|TCSX@8<<*Z>y<q>?4&vP_Q5y% zvFYR;I|2Xm-eUu4sjqeW4&;zoYYAytU{hR}L>}p5+Ha)Lt8!qxYqIJ43WL<DB4CPZ z7sG(ia%Q<{ixe<rqGFrjjI_@sv_Wryn1eXSZ`P4_Z&5tmo#cH-`SD$C2+nK~Qk`>3 zsar_B-jT}>hB|BVlUfeMoQ@rcxp14?%4nqBkGDg|krPW_<|7(I(=*VRrqG;HNSG@5 zsTNLt*gyG!=UM?bv!CT9f6KPgu@Fp@I7EwG*G*VQaaJ^pdevUk4f4?Aek2$)O3-V< z?SCh!9vcSP{!tgfG{?6nR9{~@N%v(2{bMmVof|k!_XTU>BbF@H<8)Mf38-XgU+?!R zlT<uWrb6G;diWIMU-+eg`kLG4IOI!1nRlJ}-?AAB=4msvrsMptp})os2ZmPgi3Asv zLg--or#9pUYovnLqjjH5Zq;Kl`1@md)FX5gD*O0Vr5|qa<@zyJrIx>V5uZZ3b?=lL z6nb&fogn8MF9lVw$8`FxZ`sKG4)M*bmql;=##ZPylg8sJKfx&J&x=zIjeTUfM_6s@ zrfrGBrM}$bK>reNAjU^4YkskzPm_<j*34!M3YO{IA|!YHm8+=Zi)FmU%&kk^t6WK$ z0Fx@8&yCzE->Xw@!}yf-R}39(lZEU;aT0DIOSjAj2HDZ(<=I}kwN+9Y-#iF3mh`(J z8go^g6H!iHFs8L~*$uJ}_i$@eh+5{EM(4jO6XnqB#G%GV+4TZe`Q%V*4eu7u;#`rw zWx9!Gg`uC|G#b;I_fNKS55t@<>a!ixOMu(i_=>N^G^d6?ZMDvOIbVJu?=Jj4Z=QH) z*}6Pd8M^29$h@Y@IZF*R9$Twu?{<}Bgtb>YJefWEc?~;p#Yq08KsHFf5L&qeB&3DW zr8SSV_ji`z?|w!rBz4f;K9F}p46}x?rH8<)3jq*Cya)gK>u*1(<M^L~4kC42Lr}P) zr*L4c1tAPiOa?zF4tB@KR!&xYnNQc_YyerEZ%yK@*w8O42Oo%%VjNA@%XQeMo%T-> z7ZP8;1CSrKjjAD#2Cb<cwLCFs^FjH(ZnIH36YJ=q4&SzJi#RmK>DfuRFl4<?X7|9C zM;vUFlw54W*h&k1TS1pR)g-Tw!=!F#EGGq1COeT~Tn-59LNl5#ez&ngy9q|hH^%PL zn~OTND=TnSv;!*3<;BvfHPG8GYDcX8B-I*%HAZW>!bbEoq`QaDm^VFnE&kMpsdbc- z_rI4uPqNMG19cWhynP8J|4B{nNF=gOF*(H-&Uw9%b@rCK-ctO#X7UibGcFTQsi^VD z_AI%L9Bs6%fvM)6eGBiIkqcNNidX7Xgf<ZjouKCOX746fd~U?Q*q|0@>D+T_^-Rfy zPgRKT(8&S6J--aJ$L;Li`n>&&wJZevCkDo$U)(J|BZsh8?XL<BDIvX1*Oo4c4Ls{K z{w-Jbb6*6af8?INcFughFe2KT5rip)D1h|6KGczZDG^IE>pFG8>Zvk+EXy<Ubl(In zRa)ijr@{xAJ&gaM$YgjFAV75)$^7_D6v{;kD<t!M9B<Twv1177bZqC<{4mvy#a`S` zO)cpzxRZt9^!;5bqP9vcq6oR!J{#&Y?ZB$3DSAR7Sh=tqtdUn}As34tYqg%_QDTqX z__my-g8Z8Chwq0C2`p8tFK3`T&b2}NeN<EBz|zcSqoPz=wdIiq<hgCo`fXdncX+X! zf5n-m)bao~@=x-<yX2bZ#j9{Q!+WC5HvbA{A;h9Z@Q~fQWK<J{>Q_dJ-ZKa1KyiQ- z5@XV5l79r_ds^Lpkd!4(P@my;G}Xzr7~A=PVtxNK|G@3wR7v1ldk+>+1%RgL0*Nt& z;&72TgovP{6`DKY8AwiIsi;QGla3X{?72Sy<;S9Z1k$=e<&l7GkyMSpn*60MiKz}h zz#FI9PyP$1ndXm0TVNH;{9$+srYVxRld3r7CdCLO4klhrb@}+i_TF>Az8q#qc}EYC zF&kcLTsgp`C8OlAD6ds?9@-l%UV$@eGT$9S)Ow_U!`0I)wh+iT)Ek$2;Ni>kpn-2* zTJEY$Z)~pu7sj}REf*<43oH-ACLou^_ki3EG}zr0mVWuEMOF3budi;#4=4Vbh2J}K zXunIJl$X5(*Og4Swp7q)Qh4jW(j%e7tzv5hjN-m#%KlSjDX>sTVgECt%7innjL`$_ z?osOnK4>N}GHV2wYUg{GeZm1&pG|COt0#UGhi>zZ5=qCyi<vS4urtjbUzw0TlN7K^ zsx*Z+5_=ntd#jgI2`zv&<F1Si<2ssv02Fl=aj2L@`w*xx@O#ly3;r4Xxb{r>3}`)W z8CEkd-G|Nsn#2DIIk`}Q4+?S8zBv)W{>`%sH4C#zYYhu@K3;w26>gBCaV>7jSs&>Q zVbTKV%UafM_%_IkC?$)iP{GR4Ina&mHCLh;n-ic+1#lX{JK}?NmE)up!^duRg`VVE z6tJ7;zXXbG%q|X05eE^zc0n!MRVxXF;?ibklUl=c<?f||DsjA046}vRC?)G!aiUV- zzQS2)&7!zYVy~tjOI0uV{nA44(AR7$DrnT`{NS?O{Jjb#t@KcQpBSyea$F=eRBk%Y z1`l7yYaV;-=ppB@#J``nrSz-jHV!Ba8A%|P<0JI+H-su^$4_bjM-i8Yjh+puBSxH7 zW<k!(XloyyB5sye=eFf-OUPHTD5<){9~lf4?AK24|5UmqPMO{=igMpaXY>qq*z5%7 z_aEM6j*0O*)+v|zs>)Tg<v9*T^m6xD0DbTUHNhsb=3PkO4wDLTNU(W>w+M2=Rvu-t zY9iq~=HvjLt4Rmz5_ZgaWNb~wWk+>28FH`aaN|KAia#2tFu~IbuG|ivYBZAu#!~)* zRi^CS1Z}3GAfpab73J?Ane%RoTtn0^O=_9!T)+1bEwGqztS+t6eSz(ls*h0(QY6bD zr`$wq(Q>BjtL!I?CCpZF7P#`+Yv53$wuFw-0-S)MmA6g8s0YP43!<9Fm>4PlYWtbc zN(PK5H9@9z&nU89^F!^!Dx(7%;})dJ=e3p({fXp^vxdPmi(4u(vF%~9!809p`?9rm z%JXYqWlR~>bw8FF$j{GyoygjpB`K@03j>(`C@aTfj!NK$Wk}#)5FkcG5~UAxX>Zav zlm1I-7}bJfjs))&T~l7Wr~Uxo*1M#VFEB$Q3e+{8;&nSDYyleSjB2J5>S_h=EZPWY z%byEX*b8R2%1^L0V2Y)r63L&{_bJOkdMfAnRw2=h7kOMaEc-LEl{karm@y5#Es9_8 zb}39rR@JW%2@EZ?i^$ZS@?ni+NQ9pFhRS>)Q)JE+42TgFO}Opyp(-T#Fn$VGY~*Y! zm0JS~%p^uf?1c{nd3^>xm>O)5s4r)w8nuK9)8+C8=xg?xA4ER{HnrAjs(+|ODXJ}a zXN6JuTduUgAKYLl3HM;XWT#Ubz5suAYy?I0v`!@e1d219&*DyQKW#EuYJ6-r`NmxL ziY5OdP?ppMdKPfqFeJG<Z}Vw6yrd-}n0%Nh_Y1xEHGki+*R+{VX<om0`3p6?BiYwn z{%ffH1|WUFlp*tZN}CSNB63sG7a6+sjcGMB^uB6@1LLB@A{$7u`Yl5I9hcul{T_R< zu203VLfV|qR)DRAWSo75LymMmmG?o;mQz5n)4te%*hRYab7-hzuBrP?*pvGEuFd3z z+5JCeFL!+6Hsas+V);3*U9CxZ<$npcoWoDX3X5SGIxZ%k@`sVFoo>z6bwMEz>-15} zb1GDfmRk*!5L{L(bAt`}IlVmIFHjT3Rz(})0v3zH+?jtiM?Zh`kc65u<Zmeyf0`-& zwB=ruyFv`oAvlM533iQ7#c$tk@2~An2A`=>{07W9*#whm`6C;fA3J<qjX2lfuwV%F zSZ);>)P{y&-hF{XBnc`s`?l=gRn3D@%Jsq@5pHX7wN7(0@RQ8nnAhD0IaE*Q!8ImJ z0u(RsEy@Ustr{+ki+IlC6qflVe|pi(X-grYVsS~r?`DP+D`P{xK{4D&<aX4P&y|?s zIQ;W0{;(y9fdUg(FZWLUwa(c3i8r%O-fg|-u^W+vj;o<qfWktO<N%X=S@ldS0KFW@ zI%9v*k)Mixp{h31vPXxh#a&t0T`t&s<==Guq$TQAMgyOnM^6<EdpM3U57fzN@oB=3 z-wY}VnIq78XUWflbY3y!e|E9sF=r&&?Jl&QRvTx}vPBXF=b=>ah#Lh5wI8_28}ctG z_5mU9q5m=_V?{QR&2Pea1EhN`W3~~;8@DWD5h1K{YX04}5*0X+wD?SQX!Da)2Xi!M zSV4~F*J5$6Y4w7?w^@T&pOvWM&ska2!$zXPu!tC8OEm|!M$-5Y^v;SVQfzY-kxMJl z_k%l-H`{*PYTXLB$M*YWPB6l?>t&sJHF9GD&2wi39MTwR1)PWAU`;FOOzda!Hq!VZ z<;M4f2$6{oT9~82oq?C(1%LTIWqEY?N6))jQ%5l>wd*-f6y@9FcmzT~94Qf^6Dq#f zL7-@{gA4^Ls)_w~9&@rIg|l>Yc#k$O{<T^v<@oeEs(74a9jov+5~EusgjE(hN_2QW zkqFyXs`xIv<KUe@(Yj@C5bEC9M?rBNywTK@FjE<8cvlZrBb7YXQ944T6M2hu$pFvA zoF-=FB`4B4kg~aB|46-Prl<l4eLnbVxL!1-^GhXQ-pG}@3NVj|KUgjrBYfJyto&q@ z1|Mk-zpldCdnd|<kIlbq`0H`rUcrMnySHD=K_73F=Xuap-hY)5;~8ox3ct9?>zniu zjh3`iu;A_-7V%oBpc7&3F{X{E7u}sNfiDvkJ;M_$Mjnflirvb3Mv_CN_N?^a=Wimk zE^JV{MHJA6;!O}EkiNgv7M&d}ke&DTSyeJO7#GG;V(Q^x(r*b>#E05xC0<G(lVR%B zWYgCcvQKldMB`|tr!+RlXMGYw3Bi&=kI`R}TSufH*rf?&AZO9~^86Qkk|HE#N5FF6 z)x|`BuzM;|7S$pc&7gsvJ!+<E$%>&QgGm|OYrP<kI{kHjHqsEMOidV{W}Z0DTlkqW z*?#TxuJn7Ok-LEU_%lD>o<R37rYiYTkxE!Bi;~m=zu|T-m967Bq3b9C^*1E!fa$Q* zf?-@iCq?xXmNsEDmUizLoF<q!m1pZ6yVThhy#;gpP=LO{(zNox8TC<1z2~JX&|>wU zCtiO!v10J}pR4Li(Lb^^lX+G#-`NS~=}q(r16PB4VeEO9*Vb62feHf{c}y2kui(C7 zxWCrTe~n%=?iT$YDlnM)&cG~{bW-|rL$udWD%OW5t%yJrJN=3&eU~nJYc{!>h-JB{ zC90R9F=yF7;#V20T^OCk;%ryd5!2|cV3v1yf*occwk|!SBIF9aeRvFxu5n3h@^&*! zHdj3YNYJMma<E=OCqNC;x09pG-SKK1D-k^m%rc1&YBgJBebjv{au%~!NYJh9u!6Sv zw^sCfQmtu7<x@3QXz~CkI?PlK3Ti&DyFjfK>2zzg@T7?ft7T~0^DHMHbtKy99mn8s z$BSrR+pH`(eokq`0{SL1l-jtQ74(nxnM>4a$20!4nmA#Ka{un4#pQJu>Y5DvRIQ`8 zhdY!U6{_dkT7NrV6k;vfXcSZ-Un~Z1y}u57(PYi^{j^yxDT{u>0>bvj*(h)gA3RN& z#3pvP`)Onbw@cXNkI==UEq8$0$dV@7rf!?CNlUeg>r(CCP>}deY&2!dHDr*mT|Ll` z#R-UfuElHau(DVmq)j)b6?jb0QqGZ)s>&kv7%9V&Ip~aCD*5a3;CN>-f|W(P;x4?8 zxmH!rt<ge~D8E4KKFPoWDuD)%z#=DF;!q|&Jt-;3x9{LF)!#Qc#1pj`fu_{};>b1P zL?=4$WHf^oh$89HSl>TxfzTg!<unMVh;>6%{|vExvW!FvkS3%VPYne{fkb=bOe2Eu zP9Q1lQUNihjt3&_!}X2Vv86-xjqC};L*ny~xDloiuK0cs2-p5>sAR<+qBbPuNIE|_ z#Qzov1%`{oq*!Z!?UCPGkGg;a58+jE3h3!iHeuTULui=|x^aQGsP0Tr-~Sf;Ng}}F zuc_U#sC5sC`mBFlJz;Q#x=KJO)dA7XH-8d$F$GNHae(OsEZ;eWG<{-y4=(l>>a#D# zj{kU~S&S_lU@IAgfi$dHTnp->Fa^MHS5@f2e{kY8EvUL4pg1BFwb1mP#^4kJMQgqI z34GMSVL(YTE3Ia+GAp&AB)R8115+y*>TNw!-;j>jK(r@=wsBf=ygkjIMzVdD1UTmb zrUbqLI;a%}6x%dl$$77LnjxRt*_1RIWz}ZXLYQh`vIJV^tfWK9-9f?3YSECTTwV;O z5)R2|G%MzvQv@SsHH`H5B95_mG-YNy<DRIG1Y(bHl*ux`=#>lmlLgrz>JO%Fp9*M_ zMDnQBbg-$_(^}gFm2u7~Z<W%;od*9VG0{q!#4+)<ic(amc-4I<_!{UkJ`*S|?gRk$ zx4u(MsG@Sn6mQrKu^z`B{g_njpy7``g`Z^6o+SU%N*it{Nz6qUJFS0zne9q-&j7W% zs5WJ?2nt}}W9v{&%X|IP^6&P%WwU-8OS+kS?O6B@^RG%J=;txa?|zBW@9L^$^Nd5W z(EIs?r)A@1rRDmnWOH^TH_OK^S{rsp{WQH<|0sU0QoIv9L3D2CI=qkJYF^RMQ}cJ) zG59b=zbV)6P|&7g&>2JckU}W@!zVnOh;(n)=^PZmK$`AVquCu>ZQ0^iN<U+=609eY ze^;5gBCjt>o39VkOJOWj4xT3?O8?Gv66~)Ghu&wpmfBoKyj;yqZ~A@}dA$|sc?rv% za{O0x-&>>LFwxcAYl_Qq+tY0`vMxJrT+xeXRH2-sZ5jAtN4MXWTpV$!soLGxMr7QQ zy7A5KMm4_Am&@aoRe|EB;ePgqAeks<%r_v@bQPDHTT9~gFWCm6|DW<|JMt9#Zt7d= z&HU%Z)2eY($8%md1p8)rl|~&dhraLlBI~pn!zkFIQgCNS`{t<re>6#~9ojOFvpWl4 zX<*MjhlDj3B7T><f1Vm!aW(E&e+X)edZctDCmm|a68x(AA=oloa`FGC{a_JQCH^>j zaKTH?lcXJU=n@L&XOYP>5_zYc*>X91a8>3~qcGlp#I@R^E#qNV^z-D+%o&K;exa%Q z7VWab+sQXgO2ZHQ$$o09@!{Ig&wY976#mR!X9|_Q&lhug>PR9c#lSyz&6qCV@6y$N zNyWlETP3e=T*?0{6o%Z+EhjYDVqJJGu+)qU?0s#xTzkApWwl$8r)ItI^IM7{Yr426 zW1wT5I>6{WsjqpmIQ^6zcZ0=Nw(`2)GRcex6_Y)jKlNGhu?0NI*SCHb_WfJ4`Le^1 z678q+JWq42&$<!Wn#=a<zT)5eRC`!*R)B4%$E%;duW$6><<UQ8?$i)!wy?gq7;#I? zwj2R=UmJq)b=iTfjs*=0ZAaqgW#*(2O>$(BW&oX{9>VyDRKB3w5Zu0Th4PzP^znac zW#MMPejR~SQd|t}V41avTT9#cI!>6`+f=Z5d-SYMg)y#*m&5Cg+ZY%-L7^Suq?G+7 z>wjd_Q~x6izxf~8j^G=aT@m%vt~E~*uSX041zE`TGQkKSu1eg)W~i?JfAGrR|APYs zMHY&FeP3Gg>I}_fY2B8S@9GK7RJd#F*do#&c+3?VWEWnDf!n<KoRv$G=#Dwk<m2%+ zTz!7_3a#vMvHka8y$q(lzJI#+#uQ<@=ni#t)Yl(yb9{Z@Z2P=Pc71-Db-LC=H0}KF z-rjG<=NatB*3%@_nOgX{yEFAC{9zG!Rb9NoA42!nr{6Zal&hqEqt<^rQHxuU^Z<8l zXtyP6NryL^-#6{EPT8=SZ0v?aZ_NMprs=|*R%fGHo&6pM{GPAC$e{mg?OEWN?%r2# zH=|TSx@eS0`0l%_EteQlh=$x_Y}jkI*(M`JDxwmFN|BO8B)uhFP)X87sN_;AxfB&8 zw}k)MTvPAw_x?WrcVqjW=X);Cd2Z)C&+~OWCk6NRmAEu9-}|m<=~?Y)NxS^D$Yr2+ zb&5~hN4+1-gKa}sPRc9T1wY9R_FRLRwnEQlsaw^u!S6nvcpb~SWYP<VobWG2%My3t zFt4(f4L&?L)R(5Wy?xyvwl43^;CBAP^VgqW-SOiFyYmT)=zYb-zOyd@X65CS9^QYp z{|dobegLl@yd~2)KL2Ia;M?F6vzbYOY}Omw-hi#H(u>@6j<*FTo|K>Uy&$~s`>W;z za{uFN+QHpdd-#L*>mIIo_wIU7Ld)*e{Clr&`rmuD=2G87&qW#m)n7@UXnU63)B9l6 zbTcqBEwSwT#~r1ShaBks!H+(z@0+Q;s?DeK#Y?>Y0bakx$<(Vl4t{%cy3_~A+N^`- zt>?32WeI)$L-&36_gqLL2JI`<&Q@B@c~&Qtz0Bw0QOY$+Ks&#Hb2KZ;GUptnCO5>v zSYqI5fnbM)MAy#gaAstAZ>lV5*PZU{N`ng19oP1J-zJerJ#*yxQSsFkg<YqDE06rp zt6z&J{`sZifmzw6A)oG3)@zQxpOzGkFT5O+Phx&q`p3#AEzyrXdtME8;JJ|jHG!&W zG7kzL;{)4_`f5;xg4>^pLJpv7>#W?PN-Feni92eInPqylU+evH2daC9{y6jfFD*KY zHgXaLxDuXTAw07}_ze?KXe?p`h#f!L!a9IMhb*a{bjX}eV^9s)0;D4f+{O`cNG&Yd z839q)d~nbO5(R2;=`21H1MX4)n9gJSaoy=Wa6X15m+fvv=Q~2+1Pvr)O&9P%J0k&q zot3C5EIK#?0U5JFbC@7%501=$$e;{*<ROJeZBP&ZG=iUS2qDti+MqXpAX0=O4+)tv zXgs7NLWn;F;fw@5_zAn5&=ekPI?|F%<x`n#&(SVMus5gksWfm0iJm;R5WV5CMIDS! z0|8lBnj>Ld-4P^3vl^*MAs|H48lQq_nxl8M5L!ZvAqtE}!03@n6ap3!S_s7=V9}Ju zn+xlYv^Jpf=t9atHb%x2v-PX389sEL28HcIWr-+iNawk885};FizJHr6R}j-0#Ka6 z<?+d0R4x*Wh0LfU*D#ESY#B7Z7tayQUx<Y06M;hkzW?TT7%Hqc;f<JZJp#vIgw!2- zkJP~7gzW$A8`QvJv7#}B-@~QhnrMQM1OPMeO#r_mc#YvWJQlbslr$DNgoVNK1e0Rv zvv`cLvX!43Unt)~iU7uqikFB5lc{TIp-(n3SZz+_d(nNUe1<y}J(;|)NO*?DVlsWv zDAYKCg@rK!V}wdHiV_k%%>KW@OmueyGZ7m{+d)Dp7%NPkry;|`gASAvXrm)SIQV8d zmv2Dj!p`H9Mvp7i$PqRZPp}z1;sz!FI*pxW<9u+<oFe30!)+UL2x4`Vmv-2V@02#h zHcnkAyYEg2N|W2)kz;k{Xk1vLz0P|rXBBUy@^zfAd*)rvQG3(ejhyG<v|rg=;=z4E z9=b_Q`_w!0-ky@>XT(}t+ue-5GjDvYd=|w=5TF$^sB$a*a1Sa^kiOi?eqMDa^I&7@ zJEKf#9ToD)Nc+-D6un)26l0S3+Ruu~2lm|H&=uUj^x)cqSo-t(raD$`_7-yXXS*lT zt;swlTN#_!teePV_!k}v4G0CYX4|kBK&t3S5#0blC`Xf-j)%v24#Vg%p5w7XtAhHC ziu|Mo|L{HzC+bb44nMUABjC{3pQKA<MxeiwF5%t(r*wfS8~OTCnFt~gB6~3q!f9JF zCdiq{l1(POnZ2paX4}<da|(?5h2kO${CfgpiQ@zY*P}WGjKPFi|5LRWTE~gA7jkpl zc#6>4NcExrot-rRYa>`1HPFDc69_mYUQ5eas6w7RBnFFs^m*>W)e%6!!bato&>5ay zph&<YAmJdwd%~s&Xgyeu81DKkPbOGY5s($1?z0&Q!!QCeXYhEyjEQC`G|33aZloJ5 z9u$Oj9IUdc(0a%UqC#$vJLJyx@u5O=$b+#Je1P>BVn9sD2Vy~Nh|Quy9Ed~bGT1c8 z7ve%Z`c^s%;zN8dE}ahX{n=0;oy%S&nm9PrijG0TqGf8_W{AooBpW{VYNE{mxKFgN z<WU=f#u70Tj0oL^VP|E#s6er?-pVxnP*`fUQ>|{+Qd|Dz_tjOiuZAwf&vO#Ptqobh zq?S$<-^Mivxj56_VD07uNCYKL)^d|$T0BL=M?Xn3<sJX*M$bS^UN210wm8`b_gvHa zoc~&<C8)h8zrEjgmZwT+(c8S;>(4ZN^Hbz9G9P^z#J_t?-AsbBeHG(1v6{V|$r5tc zwOpL8F|RB}XRpnHQU)H1Yli>Tc9efmTUo_?{>kXKof`!vcb8RV+V>Yi$Abv0Zem>L zw!0-RCp06D^xb@{ON%_*w2G3WzT8AfC)K3Kp7c`1q~Nhixr*VEY?CPS{-Ig=bCb{k z8~${-ce~mut@uzm?L%!>!D7OIZ>OwYsFvB@y{<~TXDQ!_cz&1VCiT|h<8s{<c<J!H z$Oh#Gy4&=(#vKv1v!=Y=D44%RU2x}^<=m5;t-Kj0;@2c{GTwoQ<$Q-eEopvPvgQ#c zy6*mxOFMr^b!pP4WsA+rlh{FCX?u1-E-Dl*Bk=AAN7Xe(%~z+LS#KltNh~qVIIOPq zJ2KD1RM*5EP8~v7$k-sBz2CnlGNZgo{=$@4vd#g8*M3U&@)upS$TIOpO(vJAYLK!G zBLl87D7VjwI-L`P-h264OlpXZ<%?2gYa@SP@1h-zO48EHZz(K(;i|Z$k0!$<vz`p@ z@oK#GcGZqOWpNVCtgPGF)US;@6FnQ4N6?JEbFEW;O#RW-nz%~whFrW-`h_FD`JcL< zFQGiz*3q{5qIF2*@%xGQZZ0a)3Bgl-d^Fs4{PQJ3;1`v~{q+V9dj>C(up}MzgdK)5 zaeF*hHioUrx3<1gA$eX=i(R!eO-@=>V0d0yX38MS;mNmI*GoRxre%M+1<mSPmt*y; zOs9nW@m16`{^3WF8>4&_(wp<VVgg(^m&gv=7ft<`E6e-hC@A(ko>af2t=DVsJ*5Y8 z6H;m&r^cGpt=;oNCZWq#tTVQ9QNWEanPO*;G`4<~Aw~q0qU6s$qv2HiJnMJ$OmXP` za^zt9w-pKtuAdIbb#!#pl4{A5`FyV2Kta_C_ntlFOmZOP(w>>AS@j&T`1zIMuDR9f zo@@Hijw^D|#Yw8)A#2~7c}77ORFCheb<MzozjxS@6?rx%qH^5=ndyJ-T-{o60IivS zf+4$MDbKJ2Kb3Z%-dbI<`|t+Z1|RCCdFr95#`)`|bnl&v-th{)dZDY%xPSMxE0NYV z1yciy)1SObt+)j}qhv^I)L3Gu<6qtyh`E+!_B8qAjQxAn`ZEexRnro_bTM0feY48) zq}4AyuZc<P;-L)B9G`WqR8af!o@d3H*MS}3$C95++hD#WXHn?>J7J28j4U~q^GtoL zs@)Te=4RH($Q-RWLI3MYuHD1gGBYq)*?4PxjMfY9eEz2s>P`y{-*$z^PKVu$;*F<D zPZ{!hWBIMI+_mlfnP`{bGpAR1m-lNkvu>0>3G0}VySUQL$74&v%2cFu=(N7R)qEoT zrp~j2TNXMsaG=fx=du=_YTGtVhpQb0$$9FV6Db_+7p9wUS#65D$Mp(xy{M)azlC|` zP|N&6qogM>(t*+%ufNc2EI9_W_>R4uxAtzo{Fn2QS85;4b3cb~6KhtGsFwAutX1S& zQzPH{`PMG;Jw}Mmep6lF`)%$l{Ingr&bgE*1bJt_xc9s~E>g|@a3vz2<Z!(1UQbl7 z(bC6}dPeJvANCY|5|i+#TK>5s^`63w4kt&RXHQmY>y9W|{@zQxqvl@c-&98}v=@`y z2|HhkI5Bu<%fQl*m8GE;e$I@1N`FMvtcBtSTlOlKSfutA`G`rR<sw^azG)ab$u?wH zIOu<R6wtro#BO}RfXB>myRiD8&)vO!H+Tv^p47dJDKk*WRL#T(iO)T~!AwA2Xkl(6 z-}a#O!1MBlxp(z(sjuU*>s%1M2V*)zvysZn$(nzuk#1yFUAQ25e*adwZdJ96hNMF5 z<-G0iL2RL*;|2PZgyZyMD}w395u49{tJ0@myM*ZIU)0c$F+bm(dKtYbKKg{T#PT=Y zrz{)Q6G)iA>&{Mzjxpj-Z{5BxeS@H|h<qXDj^w4qDjk_C^e)!ey>R_<VUMS2f<#T? zvw>aH|EQl8q}TbR{f=&NlGV4x(m7;~<bLM@foCsw>yPPF3kvzqlv$S#o0b#xp7=Vx zim5V~k#HFo<{MsM`amhx?UinoyIQ(~dtweEw0_ZSZ*+RBqEiadda9FLLQO_M6t}s> zGWlII{(1^lBH-D%44l){_fw}o%=q(Bb9e4)PiQTQW?pUTzA9C!f<a!o^#t{l!Kt&G zp42)|f4)8SDq~t`#M#&^Y*gB})2HveQR-H?wr7KeX-2ujn-DpY(J8toD;ODRHhYd- zvzbZRo}$Aj+zUu7s5r2<f>~AcJTv^o@r)%3^_#Lt#_u+4Se9^Jd>8YuxsgVO%d6=2 zkePZOvW1#?uL;`X>|Jf*f2c@TVpbUJ@IHj=KV8GbZbtOMbQxo>Zx=Gu)l3eR>?x`E z7^u;}RX}RWW%Y)yO^A<Q;cd~@`r)Eq&zY?zH6BO)B+0%cY2|vJ8<dwjR)Y_}ZD->Y z#5uRnYHp!ej@7am%TLLru+O~SO?VUcSGNSA<ompk_sSt|+bi0t$#w~9M;G7IR=?Na zbyCCebTHX7nzoSm_^GKCn@aTFcql;Ap*iZhdgDw>d+**^sNfXDgN`XKNnwjFRzLc< z)+=kXO2xjISY6BX8Y*Y+GF@8jS>pS&0L|s=vc<Wp8$z$_DVe<`FT}82acEhRr1oyi zmf)eMDTE@W1!?bgub=OCsIu@%b!X)hxxi&>I|SNy+w-Lt|G-xWmd$Byg0m@e?r&{Q zt7E<2C;w%^>Lr)wJVvl?<F`8wzS=)%nJ0B~@uG{SN%Y)ear@%qahWn1xMSkB3#1gv z_>wye4@%~~-re)|Tx-Tnm8S{La!(&cet5F{lbl{a*Ns~_T`?68&InFnjS>?AswoWt zjKI7e&1olR7f-u;Ub>RPNVmRFf3jfrh3Q$>OK!ey2n#D5d=oQsNc`}A>(Hx5-+0Y` z?IPJcJ-Jh`rg*4nUhUwCyE8wFa}*;G6_}(yii|wm!gd{<>BNujGY*M5<aw&pE2DMj zHs*C^*#pRJ-89+5M|b(n(7~?Fx7~EDAj3Zy)pKd?Cf^i&j*J~o(6Y^5?JpZOvrDTo zFvw`{ua$RgpDrugn0Ui?mx2N-F+}REb@%ggZ53oiAA$Y(<!zy|%?}xKcueXQ*O(PX zg6B1%xBDM=y)%YZDEmHZkgiW#vB6HTt0jZs8ucvs@NC-9r5d@qhYweqHAOuvPAxp0 zdh?~zrY&3EeVwoWLr$Pt;rko6YJwLz<`VsG6a9DY^yn@c#-oui77sSaW4q`Hn`pwM zO*ANuZK6TTzu82iC-0$Q6l&tWdDJWZY0E2$nM~O7PVf@``IZ-hn&>vW(_E=;t~6R> zm$`bW$z$+gwh=p3+-oS><A-9=H*(mu{;M_qS59uuyd+sLGwJKrPLzM=MfLsy^Y{Hr zZntCl3N55vT|H<{msyJsi6fNbrl&I^O4AXm(u<FM2tcflLqr9vGD=6(r`iM@i!e&B z2}G3jO^-7=_Ca15v8Xfw5f&fsaou>vj4;I8J|e>?;u=Cfy*NF>s8pVWND3$oL{!Kt zD^K72{d-eWQ()6v<-pRuYdL|4C?XNzx+_v9G9)na0mUlh!K<&G8x}mE?2E*7b}rce zO;XY=CVyePgLOl!9qs<DTek$EJ97^2jEIRb`er-(bx%Tg$+5!o19<6yfRds1rCSU1 zdbZK*q|9CWZHl_ihdD->o(%7_oh>Cbw-b#L6SJ7c;{8U#CPnZjxr^g&+TS_zA_>!g z(Ic)JjzC0W@z_yUZCuCzaH6LsDvk-+-+O9Wz@hueQJW-Wz$qT~kbzq~QP2QBAh0kl zXrl7}>|5b6#0jpId9;g(6;-Ub-6z;7S3^4Xz{hSK+3J@;b0xpKKQyS!O^sXNcJ)); zXTQ39e}7xZifm}!$%`vrNk7$u*ge?mr6?P32pLHId92*@TlubT<o1)VT#w20Zq+)` zxrXDlq42<IMlGdCZOQ&ih;~os;Qelwj;}PTGl|)V@!oXFWNMg>_2csgWocE4e+|yI ze_X=5(NUKuSa}d#{_RFzaq1IC<l6NnsQcd)XWi0THvd-Bv~zQ(qNk-LI8INBqvkC< zck|B9!)VDpYqO$ft(CuMAofhnvLRWlY+p?BBjcsFG&YDIlk!DWx4f2eYHFg-+>p5O z#gLfRAMc{$2Atq?^MB0Y#8@r(;JQ8{BV$h2vLeWM@sebv`BRQ3Yef@CTnS0$U5mY; zQyr3*v=~i^C7mb>%kmTi5K3(CCDhC^+C(Z1-g`Q`VgOsK9aU*Hf6s@|lb<%DYw-p_ zao8>CRWaKdnLBgcXqyfz896>zeIboWJy7)Sutk#6)69yj^%*5`YoB*lTiP}cV3>&o z57k|3I*RPFSLsyRh(~mX^?&@9GV{Y1qJ>=G<7Dfvo6)!fr+glJDMMG@JiD%+TKeps zrMrdc!mse{EiGTZYx^l($m#MR9Wi*1P?I3|zkYu)HT$6CoKxbl%p3Aa<(0Y1OXk>G z$CcbMKd~stw+=rv&<0X$@}Kg2Ix3_6XB<lkb`WTn>xInd?p|5ffA^^(?ck}h(t;S( zsJg1n@RjTPvsSiUU+U=Ln{sgFzFzX}kJ+(M_?gT13Ln(s&2GB)zFe9zV<zwDO^H`- znklr8HA~Gd6rif9^<}rhKdbZjW=C+xk9{}W7{6-C<0lCk3%VA6k)KEYjp_;`IsYWW z@=G{qWK{qGV3>%+<ME@~iU+}C;&^NY4|H!rY(-cc)mUKXe;Zg4E*AP*M`9`<ZUmH< zDj1Hi43{RxTttwuFbfEv{G`C5$lwG8o?xi{hm``2o}j>^D+P_$JsXV`%dy^Jf*Fja zeph_>^3_1mk2D2uf)tM+^{}?RZFj*s@f}hvLkdc_Zaunx)b?fL2^UNI*kehXPAA7? zrn`DE7#C7pX|As6Z;l>!h&^`VXxf|Oc86jTj@wObY*d}rqT+cr-dfG8nid<o!0toa zkt0Xil$&MSn&eXJ7_F+Yp7Au*X{oYlvT><rRr8u!;#7N;o6o9+B%YnQGO?2<vB8wG zZ$VI}6=mPoh2S+?e&OzgM;FT5{|M%H47`88rOUl2<5Q=6p_~(au%In%@B4<&_4A1? z?026(vZ9uB_q{5)-UBuDV)p6er_@_&t_k;Xex369@!H;V+nh_bC^XsBd<<Lq6o0=n zPV>iG?Urrzmm?%<cBq(px)l5Ebd1_p9HcANa%Yvcwp?>(hj978g>c+|l|fNHXQB*_ zW_re>Rd(iPqoFDogU64tLt9V4p}Kq1`A9dqCxfM<(te{*1<9c4sMzAnQRW;n-HTxo z$fa8aZnSm}^mZrGRP=Na+Im!wXrpTgs2mLs(b4ccB#8IvD3K{<#!A8p0+PUo;{y^- zNP!QN#UlxHR76ck;93ZVRFI-7d~Y2UJ9A4UpUq}^Gx$ifCQ%dCfHCSw4=#vH`m?#- zNLUlE0=neVJV+FfYa8wmlyp?Q_<Rltg8cpcHT^M~Y_2B+6G4m?g@({*4Nyaa7r^3E z1sW{gaxk6<5r*AZGZs%%gp($SC_)T72-ZYFqXUg@?-*<rQaCWxjqS(RQStL*&`545 zG+K*Bb=N>sQFIL$_CRZZJr_#D4U5(S#6@}Fu;|g@I1|wPX*_TP42S`A09PcCOr-g_ z)44h->nzQX>%7=J{&0c|3CN+P0*y@n-@*#;=J!+p3>_hZHmE}~2Pr0x07H-p)kULV zf(8l=SYr)iNLZMJ!8>R}lPUqegH~klUje{>3HZ;gCRCcxip{kKf}l&=%HZ%+y;mV| znnWTVNoI2bxT4T9zygn?P-!4j#Y7qf(2-Vb557Oh(GH^`>U5+JZAi$s2^9OcX-MUm zoBwAj`uGUNTv(4}$aeSh0rBwlhX0EY{o8z}xsM^t@#8W@8bfo3=uEm0Hy&U;3^1m- zlRVg5kPiep%i(~yJ;)5QSrCv@6<uv`lh5EY>AE6))MxUCFPIFWevz0|mZy%2K!Zm2 zp!zZSD!M*Y21^sLbC?@UhVJl%s2w!k*Kc?w0s(yoY=g$Ei7o>9FTMV~j>b(WNXY4^ z_zMl>FR3|U26V(ikLtSW?^JyiSnP%B0|+iuAXP(>Q1MV`7z1Olz_>V|P%7XDR0F#C zUuukKgQ`fIoK(G?{s(;m16T{$tUAiTRb$Tp2)J|)ki|pe5kjjs{6d0F3kHYuKn}xT z0tO3wO5qF18iAp4SS(1j`~-suXd-YCCc$9LWEiLbgMHTIdZ@``zyz!?6*s9K?$>d( zh{AmM&-Ju0T3~nc6AX>UqCg`5Cm2l7B7)~0eu4qd37Dy$VXzhfH+jBj3<f`W41lvR z*ZEU_0H*Z|jEEMdrhcl2Mgz(C1%}7`QV)Y8{z5A)6mD``SPRDfN*6rtm-%8*s0sM- zxm1u!<BA?#v0?;*{4pL09-(1_r&NUg9}-&6@?axH4*`H3t0B@+8^&RcjZp;H0ArvJ v8yH~l`eZa3NDvGY46s@-toz?<I13+u;qj?lzDOX^I6MwUC@ULT7$g1<x=Wr^ delta 23066 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 KL|#Jy^nU;ZbbufL From a7623bf0d20594e65c865b95aef3c4a980d4109d Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 20 Feb 2017 15:16:52 +0000 Subject: [PATCH 238/754] if host has 1 cpu (staging) then set availableWorkingCpus to 1 --- services/clsi/app.coffee | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index d13798c4ea..ed3349b172 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -176,7 +176,12 @@ server = net.createServer (socket) -> socket.destroy() currentLoad = os.loadavg()[0] - availableWorkingCpus = os.cpus().length - 1 + + if os.cpus().length == 1 + availableWorkingCpus = 1 + else + availableWorkingCpus = os.cpus().length - 1 + freeLoad = availableWorkingCpus - currentLoad freeLoadPercentage = Math.round((freeLoad / availableWorkingCpus) * 100) if freeLoadPercentage <= 0 From f9c687d5f89ddac4fa7f5268f39804df183fb416 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 20 Feb 2017 15:19:04 +0000 Subject: [PATCH 239/754] Update app.coffee --- services/clsi/app.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index ed3349b172..b5e52206ba 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -176,7 +176,8 @@ server = net.createServer (socket) -> socket.destroy() currentLoad = os.loadavg()[0] - + + # On staging there may be 1 cpu on host, don't want to set availableWorkingCpus to 0 in that instance if os.cpus().length == 1 availableWorkingCpus = 1 else From 61fb90c6aa915dfdb9b66e4c0db369eb6fadab65 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 21 Feb 2017 09:33:03 +0000 Subject: [PATCH 240/754] add fontawesome acceptance test for xelatex --- .../examples/fontawesome_xelatex/main.tex | 12 ++++++++++++ .../examples/fontawesome_xelatex/options.json | 3 +++ .../examples/fontawesome_xelatex/output.pdf | Bin 0 -> 30768 bytes 3 files changed, 15 insertions(+) create mode 100644 services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/main.tex create mode 100644 services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/options.json create mode 100644 services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/output.pdf diff --git a/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/main.tex b/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/main.tex new file mode 100644 index 0000000000..42bfa8e55c --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/main.tex @@ -0,0 +1,12 @@ +\documentclass{article} +\usepackage{fontawesome} + +\begin{document} +Cloud \faCloud + +Cog \faCog + +Database \faDatabase + +Leaf \faLeaf +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/options.json b/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/options.json new file mode 100644 index 0000000000..a2e0c09897 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/options.json @@ -0,0 +1,3 @@ +{ + "compiler": "xelatex" +} diff --git a/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/output.pdf b/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/output.pdf new file mode 100644 index 0000000000000000000000000000000000000000..327d317b29bc22807b26c78d64c1bb933f4a55b4 GIT binary patch literal 30768 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 Ds<j%{ literal 0 HcmV?d00001 From 1a4fca08ebbf76b33b95d82f1e27d74acd544a2e Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 21 Feb 2017 09:34:04 +0000 Subject: [PATCH 241/754] fix acceptance test config file for latex prefix latex command prefix was in wrong scope --- services/clsi/test/acceptance/scripts/settings.test.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/test/acceptance/scripts/settings.test.coffee b/services/clsi/test/acceptance/scripts/settings.test.coffee index a1f51ddb74..dd6574d43c 100644 --- a/services/clsi/test/acceptance/scripts/settings.test.coffee +++ b/services/clsi/test/acceptance/scripts/settings.test.coffee @@ -21,6 +21,7 @@ module.exports = #strace: true #archive_logs: true commandRunner: "docker-runner-sharelatex" + latexmkCommandPrefix: ["/usr/bin/time", "-v"] # on Linux docker: image: "texlive-full:2016.1-opt" env: @@ -29,7 +30,6 @@ module.exports = modem: socketPath: false user: "111" - latexmkCommandPrefix: ["/usr/bin/time", "-v"] # on Linux internal: clsi: From 40a2684801a50a33c94689ea083eab26321a7a81 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 23 Feb 2017 11:09:18 +0000 Subject: [PATCH 242/754] remove tcp code, moved to agent load balancer --- services/clsi/app.coffee | 33 ------------------- services/clsi/config/settings.defaults.coffee | 1 - 2 files changed, 34 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index b5e52206ba..c649842671 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -162,36 +162,3 @@ setInterval () -> ProjectPersistenceManager.clearExpiredProjects() , tenMinutes = 10 * 60 * 1000 - - -net = require('net') -os = require('os') - -server = net.createServer (socket) -> - socket.on "error", (err)-> - if err.code == "ECONNRESET" - # this always comes up, we don't know why - return - logger.err err:err, "error with socket on load check" - socket.destroy() - - currentLoad = os.loadavg()[0] - - # On staging there may be 1 cpu on host, don't want to set availableWorkingCpus to 0 in that instance - if os.cpus().length == 1 - availableWorkingCpus = 1 - else - availableWorkingCpus = os.cpus().length - 1 - - freeLoad = availableWorkingCpus - currentLoad - freeLoadPercentage = Math.round((freeLoad / availableWorkingCpus) * 100) - if freeLoadPercentage <= 0 - freeLoadPercentage = 1 # when its 0 the server is set to drain and will move projects to different servers - socket.write("up, #{freeLoadPercentage}%\n", "ASCII") - socket.end() - -server.listen load_port = (Settings.internal?.clsi?.load_port or 3044), -> - logger.info "tcp load endpoint listening on port #{load_port}" - # telnet 127.0.0.1 3044 - - diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index f1f7492a63..cb7e6be7b1 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -35,7 +35,6 @@ module.exports = internal: clsi: port: 3013 - load_port: 3044 host: "localhost" From 12b7a372e439c33033c995bd5863c911f9bc8432 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 2 Mar 2017 16:43:35 +0000 Subject: [PATCH 243/754] allow latexmk to pass through options this avoids problems in the latest version of latexmk where the $pdflatex variable has been replaced by $xelatex and $lualatex when running with -xelatex or -lualatex --- services/clsi/app/coffee/LatexRunner.coffee | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/services/clsi/app/coffee/LatexRunner.coffee b/services/clsi/app/coffee/LatexRunner.coffee index e743cf0174..efd89dfc4b 100644 --- a/services/clsi/app/coffee/LatexRunner.coffee +++ b/services/clsi/app/coffee/LatexRunner.coffee @@ -62,31 +62,32 @@ module.exports = LatexRunner = else CommandRunner.kill ProcessTable[id], callback - _latexmkBaseCommand: (Settings?.clsi?.latexmkCommandPrefix || []).concat( - ["latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR"] - ) + _latexmkBaseCommand: (Settings?.clsi?.latexmkCommandPrefix || []).concat([ + "latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR", + "-synctex=1","-interaction=batchmode" + ]) _pdflatexCommand: (mainFile) -> LatexRunner._latexmkBaseCommand.concat [ - "-pdf", "-e", "$pdflatex='pdflatex -synctex=1 -interaction=batchmode %O %S'", + "-pdf", Path.join("$COMPILE_DIR", mainFile) ] _latexCommand: (mainFile) -> LatexRunner._latexmkBaseCommand.concat [ - "-pdfdvi", "-e", "$latex='latex -synctex=1 -interaction=batchmode %O %S'", + "-pdfdvi", Path.join("$COMPILE_DIR", mainFile) ] _xelatexCommand: (mainFile) -> LatexRunner._latexmkBaseCommand.concat [ - "-xelatex", "-e", "$pdflatex='xelatex -synctex=1 -interaction=batchmode %O %S'", + "-xelatex", Path.join("$COMPILE_DIR", mainFile) ] _lualatexCommand: (mainFile) -> LatexRunner._latexmkBaseCommand.concat [ - "-pdf", "-e", "$pdflatex='lualatex -synctex=1 -interaction=batchmode %O %S'", + "-lualatex", Path.join("$COMPILE_DIR", mainFile) ] From 533804c55bf34b68fec66a7617b809da38b17561 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly <shane@kilkelly.me> Date: Mon, 6 Mar 2017 14:43:14 +0000 Subject: [PATCH 244/754] Don't compile acceptance test files during test run --- services/clsi/test/acceptance/scripts/full-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/test/acceptance/scripts/full-test.sh b/services/clsi/test/acceptance/scripts/full-test.sh index 0295eb5a40..a465bdde16 100755 --- a/services/clsi/test/acceptance/scripts/full-test.sh +++ b/services/clsi/test/acceptance/scripts/full-test.sh @@ -11,7 +11,7 @@ echo ">> Server started" sleep 5 echo ">> Running acceptance tests..." -grunt --no-color test:acceptance +grunt --no-color mochaTest:acceptance _test_exit_code=$? echo ">> Killing server" From b32c6b1f39642d01984b1aab21278b3b699e1216 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly <shane@kilkelly.me> Date: Mon, 6 Mar 2017 14:56:32 +0000 Subject: [PATCH 245/754] Upgrade logger --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index 65367df1f2..73bb08af3d 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -13,7 +13,7 @@ "mkdirp": "0.3.5", "mysql": "2.6.2", "request": "^2.21.0", - "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.4.0", + "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.5.0", "sequelize": "^2.1.3", From d123f8eb643a92c962314f72d6628e23fdab82d3 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Wed, 8 Mar 2017 11:25:09 +0000 Subject: [PATCH 246/754] include otf extension in fontawesome test --- .../acceptance/fixtures/examples/fontawesome_xelatex/main.tex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/main.tex b/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/main.tex index 42bfa8e55c..5158b672f4 100644 --- a/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/main.tex +++ b/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/main.tex @@ -1,4 +1,8 @@ \documentclass{article} +\usepackage{fontspec} +\defaultfontfeatures{Extension = .otf} % this is needed because + % fontawesome package loads by + % font name only \usepackage{fontawesome} \begin{document} From d09cbfaa8dae14a5058e641a65c4a779d6980e51 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Wed, 8 Mar 2017 11:48:36 +0000 Subject: [PATCH 247/754] improve debugging of failed acceptance tests use the example name in the output filename --- .../clsi/test/acceptance/coffee/ExampleDocumentTests.coffee | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee index 6c9e96bf8f..43dbff3c88 100644 --- a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee +++ b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee @@ -16,11 +16,13 @@ convertToPng = (pdfPath, pngPath, callback = (error) ->) -> callback() compare = (originalPath, generatedPath, callback = (error, same) ->) -> - proc = ChildProcess.exec "compare -metric mae #{fixturePath(originalPath)} #{fixturePath(generatedPath)} #{fixturePath("tmp/diff.png")}" + diff_file = "#{fixturePath(generatedPath)}-diff.png" + proc = ChildProcess.exec "compare -metric mae #{fixturePath(originalPath)} #{fixturePath(generatedPath)} #{diff_file}" stderr = "" proc.stderr.on "data", (chunk) -> stderr += chunk proc.on "exit", () -> if stderr.trim() == "0 (0)" + fs.unlink diff_file # remove output diff if test matches expected image callback null, true else console.log "compare result", stderr @@ -68,7 +70,7 @@ describe "Example Documents", -> do (example_dir) -> describe example_dir, -> before -> - @project_id = Client.randomId() + @project_id = Client.randomId() + "_" + example_dir it "should generate the correct pdf", (done) -> Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => From a6026294066e42a9bee1135353e6ede9821ad42c Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Wed, 8 Mar 2017 11:49:21 +0000 Subject: [PATCH 248/754] update xelatex acceptance test pdf --- .../examples/fontawesome_xelatex/output.pdf | Bin 30768 -> 6613 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/output.pdf b/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/output.pdf index 327d317b29bc22807b26c78d64c1bb933f4a55b4..b5a12e39a6d5d056516d97b716bfbbdd4d7362cd 100644 GIT binary patch literal 6613 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 X`zS2V2a9$f1A|~>{QRo=YGnTf$lwzT literal 30768 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 Ds<j%{ From 4806da00a492f6e9c54a1c961e7badc74578ec49 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Mon, 20 Mar 2017 10:03:48 +0000 Subject: [PATCH 249/754] additional check for valid rootResource --- services/clsi/app/coffee/RequestParser.coffee | 9 ++++++++- .../test/unit/coffee/RequestParserTests.coffee | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/RequestParser.coffee b/services/clsi/app/coffee/RequestParser.coffee index 90bc739f5c..8fc4ecf370 100644 --- a/services/clsi/app/coffee/RequestParser.coffee +++ b/services/clsi/app/coffee/RequestParser.coffee @@ -44,7 +44,7 @@ module.exports = RequestParser = type: "string" originalRootResourcePath = rootResourcePath sanitizedRootResourcePath = RequestParser._sanitizePath(rootResourcePath) - response.rootResourcePath = sanitizedRootResourcePath + response.rootResourcePath = RequestParser._checkPath(sanitizedRootResourcePath) for resource in response.resources if resource.path == originalRootResourcePath @@ -92,3 +92,10 @@ module.exports = RequestParser = _sanitizePath: (path) -> # See http://php.net/manual/en/function.escapeshellcmd.php path.replace(/[\#\&\;\`\|\*\?\~\<\>\^\(\)\[\]\{\}\$\\\x0A\xFF\x00]/g, "") + + _checkPath: (path) -> + # check that the request does not use a relative path + for dir in path.split('/') + if dir == '..' + throw "relative path in root resource" + return path diff --git a/services/clsi/test/unit/coffee/RequestParserTests.coffee b/services/clsi/test/unit/coffee/RequestParserTests.coffee index 4cf6119831..1cd931bce6 100644 --- a/services/clsi/test/unit/coffee/RequestParserTests.coffee +++ b/services/clsi/test/unit/coffee/RequestParserTests.coffee @@ -223,4 +223,22 @@ describe "RequestParser", -> it "should also escape the resource path", -> @data.resources[0].path.should.equal @goodPath + describe "with a root resource path that has a relative path", -> + beforeEach -> + @validRequest.compile.rootResourcePath = "foo/../../bar.tex" + @RequestParser.parse @validRequest, @callback + @data = @callback.args[0][1] + it "should return an error", -> + @callback.calledWith("relative path in root resource") + .should.equal true + + describe "with a root resource path that has unescaped + relative path", -> + beforeEach -> + @validRequest.compile.rootResourcePath = "foo/#../bar.tex" + @RequestParser.parse @validRequest, @callback + @data = @callback.args[0][1] + + it "should return an error", -> + @callback.calledWith("relative path in root resource") + .should.equal true From 3bd919b3eb9416556fac6cea77bd1db5b999b732 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 16 Mar 2017 16:55:53 +0000 Subject: [PATCH 250/754] support for tikz externalize make copy of main file as output.tex for tikz externalize --- .../clsi/app/coffee/CompileManager.coffee | 10 ++- .../clsi/app/coffee/ResourceWriter.coffee | 2 + services/clsi/app/coffee/TikzManager.coffee | 37 +++++++++++ .../clsi/test/unit/coffee/TikzManager.coffee | 65 +++++++++++++++++++ 4 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 services/clsi/app/coffee/TikzManager.coffee create mode 100644 services/clsi/test/unit/coffee/TikzManager.coffee diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 0a1ea9d191..e4cb452fd6 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -8,6 +8,7 @@ logger = require "logger-sharelatex" Metrics = require "./Metrics" child_process = require "child_process" DraftModeManager = require "./DraftModeManager" +TikzManager = require "./TikzManager" fs = require("fs") fse = require "fs-extra" os = require("os") @@ -42,6 +43,12 @@ module.exports = CompileManager = else callback() + createTikzFileIfRequired = (callback) -> + if TikzManager.needsOutputFile(request.rootResourcePath, request.resources) + TikzManager.injectOutputFile compileDir, request.rootResourcePath, callback + else + callback() + # set up environment variables for chktex env = {} # only run chktex on LaTeX files (not knitr .Rtex files or any others) @@ -54,7 +61,8 @@ module.exports = CompileManager = if request.check is 'validate' env['CHKTEX_VALIDATE'] = 1 - injectDraftModeIfRequired (error) -> + # apply a series of file modifications/creations for draft mode and tikz + async.series [injectDraftModeIfRequired, createTikzFileIfRequired], (error) -> return callback(error) if error? timer = new Metrics.Timer("run-compile") # find the image tag to log it as a metric, e.g. 2015.1 (convert . to - for graphite) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index 16000cbbff..a7da588652 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -50,6 +50,8 @@ module.exports = ResourceWriter = should_delete = false if path == "output.pdf" or path == "output.dvi" or path == "output.log" should_delete = true + if path == "output.tex" # created by TikzManager if present in output files + should_delete = true if should_delete jobs.push (callback) -> ResourceWriter._deleteFileIfNotDirectory Path.join(basePath, path), callback diff --git a/services/clsi/app/coffee/TikzManager.coffee b/services/clsi/app/coffee/TikzManager.coffee new file mode 100644 index 0000000000..a7f29e9ccf --- /dev/null +++ b/services/clsi/app/coffee/TikzManager.coffee @@ -0,0 +1,37 @@ +fs = require "fs" +Path = require "path" +logger = require "logger-sharelatex" + +# for \tikzexternalize to work the main file needs to match the +# jobname. Since we set the -jobname to output, we have to create a +# copy of the main file as 'output.tex'. + +module.exports = TikzManager = + needsOutputFile: (rootResourcePath, resources) -> + # if there's already an output.tex file, we don't want to touch it + for resource in resources + if resource.path is "output.tex" + return false + # if there's no output.tex, see if we are using tikz/pgf in the main file + for resource in resources + if resource.path is rootResourcePath + return TikzManager._includesTikz (resource) + # otherwise false + return false + + _includesTikz: (resource) -> + # check if we are loading tikz or pgf + content = resource.content.slice(0,4096) + if content.indexOf("\\usepackage{tikz") >= 0 + return true + else if content.indexOf("\\usepackage{pgf") >= 0 + return true + else + return false + + injectOutputFile: (compileDir, mainFile, callback = (error) ->) -> + fs.readFile Path.join(compileDir, mainFile), "utf8", (error, content) -> + return callback(error) if error? + logger.log compileDir: compileDir, mainFile: mainFile, "copied file to ouput.tex for tikz" + # use wx flag to ensure that output file does not already exist + fs.writeFile Path.join(compileDir, "output.tex"), content, {flag:'wx'}, callback diff --git a/services/clsi/test/unit/coffee/TikzManager.coffee b/services/clsi/test/unit/coffee/TikzManager.coffee new file mode 100644 index 0000000000..859c392efa --- /dev/null +++ b/services/clsi/test/unit/coffee/TikzManager.coffee @@ -0,0 +1,65 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +require('chai').should() +modulePath = require('path').join __dirname, '../../../app/js/TikzManager' + +describe 'TikzManager', -> + beforeEach -> + @TikzManager = SandboxedModule.require modulePath, requires: + "fs": @fs = {} + "logger-sharelatex": @logger = {log: () ->} + + describe "needsOutputFile", -> + it "should return true if there is a usepackage{tikz}", -> + @TikzManager.needsOutputFile("main.tex", [ + { path: 'foo.tex' }, + { path: 'main.tex', content:'foo \\usepackage{tikz}' } + ]).should.equal true + + it "should return true if there is a usepackage{pgf}", -> + @TikzManager.needsOutputFile("main.tex", [ + { path: 'foo.tex'}, + { path: 'main.tex', content:'foo \\usepackage{pgf}' } + ]).should.equal true + + it "should return false if there is no usepackage{tikz} or {pgf}", -> + @TikzManager.needsOutputFile("main.tex", [ + { path: 'foo.tex' }, + { path: 'main.tex', content:'foo \\usepackage{bar}' } + ]).should.equal false + + it "should return false if there is already an output.tex file", -> + @TikzManager.needsOutputFile("main.tex", [ + { path: 'foo.tex' }, + { path: 'main.tex' }, + { path: 'output.tex' } + ]).should.equal false + + describe "injectOutputFile", -> + beforeEach -> + @rootDir = "/mock" + @filename = "filename.tex" + @callback = sinon.stub() + @content = ''' + \\documentclass{article} + \\usepackage{tikz} + \\begin{document} + Hello world + \\end{document} + ''' + @fs.readFile = sinon.stub().callsArgWith(2, null, @content) + @fs.writeFile = sinon.stub().callsArg(3) + @TikzManager.injectOutputFile @rootDir, @filename, @callback + + it "should read the file", -> + @fs.readFile + .calledWith("#{@rootDir}/#{@filename}", "utf8") + .should.equal true + + it "should write out the same file as output.tex", -> + @fs.writeFile + .calledWith("#{@rootDir}/output.tex", @content, {flag: 'wx'}) + .should.equal true + + it "should call the callback", -> + @callback.called.should.equal true From 54bdc8fed0914410bd9b8f5b3f79fc22326b9327 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 21 Mar 2017 11:29:37 +0000 Subject: [PATCH 251/754] create separate function for path checking --- .../clsi/app/coffee/ResourceWriter.coffee | 31 ++++++++++--------- services/clsi/app/coffee/TikzManager.coffee | 11 ++++--- .../unit/coffee/ResourceWriterTests.coffee | 17 ++++++++-- .../clsi/test/unit/coffee/TikzManager.coffee | 6 ++++ 4 files changed, 45 insertions(+), 20 deletions(-) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index a7da588652..8c3245f312 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -75,19 +75,22 @@ module.exports = ResourceWriter = callback() _writeResourceToDisk: (project_id, resource, basePath, callback = (error) ->) -> - path = Path.normalize(Path.join(basePath, resource.path)) + ResourceWriter.checkPath basePath, resource.path, (error, path) -> + return callback(error) if error? + mkdirp Path.dirname(path), (error) -> + return callback(error) if error? + # TODO: Don't overwrite file if it hasn't been modified + if resource.url? + UrlCache.downloadUrlToFile project_id, resource.url, path, resource.modified, (err)-> + if err? + logger.err err:err, project_id:project_id, path:path, resource_url:resource.url, modified:resource.modified, "error downloading file for resources" + callback() #try and continue compiling even if http resource can not be downloaded at this time + else + fs.writeFile path, resource.content, callback + + checkPath: (basePath, resourcePath, callback) -> + path = Path.normalize(Path.join(basePath, resourcePath)) if (path.slice(0, basePath.length) != basePath) return callback new Error("resource path is outside root directory") - - mkdirp Path.dirname(path), (error) -> - return callback(error) if error? - # TODO: Don't overwrite file if it hasn't been modified - if resource.url? - UrlCache.downloadUrlToFile project_id, resource.url, path, resource.modified, (err)-> - if err? - logger.err err:err, project_id:project_id, path:path, resource_url:resource.url, modified:resource.modified, "error downloading file for resources" - callback() #try and continue compiling even if http resource can not be downloaded at this time - else - fs.writeFile path, resource.content, callback - - + else + return callback(null, path) diff --git a/services/clsi/app/coffee/TikzManager.coffee b/services/clsi/app/coffee/TikzManager.coffee index a7f29e9ccf..7766bc12b2 100644 --- a/services/clsi/app/coffee/TikzManager.coffee +++ b/services/clsi/app/coffee/TikzManager.coffee @@ -1,5 +1,6 @@ fs = require "fs" Path = require "path" +ResourceWriter = require "./ResourceWriter" logger = require "logger-sharelatex" # for \tikzexternalize to work the main file needs to match the @@ -30,8 +31,10 @@ module.exports = TikzManager = return false injectOutputFile: (compileDir, mainFile, callback = (error) ->) -> - fs.readFile Path.join(compileDir, mainFile), "utf8", (error, content) -> + ResourceWriter.checkPath compileDir, mainFile, (error, path) -> return callback(error) if error? - logger.log compileDir: compileDir, mainFile: mainFile, "copied file to ouput.tex for tikz" - # use wx flag to ensure that output file does not already exist - fs.writeFile Path.join(compileDir, "output.tex"), content, {flag:'wx'}, callback + fs.readFile path, "utf8", (error, content) -> + return callback(error) if error? + logger.log compileDir: compileDir, mainFile: mainFile, "copied file to ouput.tex for tikz" + # use wx flag to ensure that output file does not already exist + fs.writeFile Path.join(compileDir, "output.tex"), content, {flag:'wx'}, callback diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee index c1e72a8789..c3c25cc23c 100644 --- a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee +++ b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee @@ -157,6 +157,19 @@ describe "ResourceWriter", -> .calledWith(new Error("resource path is outside root directory")) .should.equal true - + describe "checkPath", -> + describe "with a valid path", -> + beforeEach -> + @ResourceWriter.checkPath("foo", "bar", @callback) - + it "should return the joined path", -> + @callback.calledWith(null, "foo/bar") + .should.equal true + + describe "with an invalid path", -> + beforeEach -> + @ResourceWriter.checkPath("foo", "baz/../../bar", @callback) + + it "should return an error", -> + @callback.calledWith(new Error("resource path is outside root directory")) + .should.equal true diff --git a/services/clsi/test/unit/coffee/TikzManager.coffee b/services/clsi/test/unit/coffee/TikzManager.coffee index 859c392efa..4c99daacf7 100644 --- a/services/clsi/test/unit/coffee/TikzManager.coffee +++ b/services/clsi/test/unit/coffee/TikzManager.coffee @@ -6,6 +6,7 @@ modulePath = require('path').join __dirname, '../../../app/js/TikzManager' describe 'TikzManager', -> beforeEach -> @TikzManager = SandboxedModule.require modulePath, requires: + "./ResourceWriter": @ResourceWriter = {} "fs": @fs = {} "logger-sharelatex": @logger = {log: () ->} @@ -49,8 +50,13 @@ describe 'TikzManager', -> ''' @fs.readFile = sinon.stub().callsArgWith(2, null, @content) @fs.writeFile = sinon.stub().callsArg(3) + @ResourceWriter.checkPath = sinon.stub().callsArgWith(2, null, "#{@rootDir}/#{@filename}") @TikzManager.injectOutputFile @rootDir, @filename, @callback + it "sould check the path", -> + @ResourceWriter.checkPath.calledWith(@rootDir, @filename) + .should.equal true + it "should read the file", -> @fs.readFile .calledWith("#{@rootDir}/#{@filename}", "utf8") From 1273a05ad49252559a4810ea960c06783a372aaa Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 21 Mar 2017 11:30:32 +0000 Subject: [PATCH 252/754] fix path match --- services/clsi/app/coffee/ResourceWriter.coffee | 2 +- services/clsi/test/unit/coffee/ResourceWriterTests.coffee | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index 8c3245f312..2bf6598012 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -90,7 +90,7 @@ module.exports = ResourceWriter = checkPath: (basePath, resourcePath, callback) -> path = Path.normalize(Path.join(basePath, resourcePath)) - if (path.slice(0, basePath.length) != basePath) + if (path.slice(0, basePath.length + 1) != basePath + "/") return callback new Error("resource path is outside root directory") else return callback(null, path) diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee index c3c25cc23c..96140c9883 100644 --- a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee +++ b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee @@ -173,3 +173,11 @@ describe "ResourceWriter", -> it "should return an error", -> @callback.calledWith(new Error("resource path is outside root directory")) .should.equal true + + describe "with another invalid path matching on a prefix", -> + beforeEach -> + @ResourceWriter.checkPath("foo", "../foobar/baz", @callback) + + it "should return an error", -> + @callback.calledWith(new Error("resource path is outside root directory")) + .should.equal true From dd35c5d88b841643688e35a5922ad8bdb33a8445 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 21 Mar 2017 11:36:08 +0000 Subject: [PATCH 253/754] check for \tikzexternalize directly instead of \usepackage{tikz} and \usepackage{pgf} --- services/clsi/app/coffee/TikzManager.coffee | 8 +++---- .../clsi/test/unit/coffee/TikzManager.coffee | 23 ++++++++----------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/services/clsi/app/coffee/TikzManager.coffee b/services/clsi/app/coffee/TikzManager.coffee index 7766bc12b2..cfa13321a6 100644 --- a/services/clsi/app/coffee/TikzManager.coffee +++ b/services/clsi/app/coffee/TikzManager.coffee @@ -21,11 +21,9 @@ module.exports = TikzManager = return false _includesTikz: (resource) -> - # check if we are loading tikz or pgf - content = resource.content.slice(0,4096) - if content.indexOf("\\usepackage{tikz") >= 0 - return true - else if content.indexOf("\\usepackage{pgf") >= 0 + # check if we are using tikz externalize + content = resource.content.slice(0,65536) + if content.indexOf("\\tikzexternalize") >= 0 return true else return false diff --git a/services/clsi/test/unit/coffee/TikzManager.coffee b/services/clsi/test/unit/coffee/TikzManager.coffee index 4c99daacf7..8174b4a2e2 100644 --- a/services/clsi/test/unit/coffee/TikzManager.coffee +++ b/services/clsi/test/unit/coffee/TikzManager.coffee @@ -11,28 +11,22 @@ describe 'TikzManager', -> "logger-sharelatex": @logger = {log: () ->} describe "needsOutputFile", -> - it "should return true if there is a usepackage{tikz}", -> + it "should return true if there is a \\tikzexternalize", -> + @TikzManager.needsOutputFile("main.tex", [ + { path: 'foo.tex' }, + { path: 'main.tex', content:'foo \\usepackage{tikz} \\tikzexternalize' } + ]).should.equal true + + it "should return false if there is no \\tikzexternalize", -> @TikzManager.needsOutputFile("main.tex", [ { path: 'foo.tex' }, { path: 'main.tex', content:'foo \\usepackage{tikz}' } - ]).should.equal true - - it "should return true if there is a usepackage{pgf}", -> - @TikzManager.needsOutputFile("main.tex", [ - { path: 'foo.tex'}, - { path: 'main.tex', content:'foo \\usepackage{pgf}' } - ]).should.equal true - - it "should return false if there is no usepackage{tikz} or {pgf}", -> - @TikzManager.needsOutputFile("main.tex", [ - { path: 'foo.tex' }, - { path: 'main.tex', content:'foo \\usepackage{bar}' } ]).should.equal false it "should return false if there is already an output.tex file", -> @TikzManager.needsOutputFile("main.tex", [ { path: 'foo.tex' }, - { path: 'main.tex' }, + { path: 'main.tex', content:'foo \\usepackage{tikz} \\tikzexternalize' }, { path: 'output.tex' } ]).should.equal false @@ -44,6 +38,7 @@ describe 'TikzManager', -> @content = ''' \\documentclass{article} \\usepackage{tikz} + \\tikzexternalize \\begin{document} Hello world \\end{document} From 222fc4b99c7e8df844c7295f7dec7065701abb9c Mon Sep 17 00:00:00 2001 From: Shane Kilkelly <shane@kilkelly.me> Date: Mon, 27 Mar 2017 14:47:48 +0100 Subject: [PATCH 254/754] Add a .nvmrc file --- services/clsi/.nvmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 services/clsi/.nvmrc diff --git a/services/clsi/.nvmrc b/services/clsi/.nvmrc new file mode 100644 index 0000000000..994fe99096 --- /dev/null +++ b/services/clsi/.nvmrc @@ -0,0 +1 @@ +0.10.22 \ No newline at end of file From 7e58bfd752ad2e0234073eb5275fbb56b6775ff5 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 4 Apr 2017 16:50:06 +0100 Subject: [PATCH 255/754] check if file is optimised before running qpdf --- .../app/coffee/OutputFileOptimiser.coffee | 17 ++- .../coffee/OutputFileOptimiserTests.coffee | 103 ++++++++++++++++++ 2 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 services/clsi/test/unit/coffee/OutputFileOptimiserTests.coffee diff --git a/services/clsi/app/coffee/OutputFileOptimiser.coffee b/services/clsi/app/coffee/OutputFileOptimiser.coffee index eb5266e161..b702f36f8b 100644 --- a/services/clsi/app/coffee/OutputFileOptimiser.coffee +++ b/services/clsi/app/coffee/OutputFileOptimiser.coffee @@ -11,10 +11,25 @@ module.exports = OutputFileOptimiser = # check output file (src) and see if we can optimise it, storing # the result in the build directory (dst) if src.match(/\/output\.pdf$/) - OutputFileOptimiser.optimisePDF src, dst, callback + OutputFileOptimiser.checkIfPDFIsOptimised src, (err, isOptimised) -> + return callback(null) if err? or isOptimised + OutputFileOptimiser.optimisePDF src, dst, callback else callback (null) + checkIfPDFIsOptimised: (file, callback) -> + SIZE = 16*1024 # check the header of the pdf + result = new Buffer(SIZE) + result.fill(0) # prevent leakage of uninitialised buffer + fs.open file, "r", (err, fd) -> + return callback(err) if err? + fs.read fd, result, 0, SIZE, 0, (errRead, bytesRead, buffer) -> + fs.close fd, (errClose) -> + return callback(errRead) if errRead? + return callback(errClose) if errReadClose? + isOptimised = buffer.toString('ascii').indexOf("/Linearized 1") >= 0 + callback(null, isOptimised) + optimisePDF: (src, dst, callback = (error) ->) -> tmpOutput = dst + '.opt' args = ["--linearize", src, tmpOutput] diff --git a/services/clsi/test/unit/coffee/OutputFileOptimiserTests.coffee b/services/clsi/test/unit/coffee/OutputFileOptimiserTests.coffee new file mode 100644 index 0000000000..2988715569 --- /dev/null +++ b/services/clsi/test/unit/coffee/OutputFileOptimiserTests.coffee @@ -0,0 +1,103 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +require('chai').should() +modulePath = require('path').join __dirname, '../../../app/js/OutputFileOptimiser' +path = require "path" +expect = require("chai").expect +EventEmitter = require("events").EventEmitter + +describe "OutputFileOptimiser", -> + beforeEach -> + @OutputFileOptimiser = SandboxedModule.require modulePath, requires: + "fs": @fs = {} + "path": @Path = {} + "child_process": spawn: @spawn = sinon.stub() + "logger-sharelatex": { log: sinon.stub(), warn: sinon.stub() } + "./Metrics" : {} + @directory = "/test/dir" + @callback = sinon.stub() + + describe "optimiseFile", -> + beforeEach -> + @src = "./output.pdf" + @dst = "./output.pdf" + + describe "when the file is not a pdf file", -> + beforeEach (done)-> + @src = "./output.log" + @OutputFileOptimiser.checkIfPDFIsOptimised = sinon.stub().callsArgWith(1, null, false) + @OutputFileOptimiser.optimisePDF = sinon.stub().callsArgWith(2, null) + @OutputFileOptimiser.optimiseFile @src, @dst, done + + it "should not check if the file is optimised", -> + @OutputFileOptimiser.checkIfPDFIsOptimised.calledWith(@src).should.equal false + + it "should not optimise the file", -> + @OutputFileOptimiser.optimisePDF.calledWith(@src, @dst).should.equal false + + describe "when the pdf file is not optimised", -> + beforeEach (done) -> + @OutputFileOptimiser.checkIfPDFIsOptimised = sinon.stub().callsArgWith(1, null, false) + @OutputFileOptimiser.optimisePDF = sinon.stub().callsArgWith(2, null) + @OutputFileOptimiser.optimiseFile @src, @dst, done + + it "should check if the pdf is optimised", -> + @OutputFileOptimiser.checkIfPDFIsOptimised.calledWith(@src).should.equal true + + it "should optimise the pdf", -> + @OutputFileOptimiser.optimisePDF.calledWith(@src, @dst).should.equal true + + describe "when the pdf file is optimised", -> + beforeEach (done) -> + @OutputFileOptimiser.checkIfPDFIsOptimised = sinon.stub().callsArgWith(1, null, true) + @OutputFileOptimiser.optimisePDF = sinon.stub().callsArgWith(2, null) + @OutputFileOptimiser.optimiseFile @src, @dst, done + + it "should check if the pdf is optimised", -> + @OutputFileOptimiser.checkIfPDFIsOptimised.calledWith(@src).should.equal true + + it "should not optimise the pdf", -> + @OutputFileOptimiser.optimisePDF.calledWith(@src, @dst).should.equal false + + describe "checkIfPDFISOptimised", -> + beforeEach () -> + @callback = sinon.stub() + @fd = 1234 + @fs.open = sinon.stub().yields(null, @fd) + @fs.read = sinon.stub().withArgs(@fd).yields(null, 100, new Buffer("hello /Linearized 1")) + @fs.close = sinon.stub().withArgs(@fd).yields(null) + @OutputFileOptimiser.checkIfPDFIsOptimised @src, @callback + + describe "for a linearised file", -> + beforeEach () -> + @fs.read = sinon.stub().withArgs(@fd).yields(null, 100, new Buffer("hello /Linearized 1")) + @OutputFileOptimiser.checkIfPDFIsOptimised @src, @callback + + it "should open the file", -> + @fs.open.calledWith(@src, "r").should.equal true + + it "should read the header", -> + @fs.read.calledWith(@fd).should.equal true + + it "should close the file", -> + @fs.close.calledWith(@fd).should.equal true + + it "should call the callback with a true result", -> + @callback.calledWith(null, true).should.equal true + + describe "for an unlinearised file", -> + beforeEach () -> + @fs.read = sinon.stub().withArgs(@fd).yields(null, 100, new Buffer("hello not linearized 1")) + @OutputFileOptimiser.checkIfPDFIsOptimised @src, @callback + + it "should open the file", -> + @fs.open.calledWith(@src, "r").should.equal true + + it "should read the header", -> + @fs.read.calledWith(@fd).should.equal true + + it "should close the file", -> + @fs.close.calledWith(@fd).should.equal true + + it "should call the callback with a false result", -> + @callback.calledWith(null, false).should.equal true From f7c35652813678014e3a7ca352e2855b2ff80c5b Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 7 Apr 2017 11:11:27 +0100 Subject: [PATCH 256/754] use pdfinfo on output to ensure pdfs are optimised needed to check that qpdf runs correctly inside the docker container --- .../coffee/ExampleDocumentTests.coffee | 44 +++++++++++++------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee index 43dbff3c88..a49f5d62cb 100644 --- a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee +++ b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee @@ -28,6 +28,18 @@ compare = (originalPath, generatedPath, callback = (error, same) ->) -> console.log "compare result", stderr callback null, false +checkPdfInfo = (pdfPath, callback = (error, output) ->) -> + proc = ChildProcess.exec "pdfinfo #{fixturePath(pdfPath)}" + stdout = "" + proc.stdout.on "data", (chunk) -> stdout += chunk + proc.stderr.on "data", (chunk) -> console.log "STDERR", chunk.toString() + proc.on "exit", () -> + if stdout.match(/Optimized:\s+yes/) + callback null, true + else + console.log "pdfinfo result", stdout + callback null, false + compareMultiplePages = (project_id, callback = (error) ->) -> compareNext = (page_no, callback) -> path = "tmp/#{project_id}-source-#{page_no}.png" @@ -41,24 +53,30 @@ compareMultiplePages = (project_id, callback = (error) ->) -> compareNext page_no + 1, callback compareNext 0, callback +comparePdf = (project_id, example_dir, callback = (error) ->) -> + convertToPng "tmp/#{project_id}.pdf", "tmp/#{project_id}-generated.png", (error) => + throw error if error? + convertToPng "examples/#{example_dir}/output.pdf", "tmp/#{project_id}-source.png", (error) => + throw error if error? + fs.stat fixturePath("tmp/#{project_id}-source-0.png"), (error, stat) => + if error? + compare "tmp/#{project_id}-source.png", "tmp/#{project_id}-generated.png", (error, same) => + throw error if error? + same.should.equal true + callback() + else + compareMultiplePages project_id, (error) -> + throw error if error? + callback() + downloadAndComparePdf = (project_id, example_dir, url, callback = (error) ->) -> writeStream = fs.createWriteStream(fixturePath("tmp/#{project_id}.pdf")) request.get(url).pipe(writeStream) writeStream.on "close", () => - convertToPng "tmp/#{project_id}.pdf", "tmp/#{project_id}-generated.png", (error) => + checkPdfInfo "tmp/#{project_id}.pdf", (error, optimised) => throw error if error? - convertToPng "examples/#{example_dir}/output.pdf", "tmp/#{project_id}-source.png", (error) => - throw error if error? - fs.stat fixturePath("tmp/#{project_id}-source-0.png"), (error, stat) => - if error? - compare "tmp/#{project_id}-source.png", "tmp/#{project_id}-generated.png", (error, same) => - throw error if error? - same.should.equal true - callback() - else - compareMultiplePages project_id, (error) -> - throw error if error? - callback() + optimised.should.equal true + comparePdf project_id, example_dir, callback Client.runServer(4242, fixturePath("examples")) From 86f29a4cfbed47542ad6c91f8d5af00be55c2827 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Mon, 10 Apr 2017 16:12:03 +0100 Subject: [PATCH 257/754] add setting to avoid optimisations outside docker --- services/clsi/app/coffee/OutputCacheManager.coffee | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee index 91e085ae4d..465b0431d9 100644 --- a/services/clsi/app/coffee/OutputCacheManager.coffee +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -172,8 +172,13 @@ module.exports = OutputCacheManager = logger.error err: err, src: src, dst: dst, "copy error for file in cache" callback(err) else - # call the optimiser for the file too - OutputFileOptimiser.optimiseFile src, dst, callback + if Settings.clsi?.optimiseInDocker + # don't run any optimisations on the pdf when they are done + # in the docker container + callback() + else + # call the optimiser for the file too + OutputFileOptimiser.optimiseFile src, dst, callback _checkIfShouldCopy: (src, callback = (err, shouldCopy) ->) -> return callback(null, !Path.basename(src).match(/^strace/)) From 7d93e771027947ce50e83db7ce51c04c66ce6088 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Wed, 24 May 2017 09:41:26 +0100 Subject: [PATCH 258/754] don't report compile timeouts to sentry just log them instead --- services/clsi/app/coffee/CompileController.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 56237bf205..250f9b8e7f 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -20,12 +20,13 @@ module.exports = CompileController = else if error?.validate status = "validation-#{error.validate}" else if error? - logger.error err: error, project_id: request.project_id, "error running compile" if error.timedout status = "timedout" + logger.log err: error, project_id: request.project_id, "timeout running compile" else status = "error" code = 500 + logger.error err: error, project_id: request.project_id, "error running compile" else status = "failure" for file in outputFiles From 42fa852b76de5a60af4fdfda78c4973c004c1f8c Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Wed, 24 May 2017 10:03:53 +0100 Subject: [PATCH 259/754] check file exists before running synctex --- .../clsi/app/coffee/CompileManager.coffee | 22 +++++++++++++------ .../unit/coffee/CompileManagerTests.coffee | 2 ++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index e4cb452fd6..7caa20c0fc 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -201,16 +201,24 @@ module.exports = CompileManager = logger.log project_id: project_id, user_id:user_id, page: page, h: h, v:v, stdout: stdout, "synctex pdf output" callback null, CompileManager._parseSynctexFromPdfOutput(stdout, base_dir) + _checkFileExists: (path, callback = (error) ->) -> + fs.stat path, (err, stats) -> + return callback(err) if err? + return callback(new Error("not a file")) if not stats.isFile() + callback() + _runSynctex: (args, callback = (error, stdout) ->) -> bin_path = Path.resolve(__dirname + "/../../bin/synctex") seconds = 1000 - if Settings.clsi?.synctexCommandWrapper? - [bin_path, args] = Settings.clsi?.synctexCommandWrapper bin_path, args - child_process.execFile bin_path, args, timeout: 10 * seconds, (error, stdout, stderr) -> - if error? - logger.err err:error, args:args, "error running synctex" - return callback(error) - callback(null, stdout) + outputFilePath = args[1] + CompileManager._checkFileExists outputFilePath, (err) -> + if Settings.clsi?.synctexCommandWrapper? + [bin_path, args] = Settings.clsi?.synctexCommandWrapper bin_path, args + child_process.execFile bin_path, args, timeout: 10 * seconds, (error, stdout, stderr) -> + if error? + logger.err err:error, args:args, "error running synctex" + return callback(error) + callback(null, stdout) _parseSynctexFromCodeOutput: (output) -> results = [] diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index e3f0705dd3..68629a1c43 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -192,6 +192,7 @@ describe "CompileManager", -> describe "syncFromCode", -> beforeEach -> + @fs.stat = sinon.stub().callsArgWith(1, null,{isFile: ()->true}) @child_process.execFile.callsArgWith(3, null, @stdout = "NODE\t#{@page}\t#{@h}\t#{@v}\t#{@width}\t#{@height}\n", "") @CompileManager.syncFromCode @project_id, @user_id, @file_name, @line, @column, @callback @@ -216,6 +217,7 @@ describe "CompileManager", -> describe "syncFromPdf", -> beforeEach -> + @fs.stat = sinon.stub().callsArgWith(1, null,{isFile: ()->true}) @child_process.execFile.callsArgWith(3, null, @stdout = "NODE\t#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/#{@file_name}\t#{@line}\t#{@column}\n", "") @CompileManager.syncFromPdf @project_id, @user_id, @page, @h, @v, @callback From 550979991f713630872b0948b5a3b0d62633569f Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Wed, 24 May 2017 10:03:53 +0100 Subject: [PATCH 260/754] check directory exists and bail out on error --- .../clsi/app/coffee/CompileManager.coffee | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 7caa20c0fc..27aa394e72 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -202,16 +202,25 @@ module.exports = CompileManager = callback null, CompileManager._parseSynctexFromPdfOutput(stdout, base_dir) _checkFileExists: (path, callback = (error) ->) -> - fs.stat path, (err, stats) -> - return callback(err) if err? - return callback(new Error("not a file")) if not stats.isFile() - callback() + synctexDir = Path.dirname(path) + synctexFile = Path.join(synctexDir, "output.synctex.gz") + fs.stat synctexDir, (error, stats) -> + if error?.code is 'ENOENT' + return callback(new Error("called synctex with no output directory")) + return callback(error) if error? + fs.stat synctexFile, (error, stats) -> + if error?.code is 'ENOENT' + return callback(new Error("called synctex with no output file")) + return callback(error) if error? + return callback(new Error("not a file")) if not stats?.isFile() + callback() _runSynctex: (args, callback = (error, stdout) ->) -> bin_path = Path.resolve(__dirname + "/../../bin/synctex") seconds = 1000 outputFilePath = args[1] - CompileManager._checkFileExists outputFilePath, (err) -> + CompileManager._checkFileExists outputFilePath, (error) -> + return callback(error) if error? if Settings.clsi?.synctexCommandWrapper? [bin_path, args] = Settings.clsi?.synctexCommandWrapper bin_path, args child_process.execFile bin_path, args, timeout: 10 * seconds, (error, stdout, stderr) -> From f2746b7d5bf88c90730eda6a331bfa11587eb3a3 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 15 Jun 2017 15:37:45 +0100 Subject: [PATCH 261/754] delete intermediate xdv files from xelatex --- services/clsi/app/coffee/ResourceWriter.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index 2bf6598012..e2a0e1f842 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -48,7 +48,7 @@ module.exports = ResourceWriter = should_delete = true if path.match(/^output\./) or path.match(/\.aux$/) or path.match(/^cache\//) # knitr cache should_delete = false - if path == "output.pdf" or path == "output.dvi" or path == "output.log" + if path == "output.pdf" or path == "output.dvi" or path == "output.log" or path == "output.xdv" should_delete = true if path == "output.tex" # created by TikzManager if present in output files should_delete = true From 65eaf0ad1038f30c420a8adf405800f62b01e85c Mon Sep 17 00:00:00 2001 From: Shane Kilkelly <shane@kilkelly.me> Date: Tue, 20 Jun 2017 08:25:50 +0100 Subject: [PATCH 262/754] Mock out logger in tests --- services/clsi/test/unit/coffee/ResourceWriterTests.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee index 96140c9883..fd1ae30954 100644 --- a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee +++ b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee @@ -12,6 +12,7 @@ describe "ResourceWriter", -> "./UrlCache" : @UrlCache = {} "mkdirp" : @mkdirp = sinon.stub().callsArg(1) "./OutputFileFinder": @OutputFileFinder = {} + "logger-sharelatex": {log: sinon.stub(), err: sinon.stub()} "./Metrics": @Metrics = Timer: class Timer done: sinon.stub() From 4b14de05c9bedcad7a9bc68eaee4a909ba503f32 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly <shane@kilkelly.me> Date: Tue, 20 Jun 2017 09:18:15 +0100 Subject: [PATCH 263/754] Killing an already stopped project is not an error Log a warning instead and continue. --- services/clsi/app/coffee/LatexRunner.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/LatexRunner.coffee b/services/clsi/app/coffee/LatexRunner.coffee index efd89dfc4b..6a5a4f6ae2 100644 --- a/services/clsi/app/coffee/LatexRunner.coffee +++ b/services/clsi/app/coffee/LatexRunner.coffee @@ -58,7 +58,8 @@ module.exports = LatexRunner = id = "#{project_id}" logger.log {id:id}, "killing running compile" if not ProcessTable[id]? - return callback new Error("no such project to kill") + logger.warn {id}, "no such project to kill" + return callback(null) else CommandRunner.kill ProcessTable[id], callback From a55debb79fcb13da4c66d172c99723df748ce5c1 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly <shane@kilkelly.me> Date: Fri, 23 Jun 2017 14:46:40 +0100 Subject: [PATCH 264/754] Send a 404 if the project files have gone away when running synctex. This is semantically nicer than the 500 response which used to be produced in these circumstances. --- services/clsi/app.coffee | 9 +++++++-- services/clsi/app/coffee/CompileManager.coffee | 5 +++-- services/clsi/app/coffee/Errors.coffee | 10 ++++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 services/clsi/app/coffee/Errors.coffee diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index c649842671..b99b277464 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -7,6 +7,7 @@ if Settings.sentry?.dsn? smokeTest = require "smoke-test-sharelatex" ContentTypeMapper = require "./app/js/ContentTypeMapper" +Errors = require './app/js/Errors' Path = require "path" fs = require "fs" @@ -152,8 +153,12 @@ app.get "/heapdump", (req, res)-> res.send filename app.use (error, req, res, next) -> - logger.error err: error, "server error" - res.sendStatus(error?.statusCode || 500) + if error instanceof Errors.NotFoundError + logger.warn {err: error, url: req.url}, "not found error" + return res.sendStatus(404) + else + logger.error {err: error, url: req.url}, "server error" + res.sendStatus(error?.statusCode || 500) app.listen port = (Settings.internal?.clsi?.port or 3013), host = (Settings.internal?.clsi?.host or "localhost"), (error) -> logger.info "CLSI starting up, listening on #{host}:#{port}" diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 27aa394e72..ae40bf7929 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -13,6 +13,7 @@ fs = require("fs") fse = require "fs-extra" os = require("os") async = require "async" +Errors = require './Errors' commandRunner = Settings.clsi?.commandRunner or "./CommandRunner" logger.info commandRunner:commandRunner, "selecting command runner for clsi" @@ -206,11 +207,11 @@ module.exports = CompileManager = synctexFile = Path.join(synctexDir, "output.synctex.gz") fs.stat synctexDir, (error, stats) -> if error?.code is 'ENOENT' - return callback(new Error("called synctex with no output directory")) + return callback(new Errors.NotFoundError("called synctex with no output directory")) return callback(error) if error? fs.stat synctexFile, (error, stats) -> if error?.code is 'ENOENT' - return callback(new Error("called synctex with no output file")) + return callback(new Errors.NotFoundError("called synctex with no output file")) return callback(error) if error? return callback(new Error("not a file")) if not stats?.isFile() callback() diff --git a/services/clsi/app/coffee/Errors.coffee b/services/clsi/app/coffee/Errors.coffee new file mode 100644 index 0000000000..4abea0bbbb --- /dev/null +++ b/services/clsi/app/coffee/Errors.coffee @@ -0,0 +1,10 @@ +NotFoundError = (message) -> + error = new Error(message) + error.name = "NotFoundError" + error.__proto__ = NotFoundError.prototype + return error +NotFoundError.prototype.__proto__ = Error.prototype + + +module.exports = Errors = + NotFoundError: NotFoundError From 0c7a89dfa534ce43600e0350896bc9b39c0740a5 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Wed, 12 Jul 2017 16:58:48 +0100 Subject: [PATCH 265/754] update acceptance tests settings to 2017 image --- services/clsi/test/acceptance/scripts/settings.test.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/test/acceptance/scripts/settings.test.coffee b/services/clsi/test/acceptance/scripts/settings.test.coffee index dd6574d43c..cf84c6baba 100644 --- a/services/clsi/test/acceptance/scripts/settings.test.coffee +++ b/services/clsi/test/acceptance/scripts/settings.test.coffee @@ -23,9 +23,9 @@ module.exports = commandRunner: "docker-runner-sharelatex" latexmkCommandPrefix: ["/usr/bin/time", "-v"] # on Linux docker: - image: "texlive-full:2016.1-opt" + image: "texlive-full:2017.1-opt" env: - PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2016/bin/x86_64-linux/" + PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2017/bin/x86_64-linux/" HOME: "/tmp" modem: socketPath: false From 4a4f3bb24c4363dd5cf39a6a2f76618132e6377b Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 13 Jul 2017 13:15:51 +0100 Subject: [PATCH 266/754] update acceptance test images for texlive 2017 --- .../fixtures/examples/asymptote/output.pdf | Bin 122452 -> 123688 bytes .../fixtures/examples/knitr/output.pdf | Bin 43236 -> 44072 bytes .../fixtures/examples/knitr_utf8/output.pdf | Bin 75114 -> 75823 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/services/clsi/test/acceptance/fixtures/examples/asymptote/output.pdf b/services/clsi/test/acceptance/fixtures/examples/asymptote/output.pdf index 942b9154967cd1f79d9ad467cc431d0244d29b45..e8fdde84654c01a58bff2778dc94a1f894a1a3e7 100644 GIT binary patch delta 37016 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 delta 35867 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 diff --git a/services/clsi/test/acceptance/fixtures/examples/knitr/output.pdf b/services/clsi/test/acceptance/fixtures/examples/knitr/output.pdf index a1f93799e58349967efead8c4583661423ae3f23..7a839f7b1340dc097fead32bc2e3ad962da3d257 100644 GIT binary patch delta 3366 zcmbtW3piBk8W!a;NxCtj{0f`Y%&fVrnX%n+OSz0&QYdp-BgM=#CP_4QLOa@ubd+=< zj@pSfQE98RDdiH~WRfJIn=Wj1>~j9eCFiJpo_+SrGtaaBHQ##I|9=1bec$_}K{2mM zQEKFF@3;sd&~Qc<`?EgbD1ZcnoOL)03qW)U7GP{~a3sb92wVXwl|&~4q7(1~bOse5 zWCjQTL>8a}q645(XjBRyy2(Sm;fl^gfRz;v6Y%74;9DQ*xBmFM_i@xQOuRRXFhKP6 z#Q2a0CJ2(O1t{FCV{l076N07`5bZIpkcYXm#i3z9PTfuzvN2Q;2B?7OF2;DlT!~N& z2}4GJxB~J=02%%u`=tUN76ETi0GT<o<_qCJB;W~%o<gAn(1xzPU>X4hW)H%Je@+j1 zl;LO&h)eB(p9P)DVbG`yK8H>xk?1Iefs)t^4ilx3IV2hf<y%>BQ9hT;=22-BGL^wZ z$uus)<ufQe22UPHVOj;qQy(S~W9(2|#6d2H_b4ck6Ub0AOmB*rm_2QWOktilW+G+k zZq0NJL!OkNpq8(1V{1OqsjGk!$VpnI=6Us!lY^qd+c*_J`4J97{jb9uW%SoBM*07> z$NnrDCr|r`j@{WoSQsD=pURLH?AQ`^h%o50M5hgD6#eo@>LbETs0uGYK}Q8c`>f|L zNev8q6c`w&6{xTcd#Nr`z=~`Tb&(CCWT&B|uM|6T^1YXPvH0ib2Qpgl3Z>a9_r~Xu z{^f;GZ~_S?kc0uokX~G|mAqiKtx&El68xf2d~_yRE-|Q57&*$v6AKe=6mua%WLRrM z4CSoHRG=JpI3dIAh6@3zXslYATYOFnWF*a&zF8`~)5?fp@+QLs!lVOQEwk4;Q}Ahe zcu}Z#xGZWbYo1r(vIO6l*sRH>bLVc3ib`G*9HB6!$1HQ8G<0M13~=JwzN0NM8y0Wo z{!nd}I5AXZ)6Y{s3O42G+WUVzDa~IqCq7_BYpH&LFv;}k^aWRZ?q(WW8gEzeY2c<S z?95j34q>oED)#E)Z$+-f-OM<D+UjKS?8|9BU8;|g1rae9<AiblJgIwiLxXEqXIoK2 zPxCWBZ2Ku?CFL`3Czu?0yW@4odRkS3L#2VDgTs@+F1A;+`4+FvY2}8^iBz(oR8MwI z?fa<@`F=VJqT|FQy_g*RS`u!{3)Zuy{+4+GSCfDFxNDK$;$?^G5A<f~>G{SV$SUqN zY_v!h;}Z*PuK1$~@m2U)Kkn3x`gF9iQ)_>vvDK87DP_MJRyv2Kr>eL1%W9=;^2hqf z7nLbB7U5B8vca?I+Rh%@YTIsP8E%yF7cX9Ru0894!Ht197OQQ!<%ic1t9r`%ZsvI0 z8*Dnol3m&QW`e)6rP%}5+(uuI+9%he({x()5A+^Q+f`TQ(y^^x_F@qG{auBJ!3-)v z8QaFnsIBPKdAn{<aY}LHn{h?U{Br&yeUCPxZ3Ie(!W>zV0R=(j4gYhaln;cFo=HVu zJrqe5$(nP<;UjXDjuF^ca0(Ol(IGf8KuNUGq8bYhn;iob&7HAM_bcG?+8qNv3LxJ( z8Fum!a3%_i;43>vNGJsXMSX-lre8rP|0nt=0z{|K;B*^7r!i@xM8<9%Wx#P*^v?Iy z?5}{2nW*x@8-;`5bo%HDjuR3J>-%eaM;LH$6tN_>1p4^2j~{a^&N?qi=m-*Ez_C1B z@o+f97di%u!z6H+p<~Pb;7gwXmW(try0zeJaz<`dYxq2aV~Qtm>Q(Rex$D~^p~a1b zhH-+6Tkmba1$*Z1ysYavqqXU3|AThFH5sNyE>TH4cTK1>D%9LwQJ{LP?UHGpstO)Y z5a}gV5ho;hgiKo`O;bEoc&90AesCeJt8(ol1Gi(-M1QE|+PH6Dps~%szMG}1YZKXB z9Mc$B6KB)bbpQG!_xsHbZK0iK$#&YM6gvF}J*@lnZ(5?br*2cP9naJzT{@b=HLDA) zw)@?!M5C;8P3hF1bR6`T?$egFGn9@;8-9=K^Q)ux@CjWhU4Bx3Hr~<RjeoDQXG@~` zdw%(G``w98a`BylSy8@IUd4Xu7bYyZ7F}58QpX4_N^lKVS(Olw&$oWq$}H<L@5rso zZ@#?^&AW4;MofE`{O-o9`$pu$hX~KLPSn<}HuF1aZ2i3FWnW8H&#B9Gu4Wx=kuNUP zm1ow8)b|WjyZ$6yD(%@=n7(;ta>%Q~6-P*Z`t>r)*koFC>zO+q?knS$YtfBWPdnZ` zqi>!43GL9W&xlzeY~>dA;%6%>vJU^UA`vLgX|##;d0=3xlRo`B>SVmhQ_Za<ReAGs ze|~eK;Go}T?dDqd>wOV}8l4)}*AI8SX{b53RjavpowK`ns;9D8TCH{ae!Z%7_Op(9 zD_aBilUFUWEp6@9b9UtQYR8)tSv{MieqHf)ZGx7WX8f*7lky%JkGeXFvocJjBWQZ6 zE@!E2F3-HItYqb(q+CNIo$95F=awsMh-*04Em|l%M*ndM(`n87l9EQI7Ak3KvyHi% zzSpU2dZ*)->fb6J_f|=JuDq`+F?%DJ#!6vtGPe~a9Bognc$k@eI5IAOR*-mQ_wEQM z@3e3OYKpV1orku0!FsmF`DM;aL^HjfuKriey*s4ly|);(Yusb$d32v!#9Mr6?PIn5 zf#r?IjrvjNMqQVd`5(AN63bsx2)&P|<mdc@)<<YP_)OeAkaH@+U+SfP=)r3B_6v*R z>6Hru;(gX31r2ZOKZa*kUO8Nn-X!C&gEUs$=v;KpHE4fps@l^t<w(GR_8PYvA96Z^ ztUIi}I~QH(_25D!qv&q_oc((?Ki7ZrSn$f}Vn!M%zoo`LOij1+{NRAyACtU`O?Qni zRNpU6hG++!nIu>!uxCp!U}A4hCLweZ9U&nU3W_jRnL-09kAR-j#2fR4OIIQ9o3W`T z+13jgz+WB;T!O<fAcyle1?xp@$=WcC4Z<;T7<?N*31kAr6r>hp;jI}E)mzLKiJ-eD zI<iB;#+rpO4Uza2?+mTfa7X#See;kXhW`0)I9~3bSzZN2<ErJ4)m_w+taTJr%_m$L c@4x=bKtjXJ7E2<;7#~MQP@IvG1IrQjZ)V)PYybcN delta 2743 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 AHUIzs diff --git a/services/clsi/test/acceptance/fixtures/examples/knitr_utf8/output.pdf b/services/clsi/test/acceptance/fixtures/examples/knitr_utf8/output.pdf index 015941cc2019cf51de6b6405eb5cea68829d06ef..2d63d1af08b950f633c412658d756bb3c03cc078 100644 GIT binary patch literal 75823 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 literal 75114 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 From fd5b557ddefd093bcb73b8ddaa55c4a75d6ad482 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 18 Jul 2017 11:29:59 +0100 Subject: [PATCH 267/754] fix exception for empty content in TikzManager --- services/clsi/app/coffee/TikzManager.coffee | 4 ++-- services/clsi/test/unit/coffee/TikzManager.coffee | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/TikzManager.coffee b/services/clsi/app/coffee/TikzManager.coffee index cfa13321a6..5c08f205fd 100644 --- a/services/clsi/app/coffee/TikzManager.coffee +++ b/services/clsi/app/coffee/TikzManager.coffee @@ -22,8 +22,8 @@ module.exports = TikzManager = _includesTikz: (resource) -> # check if we are using tikz externalize - content = resource.content.slice(0,65536) - if content.indexOf("\\tikzexternalize") >= 0 + content = resource.content?.slice(0,65536) + if content?.indexOf("\\tikzexternalize") >= 0 return true else return false diff --git a/services/clsi/test/unit/coffee/TikzManager.coffee b/services/clsi/test/unit/coffee/TikzManager.coffee index 8174b4a2e2..816b3b10cf 100644 --- a/services/clsi/test/unit/coffee/TikzManager.coffee +++ b/services/clsi/test/unit/coffee/TikzManager.coffee @@ -30,6 +30,12 @@ describe 'TikzManager', -> { path: 'output.tex' } ]).should.equal false + it "should return false if the file has no content", -> + @TikzManager.needsOutputFile("main.tex", [ + { path: 'foo.tex' }, + { path: 'main.tex' } + ]).should.equal false + describe "injectOutputFile", -> beforeEach -> @rootDir = "/mock" From d43357e8c817bd38b4d78c301cbfa5735c9a3782 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 18 Jul 2017 11:30:22 +0100 Subject: [PATCH 268/754] stub out unwanted dependency in unit tests --- services/clsi/test/unit/coffee/CompileManagerTests.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 68629a1c43..de33166406 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -18,6 +18,7 @@ describe "CompileManager", -> "child_process": @child_process = {} "./CommandRunner": @CommandRunner = {} "./DraftModeManager": @DraftModeManager = {} + "./TikzManager": @TikzManager = {} "fs": @fs = {} @callback = sinon.stub() @@ -55,6 +56,7 @@ describe "CompileManager", -> @OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, @output_files) @OutputCacheManager.saveOutputFiles = sinon.stub().callsArgWith(2, null, @build_files) @DraftModeManager.injectDraftMode = sinon.stub().callsArg(1) + @TikzManager.needsOutputFile = sinon.stub().returns(false) describe "normally", -> beforeEach -> From 19c4d020b5f9ae6b0945d15286136fb74d00c4e7 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Sun, 23 Jul 2017 22:42:07 +0100 Subject: [PATCH 269/754] change --- services/clsi/app.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index b99b277464..0e056fc176 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -1,3 +1,4 @@ + CompileController = require "./app/js/CompileController" Settings = require "settings-sharelatex" logger = require "logger-sharelatex" From 2aeec8a12f74a7de0622aede96c0ddb42f8d9723 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Sun, 23 Jul 2017 22:45:04 +0100 Subject: [PATCH 270/754] Revert "change" This reverts commit 104ce81ebdf41d88acd7fb6f2abf99fbc4eb91df. --- services/clsi/app.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 0e056fc176..b99b277464 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -1,4 +1,3 @@ - CompileController = require "./app/js/CompileController" Settings = require "settings-sharelatex" logger = require "logger-sharelatex" From d382f2929f48d606b6f3cc304bc343ed2a3bffb6 Mon Sep 17 00:00:00 2001 From: Hayden Faulds <fauldsh@gmail.com> Date: Thu, 27 Jul 2017 14:02:24 +0100 Subject: [PATCH 271/754] keep compiles directory --- services/clsi/compiles/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 services/clsi/compiles/.gitkeep diff --git a/services/clsi/compiles/.gitkeep b/services/clsi/compiles/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 From 931abb7c813fcb2998479026c11459736683b847 Mon Sep 17 00:00:00 2001 From: Hayden Faulds <fauldsh@gmail.com> Date: Thu, 27 Jul 2017 15:54:20 +0100 Subject: [PATCH 272/754] keep cache directory --- services/clsi/cache/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 services/clsi/cache/.gitkeep diff --git a/services/clsi/cache/.gitkeep b/services/clsi/cache/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 From da86a094a8be4d4894ec31d2286e3c090b51f7ed Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 1 Aug 2017 14:35:55 +0100 Subject: [PATCH 273/754] write files incrementally --- .../clsi/app/coffee/CompileController.coffee | 5 ++- .../clsi/app/coffee/CompileManager.coffee | 2 +- services/clsi/app/coffee/RequestParser.coffee | 6 ++++ .../clsi/app/coffee/ResourceWriter.coffee | 34 ++++++++++++++++++- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 250f9b8e7f..aefa70dd1b 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -15,7 +15,10 @@ module.exports = CompileController = ProjectPersistenceManager.markProjectAsJustAccessed request.project_id, (error) -> return next(error) if error? CompileManager.doCompile request, (error, outputFiles = []) -> - if error?.terminated + if error?.message is "invalid state" + code = 409 # Http 409 Conflict + status = "retry" + else if error?.terminated status = "terminated" else if error?.validate status = "validation-#{error.validate}" diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index ae40bf7929..8673b52de5 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -31,7 +31,7 @@ module.exports = CompileManager = timer = new Metrics.Timer("write-to-disk") logger.log project_id: request.project_id, user_id: request.user_id, "syncing resources to disk" - ResourceWriter.syncResourcesToDisk request.project_id, request.resources, compileDir, (error) -> + ResourceWriter.syncResourcesToDisk request, compileDir, (error) -> if error? logger.err err:error, project_id: request.project_id, user_id: request.user_id, "error writing resources to disk" return callback(error) diff --git a/services/clsi/app/coffee/RequestParser.coffee b/services/clsi/app/coffee/RequestParser.coffee index 8fc4ecf370..fe22982fda 100644 --- a/services/clsi/app/coffee/RequestParser.coffee +++ b/services/clsi/app/coffee/RequestParser.coffee @@ -31,6 +31,12 @@ module.exports = RequestParser = response.check = @_parseAttribute "check", compile.options.check, type: "string" + response.incremental = @_parseAttribute "incremental", + compile.options.incremental, + type: "string" + response.state = @_parseAttribute "state", + compile.options.state, + type: "string" if response.timeout > RequestParser.MAX_TIMEOUT response.timeout = RequestParser.MAX_TIMEOUT diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index e2a0e1f842..a4ae42517e 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -11,7 +11,39 @@ settings = require("settings-sharelatex") parallelFileDownloads = settings.parallelFileDownloads or 1 module.exports = ResourceWriter = - syncResourcesToDisk: (project_id, resources, basePath, callback = (error) ->) -> + + syncResourcesToDisk: (request, basePath, callback = (error) ->) -> + if request.incremental? + ResourceWriter.checkState request.incremental, basePath, (error, ok) -> + logger.log state: request.state, result:ok, "checked state on incremental request" + return callback new Error("invalid state") if not ok + ResourceWriter.saveIncrementalResourcesToDisk request.project_id, request.resources, basePath, callback + else + @saveAllResourcesToDisk request.project_id, request.resources, basePath, (error) -> + return callback(error) if error? + ResourceWriter.storeState request.state, basePath, callback + + storeState: (state, basePath, callback) -> + logger.log state:state, basePath:basePath, "writing state" + fs.writeFile Path.join(basePath, ".resource-state"), state, {encoding: 'ascii'}, callback + + checkState: (state, basePath, callback) -> + fs.readFile Path.join(basePath, ".resource-state"), {encoding:'ascii'}, (err, oldState) -> + logger.log state:state, oldState: oldState, basePath:basePath, err:err, "checking state" + if state is oldState + return callback(null, true) + else + return callback(null, false) + + saveIncrementalResourcesToDisk: (project_id, resources, basePath, callback = (error) ->) -> + @_createDirectory basePath, (error) => + return callback(error) if error? + jobs = for resource in resources + do (resource) => + (callback) => @_writeResourceToDisk(project_id, resource, basePath, callback) + async.parallelLimit jobs, parallelFileDownloads, callback + + saveAllResourcesToDisk: (project_id, resources, basePath, callback = (error) ->) -> @_createDirectory basePath, (error) => return callback(error) if error? @_removeExtraneousFiles resources, basePath, (error) => From d4aad06c1f56c609ff1d2c8ee0f226d03c96bb8f Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 3 Aug 2017 11:51:58 +0100 Subject: [PATCH 274/754] use syncType and syncState for clsi state options --- services/clsi/app/coffee/RequestParser.coffee | 8 ++++---- .../clsi/app/coffee/ResourceWriter.coffee | 20 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/services/clsi/app/coffee/RequestParser.coffee b/services/clsi/app/coffee/RequestParser.coffee index fe22982fda..d2e67bca76 100644 --- a/services/clsi/app/coffee/RequestParser.coffee +++ b/services/clsi/app/coffee/RequestParser.coffee @@ -31,11 +31,11 @@ module.exports = RequestParser = response.check = @_parseAttribute "check", compile.options.check, type: "string" - response.incremental = @_parseAttribute "incremental", - compile.options.incremental, + response.syncType = @_parseAttribute "syncType", + compile.options.syncType, type: "string" - response.state = @_parseAttribute "state", - compile.options.state, + response.syncState = @_parseAttribute "syncState", + compile.options.syncState, type: "string" if response.timeout > RequestParser.MAX_TIMEOUT diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index a4ae42517e..fca2c43682 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -13,23 +13,23 @@ parallelFileDownloads = settings.parallelFileDownloads or 1 module.exports = ResourceWriter = syncResourcesToDisk: (request, basePath, callback = (error) ->) -> - if request.incremental? - ResourceWriter.checkState request.incremental, basePath, (error, ok) -> - logger.log state: request.state, result:ok, "checked state on incremental request" + if request.syncType? is "incremental" + ResourceWriter.checkSyncState request.syncState, basePath, (error, ok) -> + logger.log syncState: request.syncState, result:ok, "checked state on incremental request" return callback new Error("invalid state") if not ok ResourceWriter.saveIncrementalResourcesToDisk request.project_id, request.resources, basePath, callback else @saveAllResourcesToDisk request.project_id, request.resources, basePath, (error) -> return callback(error) if error? - ResourceWriter.storeState request.state, basePath, callback + ResourceWriter.storeSyncState request.syncState, basePath, callback - storeState: (state, basePath, callback) -> - logger.log state:state, basePath:basePath, "writing state" - fs.writeFile Path.join(basePath, ".resource-state"), state, {encoding: 'ascii'}, callback + storeSyncState: (state, basePath, callback) -> + logger.log state:state, basePath:basePath, "writing sync state" + fs.writeFile Path.join(basePath, ".resource-sync-state"), state, {encoding: 'ascii'}, callback - checkState: (state, basePath, callback) -> - fs.readFile Path.join(basePath, ".resource-state"), {encoding:'ascii'}, (err, oldState) -> - logger.log state:state, oldState: oldState, basePath:basePath, err:err, "checking state" + checkSyncState: (state, basePath, callback) -> + fs.readFile Path.join(basePath, ".resource-sync-state"), {encoding:'ascii'}, (err, oldState) -> + logger.log state:state, oldState: oldState, basePath:basePath, err:err, "checking sync state" if state is oldState return callback(null, true) else From 44ae01170398214a071cc8ba5e7b1da4ab931a6e Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 3 Aug 2017 15:56:59 +0100 Subject: [PATCH 275/754] added files out of sync error object --- services/clsi/app/coffee/CompileController.coffee | 3 ++- services/clsi/app/coffee/Errors.coffee | 7 +++++++ services/clsi/app/coffee/ResourceWriter.coffee | 3 ++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index aefa70dd1b..6f93bfea74 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -4,6 +4,7 @@ Settings = require "settings-sharelatex" Metrics = require "./Metrics" ProjectPersistenceManager = require "./ProjectPersistenceManager" logger = require "logger-sharelatex" +Errors = require "./Errors" module.exports = CompileController = compile: (req, res, next = (error) ->) -> @@ -15,7 +16,7 @@ module.exports = CompileController = ProjectPersistenceManager.markProjectAsJustAccessed request.project_id, (error) -> return next(error) if error? CompileManager.doCompile request, (error, outputFiles = []) -> - if error?.message is "invalid state" + if error instanceOf Errors.FilesOutOfSyncError code = 409 # Http 409 Conflict status = "retry" else if error?.terminated diff --git a/services/clsi/app/coffee/Errors.coffee b/services/clsi/app/coffee/Errors.coffee index 4abea0bbbb..2e3ae7597d 100644 --- a/services/clsi/app/coffee/Errors.coffee +++ b/services/clsi/app/coffee/Errors.coffee @@ -5,6 +5,13 @@ NotFoundError = (message) -> return error NotFoundError.prototype.__proto__ = Error.prototype +FilesOutOfSyncError = (message) -> + error = new Error(message) + error.name = "FilesOutOfSyncError" + error.__proto__ = FilesOutOfSyncError.prototype + return error +FilesOutOfSyncError.prototype.__proto__ = Error.prototype module.exports = Errors = NotFoundError: NotFoundError + FilesOutOfSyncError: FilesOutOfSyncError diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index fca2c43682..725d069966 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -5,6 +5,7 @@ async = require "async" mkdirp = require "mkdirp" OutputFileFinder = require "./OutputFileFinder" Metrics = require "./Metrics" +Errors = require "./Errors" logger = require "logger-sharelatex" settings = require("settings-sharelatex") @@ -16,7 +17,7 @@ module.exports = ResourceWriter = if request.syncType? is "incremental" ResourceWriter.checkSyncState request.syncState, basePath, (error, ok) -> logger.log syncState: request.syncState, result:ok, "checked state on incremental request" - return callback new Error("invalid state") if not ok + return callback new Errors.FilesOutOfSyncError("invalid state for incremental update") if not ok ResourceWriter.saveIncrementalResourcesToDisk request.project_id, request.resources, basePath, callback else @saveAllResourcesToDisk request.project_id, request.resources, basePath, (error) -> From fe5ba5b619b5a77af17b17ca68602f286e99a8d2 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Mon, 7 Aug 2017 10:19:56 +0100 Subject: [PATCH 276/754] restrict syncType values to full/incremental --- services/clsi/app/coffee/RequestParser.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/app/coffee/RequestParser.coffee b/services/clsi/app/coffee/RequestParser.coffee index d2e67bca76..8b8de76e62 100644 --- a/services/clsi/app/coffee/RequestParser.coffee +++ b/services/clsi/app/coffee/RequestParser.coffee @@ -33,6 +33,7 @@ module.exports = RequestParser = type: "string" response.syncType = @_parseAttribute "syncType", compile.options.syncType, + validValues: ["full", "incremental"] type: "string" response.syncState = @_parseAttribute "syncState", compile.options.syncState, From 3a930a636e49defc7a7f550553037ff9602ad060 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Mon, 7 Aug 2017 14:26:13 +0100 Subject: [PATCH 277/754] fix incremental request --- services/clsi/app/coffee/CompileController.coffee | 2 +- services/clsi/app/coffee/CompileManager.coffee | 7 +++++-- services/clsi/app/coffee/ResourceWriter.coffee | 15 ++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 6f93bfea74..f4dcb0ec66 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -16,7 +16,7 @@ module.exports = CompileController = ProjectPersistenceManager.markProjectAsJustAccessed request.project_id, (error) -> return next(error) if error? CompileManager.doCompile request, (error, outputFiles = []) -> - if error instanceOf Errors.FilesOutOfSyncError + if error instanceof Errors.FilesOutOfSyncError code = 409 # Http 409 Conflict status = "retry" else if error?.terminated diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 8673b52de5..2b563301b5 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -32,9 +32,12 @@ module.exports = CompileManager = timer = new Metrics.Timer("write-to-disk") logger.log project_id: request.project_id, user_id: request.user_id, "syncing resources to disk" ResourceWriter.syncResourcesToDisk request, compileDir, (error) -> - if error? + if error? and error instanceof Errors.FilesOutOfSyncError + logger.warn project_id: request.project_id, user_id: request.user_id, "files out of sync, please retry" + return callback(error) + else if error? logger.err err:error, project_id: request.project_id, user_id: request.user_id, "error writing resources to disk" - return callback(error) + return callback(error) logger.log project_id: request.project_id, user_id: request.user_id, time_taken: Date.now() - timer.start, "written files to disk" timer.done() diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index 725d069966..29e0e5762e 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -14,10 +14,10 @@ parallelFileDownloads = settings.parallelFileDownloads or 1 module.exports = ResourceWriter = syncResourcesToDisk: (request, basePath, callback = (error) ->) -> - if request.syncType? is "incremental" - ResourceWriter.checkSyncState request.syncState, basePath, (error, ok) -> - logger.log syncState: request.syncState, result:ok, "checked state on incremental request" - return callback new Errors.FilesOutOfSyncError("invalid state for incremental update") if not ok + if request.syncType is "incremental" + ResourceWriter.checkSyncState request.syncState, basePath, (error, syncStateOk) -> + logger.log syncState: request.syncState, result:syncStateOk, "checked state on incremental request" + return callback new Errors.FilesOutOfSyncError("invalid state for incremental update") if not syncStateOk ResourceWriter.saveIncrementalResourcesToDisk request.project_id, request.resources, basePath, callback else @saveAllResourcesToDisk request.project_id, request.resources, basePath, (error) -> @@ -30,11 +30,8 @@ module.exports = ResourceWriter = checkSyncState: (state, basePath, callback) -> fs.readFile Path.join(basePath, ".resource-sync-state"), {encoding:'ascii'}, (err, oldState) -> - logger.log state:state, oldState: oldState, basePath:basePath, err:err, "checking sync state" - if state is oldState - return callback(null, true) - else - return callback(null, false) + # ignore errors, return true if state matches, false otherwise (including errors) + return callback(null, if state is oldState then true else false) saveIncrementalResourcesToDisk: (project_id, resources, basePath, callback = (error) ->) -> @_createDirectory basePath, (error) => From 567a89350b506760a0a847f4f515d5b9502e480b Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Mon, 7 Aug 2017 15:00:16 +0100 Subject: [PATCH 278/754] fix broken unit tests --- services/clsi/test/unit/coffee/CompileManagerTests.coffee | 4 ++-- services/clsi/test/unit/coffee/ResourceWriterTests.coffee | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index de33166406..25109b621f 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -51,7 +51,7 @@ describe "CompileManager", -> @env = {} @Settings.compileDir = "compiles" @compileDir = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}" - @ResourceWriter.syncResourcesToDisk = sinon.stub().callsArg(3) + @ResourceWriter.syncResourcesToDisk = sinon.stub().callsArg(2) @LatexRunner.runLatex = sinon.stub().callsArg(2) @OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, @output_files) @OutputCacheManager.saveOutputFiles = sinon.stub().callsArgWith(2, null, @build_files) @@ -64,7 +64,7 @@ describe "CompileManager", -> it "should write the resources to disk", -> @ResourceWriter.syncResourcesToDisk - .calledWith(@project_id, @resources, @compileDir) + .calledWith(@request, @compileDir) .should.equal true it "should run LaTeX", -> diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee index fd1ae30954..efaf36a3fa 100644 --- a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee +++ b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee @@ -29,7 +29,9 @@ describe "ResourceWriter", -> ] @ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3) @ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2) - @ResourceWriter.syncResourcesToDisk(@project_id, @resources, @basePath, @callback) + @ResourceWriter.checkSyncState = sinon.stub().callsArg(2) + @ResourceWriter.storeSyncState = sinon.stub().callsArg(2) + @ResourceWriter.syncResourcesToDisk({project_id: @project_id, resources: @resources}, @basePath, @callback) it "should remove old files", -> @ResourceWriter._removeExtraneousFiles From c3511e91ef5ad9abb61bc34ada1ebb9342f11e8b Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 7 Aug 2017 15:29:18 +0100 Subject: [PATCH 279/754] Revert "Keep compiles and cache directories" --- services/clsi/cache/.gitkeep | 0 services/clsi/compiles/.gitkeep | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 services/clsi/cache/.gitkeep delete mode 100644 services/clsi/compiles/.gitkeep diff --git a/services/clsi/cache/.gitkeep b/services/clsi/cache/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/services/clsi/compiles/.gitkeep b/services/clsi/compiles/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 From 1e34f6371e79ba8c441b9259a60b9a8bb4baffa6 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 7 Aug 2017 16:21:37 +0100 Subject: [PATCH 280/754] use grunt to make compiles and cache dirs --- services/clsi/Gruntfile.coffee | 8 +++++++- services/clsi/package.json | 17 +++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/services/clsi/Gruntfile.coffee b/services/clsi/Gruntfile.coffee index 09d37346cf..a30f141ed5 100644 --- a/services/clsi/Gruntfile.coffee +++ b/services/clsi/Gruntfile.coffee @@ -46,6 +46,11 @@ module.exports = (grunt) -> app: src: "app.js" + mkdir: + all: + options: + create: ["cache", "compiles"] + mochaTest: unit: options: @@ -70,6 +75,7 @@ module.exports = (grunt) -> grunt.loadNpmTasks 'grunt-shell' grunt.loadNpmTasks 'grunt-execute' grunt.loadNpmTasks 'grunt-bunyan' + grunt.loadNpmTasks 'grunt-mkdir' grunt.registerTask 'compile:bin', () -> callback = @async() @@ -93,6 +99,6 @@ module.exports = (grunt) -> grunt.registerTask 'install', 'compile:app' - grunt.registerTask 'default', ['run'] + grunt.registerTask 'default', ['mkdir', 'run'] diff --git a/services/clsi/package.json b/services/clsi/package.json index 73bb08af3d..56843026e7 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -9,23 +9,24 @@ "author": "James Allen <james@sharelatex.com>", "dependencies": { "async": "0.2.9", + "body-parser": "^1.2.0", + "express": "^4.2.0", + "fs-extra": "^0.16.3", + "grunt-mkdir": "^1.0.0", + "heapdump": "^0.3.5", + "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", "lynx": "0.0.11", + "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.5.0", "mkdirp": "0.3.5", "mysql": "2.6.2", "request": "^2.21.0", - "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", - "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", - "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.5.0", "sequelize": "^2.1.3", - "wrench": "~1.5.4", + "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", "sqlite3": "~3.1.8", - "express": "^4.2.0", - "body-parser": "^1.2.0", - "fs-extra": "^0.16.3", "underscore": "^1.8.2", "v8-profiler": "^5.2.4", - "heapdump": "^0.3.5" + "wrench": "~1.5.4" }, "devDependencies": { "mocha": "1.10.0", From 4ebc7e5e4a45dbb7f97bb52afe6a9fc3328414a4 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 8 Aug 2017 16:27:53 +0100 Subject: [PATCH 281/754] clean up the state file if no state passed in --- services/clsi/app/coffee/ResourceWriter.coffee | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index 29e0e5762e..ad14b917e4 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -25,11 +25,21 @@ module.exports = ResourceWriter = ResourceWriter.storeSyncState request.syncState, basePath, callback storeSyncState: (state, basePath, callback) -> - logger.log state:state, basePath:basePath, "writing sync state" - fs.writeFile Path.join(basePath, ".resource-sync-state"), state, {encoding: 'ascii'}, callback + stateFile = Path.join(basePath, ".resource-sync-state") + if not state? # remove the file if no state passed in + logger.log state:state, basePath:basePath, "clearing sync state" + fs.unlink stateFile, (err) -> + if err? and err.code isnt 'ENOENT' + return callback(err) + else + return callback() + else + logger.log state:state, basePath:basePath, "writing sync state" + fs.writeFile stateFile, state, {encoding: 'ascii'}, callback checkSyncState: (state, basePath, callback) -> - fs.readFile Path.join(basePath, ".resource-sync-state"), {encoding:'ascii'}, (err, oldState) -> + stateFile = Path.join(basePath, ".resource-sync-state") + fs.readFile stateFile, {encoding:'ascii'}, (err, oldState) -> # ignore errors, return true if state matches, false otherwise (including errors) return callback(null, if state is oldState then true else false) From 2950c01130e3bd6bc4373307affe371328c86287 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Wed, 9 Aug 2017 15:10:24 +0100 Subject: [PATCH 282/754] add comment about syncType/syncState --- services/clsi/app/coffee/RequestParser.coffee | 14 ++++++++++ .../clsi/app/coffee/ResourceWriter.coffee | 26 ++++++++++++++++--- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/services/clsi/app/coffee/RequestParser.coffee b/services/clsi/app/coffee/RequestParser.coffee index 8b8de76e62..596b52995c 100644 --- a/services/clsi/app/coffee/RequestParser.coffee +++ b/services/clsi/app/coffee/RequestParser.coffee @@ -31,10 +31,24 @@ module.exports = RequestParser = response.check = @_parseAttribute "check", compile.options.check, type: "string" + + # The syncType specifies whether the request contains all + # resources (full) or only those resources to be updated + # in-place (incremental). response.syncType = @_parseAttribute "syncType", compile.options.syncType, validValues: ["full", "incremental"] type: "string" + + # The syncState is an identifier passed in with the request + # which has the property that it changes when any resource is + # added, deleted, moved or renamed. + # + # on syncType full the syncState identifier is passed in and + # stored + # + # on syncType incremental the syncState identifier must match + # the stored value response.syncState = @_parseAttribute "syncState", compile.options.syncState, type: "string" diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index ad14b917e4..9a78671ee6 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -24,8 +24,23 @@ module.exports = ResourceWriter = return callback(error) if error? ResourceWriter.storeSyncState request.syncState, basePath, callback + # The sync state is an identifier which must match for an + # incremental update to be allowed. + # + # The initial value is passed in and stored on a full + # compile. + # + # Subsequent incremental compiles must come with the same value - if + # not they will be rejected with a 409 Conflict response. + # + # An incremental compile can only update existing files with new + # content. The sync state identifier must change if any docs or + # files are moved, added, deleted or renamed. + + SYNC_STATE_FILE: ".project-sync-state" + storeSyncState: (state, basePath, callback) -> - stateFile = Path.join(basePath, ".resource-sync-state") + stateFile = Path.join(basePath, @SYNC_STATE_FILE) if not state? # remove the file if no state passed in logger.log state:state, basePath:basePath, "clearing sync state" fs.unlink stateFile, (err) -> @@ -38,10 +53,13 @@ module.exports = ResourceWriter = fs.writeFile stateFile, state, {encoding: 'ascii'}, callback checkSyncState: (state, basePath, callback) -> - stateFile = Path.join(basePath, ".resource-sync-state") + stateFile = Path.join(basePath, @SYNC_STATE_FILE) fs.readFile stateFile, {encoding:'ascii'}, (err, oldState) -> - # ignore errors, return true if state matches, false otherwise (including errors) - return callback(null, if state is oldState then true else false) + if err? and err.code isnt 'ENOENT' + return callback(err) + else + # return true if state matches, false otherwise (including file not existing) + callback(null, if state is oldState then true else false) saveIncrementalResourcesToDisk: (project_id, resources, basePath, callback = (error) ->) -> @_createDirectory basePath, (error) => From c0ed71f65c0c94b361162d225ee0a87a2a1f5c61 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Wed, 9 Aug 2017 15:22:44 +0100 Subject: [PATCH 283/754] fix unit tests --- services/clsi/test/unit/coffee/ResourceWriterTests.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee index efaf36a3fa..d4dd9c16ee 100644 --- a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee +++ b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee @@ -7,7 +7,9 @@ path = require "path" describe "ResourceWriter", -> beforeEach -> @ResourceWriter = SandboxedModule.require modulePath, requires: - "fs": @fs = { mkdir: sinon.stub().callsArg(1) } + "fs": @fs = + mkdir: sinon.stub().callsArg(1) + unlink: sinon.stub().callsArg(1) "wrench": @wrench = {} "./UrlCache" : @UrlCache = {} "mkdirp" : @mkdirp = sinon.stub().callsArg(1) From 8415ea2f717e6f3d2247eeb724bd027ff1fcbcc5 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 17 Aug 2017 14:53:35 +0100 Subject: [PATCH 284/754] store the resource list in a file --- .../clsi/app/coffee/CompileManager.coffee | 13 ++++++---- .../app/coffee/ResourceListManager.coffee | 24 +++++++++++++++++++ .../clsi/app/coffee/ResourceWriter.coffee | 13 +++++++--- .../unit/coffee/CompileManagerTests.coffee | 2 +- .../unit/coffee/ResourceWriterTests.coffee | 3 +++ 5 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 services/clsi/app/coffee/ResourceListManager.coffee diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 2b563301b5..d6c79da0e3 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -31,7 +31,8 @@ module.exports = CompileManager = timer = new Metrics.Timer("write-to-disk") logger.log project_id: request.project_id, user_id: request.user_id, "syncing resources to disk" - ResourceWriter.syncResourcesToDisk request, compileDir, (error) -> + ResourceWriter.syncResourcesToDisk request, compileDir, (error, resourceList) -> + # NOTE: resourceList is insecure, it should only be used to exclude files from the output list if error? and error instanceof Errors.FilesOutOfSyncError logger.warn project_id: request.project_id, user_id: request.user_id, "files out of sync, please retry" return callback(error) @@ -40,15 +41,17 @@ module.exports = CompileManager = return callback(error) logger.log project_id: request.project_id, user_id: request.user_id, time_taken: Date.now() - timer.start, "written files to disk" timer.done() - + + # FIXME - for incremental compiles we don't want to inject this multiple times injectDraftModeIfRequired = (callback) -> if request.draft DraftModeManager.injectDraftMode Path.join(compileDir, request.rootResourcePath), callback else callback() + # FIXME - for incremental compiles we may need to update output.tex every time createTikzFileIfRequired = (callback) -> - if TikzManager.needsOutputFile(request.rootResourcePath, request.resources) + if TikzManager.needsOutputFile(request.rootResourcePath, resourceList) TikzManager.injectOutputFile compileDir, request.rootResourcePath, callback else callback() @@ -94,7 +97,7 @@ module.exports = CompileManager = error.validate = "fail" # compile was killed by user, was a validation, or a compile which failed validation if error?.terminated or error?.validate - OutputFileFinder.findOutputFiles request.resources, compileDir, (err, outputFiles) -> + OutputFileFinder.findOutputFiles resourceList, compileDir, (err, outputFiles) -> return callback(err) if err? callback(error, outputFiles) # return output files so user can check logs return @@ -114,7 +117,7 @@ module.exports = CompileManager = if stats?["latex-runs"] > 0 and timings?["cpu-time"] > 0 Metrics.timing("run-compile-cpu-time-per-pass", timings["cpu-time"] / stats["latex-runs"]) - OutputFileFinder.findOutputFiles request.resources, compileDir, (error, outputFiles) -> + OutputFileFinder.findOutputFiles resourceList, compileDir, (error, outputFiles) -> return callback(error) if error? OutputCacheManager.saveOutputFiles outputFiles, compileDir, (error, newOutputFiles) -> callback null, newOutputFiles diff --git a/services/clsi/app/coffee/ResourceListManager.coffee b/services/clsi/app/coffee/ResourceListManager.coffee new file mode 100644 index 0000000000..d68f5d1ad6 --- /dev/null +++ b/services/clsi/app/coffee/ResourceListManager.coffee @@ -0,0 +1,24 @@ +Path = require "path" +fs = require "fs" +mkdirp = require "mkdirp" +logger = require "logger-sharelatex" +settings = require("settings-sharelatex") + +module.exports = ResourceListManager = + + # This file is a list of the input files for the project, one per + # line, used to identify output files (i.e. files not on this list) + # when the incoming request is incremental. + RESOURCE_LIST_FILE: ".project-resource-list" + + saveResourceList: (resources, basePath, callback = (error) ->) -> + resourceListFile = Path.join(basePath, @RESOURCE_LIST_FILE) + resourceList = (resource.path for resource in resources) + fs.writeFile resourceListFile, resourceList.join("\n"), callback + + loadResourceList: (basePath, callback = (error) ->) -> + resourceListFile = Path.join(basePath, @RESOURCE_LIST_FILE) + fs.readFile resourceListFile, (err, resourceList) -> + return callback(err) if err? + resources = ({path: path} for path in resourceList?.toString()?.split("\n") or []) + callback(null, resources) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index 9a78671ee6..18b8122e1e 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -4,6 +4,7 @@ fs = require "fs" async = require "async" mkdirp = require "mkdirp" OutputFileFinder = require "./OutputFileFinder" +ResourceListManager = require "./ResourceListManager" Metrics = require "./Metrics" Errors = require "./Errors" logger = require "logger-sharelatex" @@ -13,16 +14,22 @@ parallelFileDownloads = settings.parallelFileDownloads or 1 module.exports = ResourceWriter = - syncResourcesToDisk: (request, basePath, callback = (error) ->) -> + syncResourcesToDisk: (request, basePath, callback = (error, resourceList) ->) -> if request.syncType is "incremental" ResourceWriter.checkSyncState request.syncState, basePath, (error, syncStateOk) -> logger.log syncState: request.syncState, result:syncStateOk, "checked state on incremental request" return callback new Errors.FilesOutOfSyncError("invalid state for incremental update") if not syncStateOk - ResourceWriter.saveIncrementalResourcesToDisk request.project_id, request.resources, basePath, callback + ResourceWriter.saveIncrementalResourcesToDisk request.project_id, request.resources, basePath, (error) -> + return callback(error) if error? + ResourceListManager.loadResourceList basePath, callback else @saveAllResourcesToDisk request.project_id, request.resources, basePath, (error) -> return callback(error) if error? - ResourceWriter.storeSyncState request.syncState, basePath, callback + ResourceWriter.storeSyncState request.syncState, basePath, (error) -> + return callback(error) if error? + ResourceListManager.saveResourceList request.resources, basePath, (error) => + return callback(error) if error? + callback(null, request.resources) # The sync state is an identifier which must match for an # incremental update to be allowed. diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 25109b621f..ff671b27c3 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -51,7 +51,7 @@ describe "CompileManager", -> @env = {} @Settings.compileDir = "compiles" @compileDir = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}" - @ResourceWriter.syncResourcesToDisk = sinon.stub().callsArg(2) + @ResourceWriter.syncResourcesToDisk = sinon.stub().callsArgWith(2, null, @resources) @LatexRunner.runLatex = sinon.stub().callsArg(2) @OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, @output_files) @OutputCacheManager.saveOutputFiles = sinon.stub().callsArgWith(2, null, @build_files) diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee index d4dd9c16ee..0e602502f1 100644 --- a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee +++ b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee @@ -10,6 +10,7 @@ describe "ResourceWriter", -> "fs": @fs = mkdir: sinon.stub().callsArg(1) unlink: sinon.stub().callsArg(1) + "./ResourceListManager": @ResourceListManager = {} "wrench": @wrench = {} "./UrlCache" : @UrlCache = {} "mkdirp" : @mkdirp = sinon.stub().callsArg(1) @@ -33,6 +34,8 @@ describe "ResourceWriter", -> @ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2) @ResourceWriter.checkSyncState = sinon.stub().callsArg(2) @ResourceWriter.storeSyncState = sinon.stub().callsArg(2) + @ResourceListManager.saveResourceList = sinon.stub().callsArg(2) + @ResourceListManager.loadResourceList = sinon.stub().callsArg(1) @ResourceWriter.syncResourcesToDisk({project_id: @project_id, resources: @resources}, @basePath, @callback) it "should remove old files", -> From b901884248bab8223c573fbf7123a36ca3770919 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 17 Aug 2017 15:03:37 +0100 Subject: [PATCH 285/754] avoid adding draft mode more than once --- services/clsi/app/coffee/CompileManager.coffee | 2 -- services/clsi/app/coffee/DraftModeManager.coffee | 5 ++++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index d6c79da0e3..56d04db737 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -42,14 +42,12 @@ module.exports = CompileManager = logger.log project_id: request.project_id, user_id: request.user_id, time_taken: Date.now() - timer.start, "written files to disk" timer.done() - # FIXME - for incremental compiles we don't want to inject this multiple times injectDraftModeIfRequired = (callback) -> if request.draft DraftModeManager.injectDraftMode Path.join(compileDir, request.rootResourcePath), callback else callback() - # FIXME - for incremental compiles we may need to update output.tex every time createTikzFileIfRequired = (callback) -> if TikzManager.needsOutputFile(request.rootResourcePath, resourceList) TikzManager.injectOutputFile compileDir, request.rootResourcePath, callback diff --git a/services/clsi/app/coffee/DraftModeManager.coffee b/services/clsi/app/coffee/DraftModeManager.coffee index 253cbffdd3..2f9e931c0c 100644 --- a/services/clsi/app/coffee/DraftModeManager.coffee +++ b/services/clsi/app/coffee/DraftModeManager.coffee @@ -5,6 +5,9 @@ module.exports = DraftModeManager = injectDraftMode: (filename, callback = (error) ->) -> fs.readFile filename, "utf8", (error, content) -> return callback(error) if error? + # avoid adding draft mode more than once + if content?.indexOf("\\documentclass\[draft") >= 0 + return callback() modified_content = DraftModeManager._injectDraftOption content logger.log { content: content.slice(0,1024), # \documentclass is normally v near the top @@ -18,4 +21,4 @@ module.exports = DraftModeManager = # With existing options (must be first, otherwise both are applied) .replace(/\\documentclass\[/g, "\\documentclass[draft,") # Without existing options - .replace(/\\documentclass\{/g, "\\documentclass[draft]{") \ No newline at end of file + .replace(/\\documentclass\{/g, "\\documentclass[draft]{") From c7a9487216586e61779f79a30371fb937ee5addc Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 17 Aug 2017 15:57:05 +0100 Subject: [PATCH 286/754] test syncType in RequestParser --- .../clsi/test/unit/coffee/RequestParserTests.coffee | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/services/clsi/test/unit/coffee/RequestParserTests.coffee b/services/clsi/test/unit/coffee/RequestParserTests.coffee index 1cd931bce6..0b420b362d 100644 --- a/services/clsi/test/unit/coffee/RequestParserTests.coffee +++ b/services/clsi/test/unit/coffee/RequestParserTests.coffee @@ -242,3 +242,13 @@ describe "RequestParser", -> it "should return an error", -> @callback.calledWith("relative path in root resource") .should.equal true + + describe "with an unknown syncType", -> + beforeEach -> + @validRequest.compile.options.syncType = "unexpected" + @RequestParser.parse @validRequest, @callback + @data = @callback.args[0][1] + + it "should return an error", -> + @callback.calledWith("syncType attribute should be one of: full, incremental") + .should.equal true From d3da4e1d3f9c637866bd2ee3955978aa604e230c Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 17 Aug 2017 16:59:37 +0100 Subject: [PATCH 287/754] ResourceWriter unit tests (wip) --- .../clsi/app/coffee/ResourceWriter.coffee | 11 +++- .../unit/coffee/ResourceWriterTests.coffee | 56 ++++++++++++++++++- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index 18b8122e1e..d0d19882d1 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -19,9 +19,14 @@ module.exports = ResourceWriter = ResourceWriter.checkSyncState request.syncState, basePath, (error, syncStateOk) -> logger.log syncState: request.syncState, result:syncStateOk, "checked state on incremental request" return callback new Errors.FilesOutOfSyncError("invalid state for incremental update") if not syncStateOk - ResourceWriter.saveIncrementalResourcesToDisk request.project_id, request.resources, basePath, (error) -> + ResourceListManager.loadResourceList basePath, (error, resourceList) -> return callback(error) if error? - ResourceListManager.loadResourceList basePath, callback + ResourceWriter._removeExtraneousFiles resourceList, basePath, (error) => + return callback(error) if error? + ResourceWriter.saveIncrementalResourcesToDisk request.project_id, request.resources, basePath, (error) -> + return callback(error) if error? + callback(null, resourceList) + else @saveAllResourcesToDisk request.project_id, request.resources, basePath, (error) -> return callback(error) if error? @@ -113,6 +118,8 @@ module.exports = ResourceWriter = should_delete = true if path.match(/^output\./) or path.match(/\.aux$/) or path.match(/^cache\//) # knitr cache should_delete = false + if path in ['.project-resource-list', '.project-sync-state'] + should_delete = false if path == "output.pdf" or path == "output.dvi" or path == "output.log" or path == "output.xdv" should_delete = true if path == "output.tex" # created by TikzManager if present in output files diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee index 0e602502f1..32ffb13227 100644 --- a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee +++ b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee @@ -23,7 +23,7 @@ describe "ResourceWriter", -> @basePath = "/path/to/write/files/to" @callback = sinon.stub() - describe "syncResourcesToDisk", -> + describe "syncResourcesToDisk on a full request", -> beforeEach -> @resources = [ "resource-1-mock" @@ -36,7 +36,59 @@ describe "ResourceWriter", -> @ResourceWriter.storeSyncState = sinon.stub().callsArg(2) @ResourceListManager.saveResourceList = sinon.stub().callsArg(2) @ResourceListManager.loadResourceList = sinon.stub().callsArg(1) - @ResourceWriter.syncResourcesToDisk({project_id: @project_id, resources: @resources}, @basePath, @callback) + @ResourceWriter.syncResourcesToDisk({ + project_id: @project_id + syncState: @syncState = "0123456789abcdef" + resources: @resources + }, @basePath, @callback) + + it "should remove old files", -> + @ResourceWriter._removeExtraneousFiles + .calledWith(@resources, @basePath) + .should.equal true + + it "should write each resource to disk", -> + for resource in @resources + @ResourceWriter._writeResourceToDisk + .calledWith(@project_id, resource, @basePath) + .should.equal true + + it "should store the sync state", -> + console.log "CHECKING", @syncState, @basePath + @ResourceWriter.storeSyncState + .calledWith(@syncState, @basePath) + .should.equal true + + it "should save the resource list", -> + @ResourceListManager.saveResourceList + .calledWith(@resources, @basePath) + .should.equal true + + it "should call the callback", -> + @callback.called.should.equal true + + describe "syncResourcesToDisk on an incremental update", -> + beforeEach -> + @resources = [ + "resource-1-mock" + ] + @ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3) + @ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2) + @ResourceWriter.checkSyncState = sinon.stub().callsArgWith(2, null, true) + @ResourceWriter.storeSyncState = sinon.stub().callsArg(2) + @ResourceListManager.saveResourceList = sinon.stub().callsArg(2) + @ResourceListManager.loadResourceList = sinon.stub().callsArg(1) + @ResourceWriter.syncResourcesToDisk({ + project_id: @project_id, + syncType: "incremental", + syncState: @syncState = "1234567890abcdef", + resources: @resources + }, @basePath, @callback) + + it "should check the sync state matches", -> + @ResourceWriter.checkSyncState + .calledWith(@syncState, @basePath) + .should.equal true it "should remove old files", -> @ResourceWriter._removeExtraneousFiles From d614af286011924b93728fb1a49e6d7713ceb0fc Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 18 Aug 2017 09:41:43 +0100 Subject: [PATCH 288/754] finish unit test for incremental update --- services/clsi/test/unit/coffee/ResourceWriterTests.coffee | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee index 32ffb13227..9a17b05c65 100644 --- a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee +++ b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee @@ -54,7 +54,6 @@ describe "ResourceWriter", -> .should.equal true it "should store the sync state", -> - console.log "CHECKING", @syncState, @basePath @ResourceWriter.storeSyncState .calledWith(@syncState, @basePath) .should.equal true @@ -77,7 +76,7 @@ describe "ResourceWriter", -> @ResourceWriter.checkSyncState = sinon.stub().callsArgWith(2, null, true) @ResourceWriter.storeSyncState = sinon.stub().callsArg(2) @ResourceListManager.saveResourceList = sinon.stub().callsArg(2) - @ResourceListManager.loadResourceList = sinon.stub().callsArg(1) + @ResourceListManager.loadResourceList = sinon.stub().callsArgWith(1, null, @resources) @ResourceWriter.syncResourcesToDisk({ project_id: @project_id, syncType: "incremental", From 96b801b093dd0709c39cefa94eb9c01c1748b30b Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 18 Aug 2017 09:41:59 +0100 Subject: [PATCH 289/754] fix whitespace --- services/clsi/app/coffee/ResourceWriter.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index d0d19882d1..fae570f877 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -53,7 +53,7 @@ module.exports = ResourceWriter = storeSyncState: (state, basePath, callback) -> stateFile = Path.join(basePath, @SYNC_STATE_FILE) - if not state? # remove the file if no state passed in + if not state? # remove the file if no state passed in logger.log state:state, basePath:basePath, "clearing sync state" fs.unlink stateFile, (err) -> if err? and err.code isnt 'ENOENT' From a84c884fc9cb5cd4c094bf4098c7d09477120442 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 18 Aug 2017 10:22:17 +0100 Subject: [PATCH 290/754] splice state management into ResourceStateManager --- .../app/coffee/ResourceStateManager.coffee | 46 ++++++++++++++++++ .../clsi/app/coffee/ResourceWriter.coffee | 47 ++----------------- .../unit/coffee/ResourceWriterTests.coffee | 35 +++++++++++--- 3 files changed, 79 insertions(+), 49 deletions(-) create mode 100644 services/clsi/app/coffee/ResourceStateManager.coffee diff --git a/services/clsi/app/coffee/ResourceStateManager.coffee b/services/clsi/app/coffee/ResourceStateManager.coffee new file mode 100644 index 0000000000..2a6d177b2e --- /dev/null +++ b/services/clsi/app/coffee/ResourceStateManager.coffee @@ -0,0 +1,46 @@ +Path = require "path" +fs = require "fs" +mkdirp = require "mkdirp" +logger = require "logger-sharelatex" +settings = require("settings-sharelatex") +Errors = require "./Errors" + +module.exports = ResourceStateManager = + + # The sync state is an identifier which must match for an + # incremental update to be allowed. + # + # The initial value is passed in and stored on a full + # compile. + # + # Subsequent incremental compiles must come with the same value - if + # not they will be rejected with a 409 Conflict response. + # + # An incremental compile can only update existing files with new + # content. The sync state identifier must change if any docs or + # files are moved, added, deleted or renamed. + + SYNC_STATE_FILE: ".project-sync-state" + + saveProjectStateHash: (state, basePath, callback) -> + stateFile = Path.join(basePath, @SYNC_STATE_FILE) + if not state? # remove the file if no state passed in + logger.log state:state, basePath:basePath, "clearing sync state" + fs.unlink stateFile, (err) -> + if err? and err.code isnt 'ENOENT' + return callback(err) + else + return callback() + else + logger.log state:state, basePath:basePath, "writing sync state" + fs.writeFile stateFile, state, {encoding: 'ascii'}, callback + + checkProjectStateHashMatches: (state, basePath, callback) -> + stateFile = Path.join(basePath, @SYNC_STATE_FILE) + fs.readFile stateFile, {encoding:'ascii'}, (err, oldState) -> + if err? and err.code isnt 'ENOENT' + return callback(err) + else if state isnt oldState + return callback new Errors.FilesOutOfSyncError("invalid state for incremental update") + else if state is oldState + callback(null) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index fae570f877..7f6702621c 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -4,9 +4,9 @@ fs = require "fs" async = require "async" mkdirp = require "mkdirp" OutputFileFinder = require "./OutputFileFinder" +ResourceStateManager = require "./ResourceStateManager" ResourceListManager = require "./ResourceListManager" Metrics = require "./Metrics" -Errors = require "./Errors" logger = require "logger-sharelatex" settings = require("settings-sharelatex") @@ -16,9 +16,8 @@ module.exports = ResourceWriter = syncResourcesToDisk: (request, basePath, callback = (error, resourceList) ->) -> if request.syncType is "incremental" - ResourceWriter.checkSyncState request.syncState, basePath, (error, syncStateOk) -> - logger.log syncState: request.syncState, result:syncStateOk, "checked state on incremental request" - return callback new Errors.FilesOutOfSyncError("invalid state for incremental update") if not syncStateOk + ResourceStateManager.checkProjectStateHashMatches request.syncState, basePath, (error) -> + return callback(error) if error? ResourceListManager.loadResourceList basePath, (error, resourceList) -> return callback(error) if error? ResourceWriter._removeExtraneousFiles resourceList, basePath, (error) => @@ -26,53 +25,15 @@ module.exports = ResourceWriter = ResourceWriter.saveIncrementalResourcesToDisk request.project_id, request.resources, basePath, (error) -> return callback(error) if error? callback(null, resourceList) - else @saveAllResourcesToDisk request.project_id, request.resources, basePath, (error) -> return callback(error) if error? - ResourceWriter.storeSyncState request.syncState, basePath, (error) -> + ResourceStateManager.saveProjectStateHash request.syncState, basePath, (error) -> return callback(error) if error? ResourceListManager.saveResourceList request.resources, basePath, (error) => return callback(error) if error? callback(null, request.resources) - # The sync state is an identifier which must match for an - # incremental update to be allowed. - # - # The initial value is passed in and stored on a full - # compile. - # - # Subsequent incremental compiles must come with the same value - if - # not they will be rejected with a 409 Conflict response. - # - # An incremental compile can only update existing files with new - # content. The sync state identifier must change if any docs or - # files are moved, added, deleted or renamed. - - SYNC_STATE_FILE: ".project-sync-state" - - storeSyncState: (state, basePath, callback) -> - stateFile = Path.join(basePath, @SYNC_STATE_FILE) - if not state? # remove the file if no state passed in - logger.log state:state, basePath:basePath, "clearing sync state" - fs.unlink stateFile, (err) -> - if err? and err.code isnt 'ENOENT' - return callback(err) - else - return callback() - else - logger.log state:state, basePath:basePath, "writing sync state" - fs.writeFile stateFile, state, {encoding: 'ascii'}, callback - - checkSyncState: (state, basePath, callback) -> - stateFile = Path.join(basePath, @SYNC_STATE_FILE) - fs.readFile stateFile, {encoding:'ascii'}, (err, oldState) -> - if err? and err.code isnt 'ENOENT' - return callback(err) - else - # return true if state matches, false otherwise (including file not existing) - callback(null, if state is oldState then true else false) - saveIncrementalResourcesToDisk: (project_id, resources, basePath, callback = (error) ->) -> @_createDirectory basePath, (error) => return callback(error) if error? diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee index 9a17b05c65..0804438f3b 100644 --- a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee +++ b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee @@ -11,6 +11,7 @@ describe "ResourceWriter", -> mkdir: sinon.stub().callsArg(1) unlink: sinon.stub().callsArg(1) "./ResourceListManager": @ResourceListManager = {} + "./ResourceStateManager": @ResourceStateManager = {} "wrench": @wrench = {} "./UrlCache" : @UrlCache = {} "mkdirp" : @mkdirp = sinon.stub().callsArg(1) @@ -32,8 +33,8 @@ describe "ResourceWriter", -> ] @ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3) @ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2) - @ResourceWriter.checkSyncState = sinon.stub().callsArg(2) - @ResourceWriter.storeSyncState = sinon.stub().callsArg(2) + @ResourceStateManager.checkProjectStateHashMatches = sinon.stub().callsArg(2) + @ResourceStateManager.saveProjectStateHash = sinon.stub().callsArg(2) @ResourceListManager.saveResourceList = sinon.stub().callsArg(2) @ResourceListManager.loadResourceList = sinon.stub().callsArg(1) @ResourceWriter.syncResourcesToDisk({ @@ -54,7 +55,7 @@ describe "ResourceWriter", -> .should.equal true it "should store the sync state", -> - @ResourceWriter.storeSyncState + @ResourceStateManager.saveProjectStateHash .calledWith(@syncState, @basePath) .should.equal true @@ -73,8 +74,8 @@ describe "ResourceWriter", -> ] @ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3) @ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2) - @ResourceWriter.checkSyncState = sinon.stub().callsArgWith(2, null, true) - @ResourceWriter.storeSyncState = sinon.stub().callsArg(2) + @ResourceStateManager.checkProjectStateHashMatches = sinon.stub().callsArg(2) + @ResourceStateManager.saveProjectStateHash = sinon.stub().callsArg(2) @ResourceListManager.saveResourceList = sinon.stub().callsArg(2) @ResourceListManager.loadResourceList = sinon.stub().callsArgWith(1, null, @resources) @ResourceWriter.syncResourcesToDisk({ @@ -85,7 +86,7 @@ describe "ResourceWriter", -> }, @basePath, @callback) it "should check the sync state matches", -> - @ResourceWriter.checkSyncState + @ResourceStateManager.checkProjectStateHashMatches .calledWith(@syncState, @basePath) .should.equal true @@ -103,6 +104,28 @@ describe "ResourceWriter", -> it "should call the callback", -> @callback.called.should.equal true + describe "syncResourcesToDisk on an incremental update when the state does not match", -> + beforeEach -> + @resources = [ + "resource-1-mock" + ] + @ResourceStateManager.checkProjectStateHashMatches = sinon.stub().callsArgWith(2, @error = new Error()) + @ResourceWriter.syncResourcesToDisk({ + project_id: @project_id, + syncType: "incremental", + syncState: @syncState = "1234567890abcdef", + resources: @resources + }, @basePath, @callback) + + it "should check whether the sync state matches", -> + @ResourceStateManager.checkProjectStateHashMatches + .calledWith(@syncState, @basePath) + .should.equal true + + it "should call the callback with an error", -> + @callback.calledWith(@error).should.equal true + + describe "_removeExtraneousFiles", -> beforeEach -> @output_files = [{ From 26f85ba75f0460c323b92f3bd404a752f5cae3c6 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 18 Aug 2017 11:17:01 +0100 Subject: [PATCH 291/754] read resource files safely put a limit on the amount of data read --- .../app/coffee/ResourceListManager.coffee | 5 ++-- .../app/coffee/ResourceStateManager.coffee | 11 ++++----- services/clsi/app/coffee/SafeReader.coffee | 24 +++++++++++++++++++ 3 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 services/clsi/app/coffee/SafeReader.coffee diff --git a/services/clsi/app/coffee/ResourceListManager.coffee b/services/clsi/app/coffee/ResourceListManager.coffee index d68f5d1ad6..aef2d2fcfb 100644 --- a/services/clsi/app/coffee/ResourceListManager.coffee +++ b/services/clsi/app/coffee/ResourceListManager.coffee @@ -1,8 +1,8 @@ Path = require "path" fs = require "fs" -mkdirp = require "mkdirp" logger = require "logger-sharelatex" settings = require("settings-sharelatex") +SafeReader = require "./SafeReader" module.exports = ResourceListManager = @@ -18,7 +18,8 @@ module.exports = ResourceListManager = loadResourceList: (basePath, callback = (error) ->) -> resourceListFile = Path.join(basePath, @RESOURCE_LIST_FILE) - fs.readFile resourceListFile, (err, resourceList) -> + # limit file to 128K, compile directory is user accessible + SafeReader.readFile resourceListFile, 128*1024, 'utf8', (err, resourceList) -> return callback(err) if err? resources = ({path: path} for path in resourceList?.toString()?.split("\n") or []) callback(null, resources) diff --git a/services/clsi/app/coffee/ResourceStateManager.coffee b/services/clsi/app/coffee/ResourceStateManager.coffee index 2a6d177b2e..eb193b885d 100644 --- a/services/clsi/app/coffee/ResourceStateManager.coffee +++ b/services/clsi/app/coffee/ResourceStateManager.coffee @@ -1,9 +1,9 @@ Path = require "path" fs = require "fs" -mkdirp = require "mkdirp" logger = require "logger-sharelatex" settings = require("settings-sharelatex") Errors = require "./Errors" +SafeReader = require "./SafeReader" module.exports = ResourceStateManager = @@ -37,10 +37,9 @@ module.exports = ResourceStateManager = checkProjectStateHashMatches: (state, basePath, callback) -> stateFile = Path.join(basePath, @SYNC_STATE_FILE) - fs.readFile stateFile, {encoding:'ascii'}, (err, oldState) -> - if err? and err.code isnt 'ENOENT' - return callback(err) - else if state isnt oldState + SafeReader.readFile stateFile, 64, 'ascii', (err, oldState) -> + return callback(err) if err? + if state isnt oldState return callback new Errors.FilesOutOfSyncError("invalid state for incremental update") - else if state is oldState + else callback(null) diff --git a/services/clsi/app/coffee/SafeReader.coffee b/services/clsi/app/coffee/SafeReader.coffee new file mode 100644 index 0000000000..7ed3693c1f --- /dev/null +++ b/services/clsi/app/coffee/SafeReader.coffee @@ -0,0 +1,24 @@ +fs = require "fs" + +module.exports = SafeReader = + + # safely read up to size bytes from a file and return result as a + # string + + readFile: (file, size, encoding, callback = (error, result) ->) -> + fs.open file, 'r', (err, fd) -> + return callback() if err? and err.code is 'ENOENT' + return callback(err) if err? + + # safely return always closing the file + callbackWithClose = (err, result) -> + fs.close fd, (err1) -> + return callback(err) if err? + return callback(err1) if err1? + callback(null, result) + + buff = new Buffer(size, 0) # fill with zeros + fs.read fd, buff, 0, buff.length, 0, (err, bytesRead, buffer) -> + return callbackWithClose(err) if err? + result = buffer.toString(encoding, 0, bytesRead) + callbackWithClose(null, result) From 87935f30c5b2f7447404a85f0bf9331b626125c9 Mon Sep 17 00:00:00 2001 From: Alasdair Smith <ali@alasdairsmith.co.uk> Date: Thu, 24 Aug 2017 13:14:01 +0100 Subject: [PATCH 292/754] Update docker-runner-sharelatex config --- services/clsi/config/settings.defaults.coffee | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index cb7e6be7b1..4b391789c1 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -16,21 +16,16 @@ module.exports = clsiCacheDir: Path.resolve(__dirname + "/../cache") synctexBaseDir: (project_id) -> Path.join(@compilesDir, project_id) - # clsi: - # strace: true - # archive_logs: true - # commandRunner: "docker-runner-sharelatex" - # docker: - # image: "quay.io/sharelatex/texlive-full" - # env: - # PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2013/bin/x86_64-linux/" - # HOME: "/tmp" - # modem: - # socketPath: false - # user: "tex" - # latexmkCommandPrefix: [] - # # latexmkCommandPrefix: ["/usr/bin/time", "-v"] # on Linux - # # latexmkCommandPrefix: ["/usr/local/bin/gtime", "-v"] # on Mac OSX, installed with `brew install gnu-time` +# clsi: +# commandRunner: "docker-runner-sharelatex" +# docker: +# image: "quay.io/sharelatex/texlive-full:2017.1" +# env: +# HOME: "/tmp" +# socketPath: "/var/run/docker.sock" +# user: "tex" +# expireProjectAfterIdleMs: 24 * 60 * 60 * 1000 +# checkProjectsIntervalMs: 10 * 60 * 1000 internal: clsi: From ea3ee82e74fe8bce61573fe8ab0753482dfca26a Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 29 Aug 2017 12:09:31 +0100 Subject: [PATCH 293/754] added logging --- services/clsi/app/coffee/ResourceStateManager.coffee | 1 + services/clsi/app/coffee/ResourceWriter.coffee | 2 ++ 2 files changed, 3 insertions(+) diff --git a/services/clsi/app/coffee/ResourceStateManager.coffee b/services/clsi/app/coffee/ResourceStateManager.coffee index eb193b885d..946c8b5607 100644 --- a/services/clsi/app/coffee/ResourceStateManager.coffee +++ b/services/clsi/app/coffee/ResourceStateManager.coffee @@ -39,6 +39,7 @@ module.exports = ResourceStateManager = stateFile = Path.join(basePath, @SYNC_STATE_FILE) SafeReader.readFile stateFile, 64, 'ascii', (err, oldState) -> return callback(err) if err? + logger.log state:state, oldState: oldState, basePath:basePath, stateMatches: !(state isnt oldState), "checking sync state" if state isnt oldState return callback new Errors.FilesOutOfSyncError("invalid state for incremental update") else diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index 7f6702621c..7c3bc732a2 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -16,6 +16,7 @@ module.exports = ResourceWriter = syncResourcesToDisk: (request, basePath, callback = (error, resourceList) ->) -> if request.syncType is "incremental" + logger.log project_id: request.project_id, user_id: request.user_id, "incremental sync" ResourceStateManager.checkProjectStateHashMatches request.syncState, basePath, (error) -> return callback(error) if error? ResourceListManager.loadResourceList basePath, (error, resourceList) -> @@ -26,6 +27,7 @@ module.exports = ResourceWriter = return callback(error) if error? callback(null, resourceList) else + logger.log project_id: request.project_id, user_id: request.user_id, "full sync" @saveAllResourcesToDisk request.project_id, request.resources, basePath, (error) -> return callback(error) if error? ResourceStateManager.saveProjectStateHash request.syncState, basePath, (error) -> From ae2af06f7b33944ded2b4f2c67567fe155efd7b5 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly <shane@kilkelly.me> Date: Tue, 29 Aug 2017 14:30:43 +0100 Subject: [PATCH 294/754] Upgrade to node 6.9 --- services/clsi/.nvmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/.nvmrc b/services/clsi/.nvmrc index 994fe99096..e18a34b9d6 100644 --- a/services/clsi/.nvmrc +++ b/services/clsi/.nvmrc @@ -1 +1 @@ -0.10.22 \ No newline at end of file +6.11.2 From 3d1c3a1d2781396b9975a4fb8612c6c3a7e57d12 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 7 Sep 2017 11:54:38 +0100 Subject: [PATCH 295/754] combine the resource state and resource list to prevent them getting out of sync --- .../app/coffee/ResourceListManager.coffee | 25 ------------------- .../app/coffee/ResourceStateManager.coffee | 23 ++++++++++------- .../clsi/app/coffee/ResourceWriter.coffee | 19 ++++++-------- .../unit/coffee/ResourceWriterTests.coffee | 20 ++++----------- 4 files changed, 26 insertions(+), 61 deletions(-) delete mode 100644 services/clsi/app/coffee/ResourceListManager.coffee diff --git a/services/clsi/app/coffee/ResourceListManager.coffee b/services/clsi/app/coffee/ResourceListManager.coffee deleted file mode 100644 index aef2d2fcfb..0000000000 --- a/services/clsi/app/coffee/ResourceListManager.coffee +++ /dev/null @@ -1,25 +0,0 @@ -Path = require "path" -fs = require "fs" -logger = require "logger-sharelatex" -settings = require("settings-sharelatex") -SafeReader = require "./SafeReader" - -module.exports = ResourceListManager = - - # This file is a list of the input files for the project, one per - # line, used to identify output files (i.e. files not on this list) - # when the incoming request is incremental. - RESOURCE_LIST_FILE: ".project-resource-list" - - saveResourceList: (resources, basePath, callback = (error) ->) -> - resourceListFile = Path.join(basePath, @RESOURCE_LIST_FILE) - resourceList = (resource.path for resource in resources) - fs.writeFile resourceListFile, resourceList.join("\n"), callback - - loadResourceList: (basePath, callback = (error) ->) -> - resourceListFile = Path.join(basePath, @RESOURCE_LIST_FILE) - # limit file to 128K, compile directory is user accessible - SafeReader.readFile resourceListFile, 128*1024, 'utf8', (err, resourceList) -> - return callback(err) if err? - resources = ({path: path} for path in resourceList?.toString()?.split("\n") or []) - callback(null, resources) diff --git a/services/clsi/app/coffee/ResourceStateManager.coffee b/services/clsi/app/coffee/ResourceStateManager.coffee index 946c8b5607..7f97405066 100644 --- a/services/clsi/app/coffee/ResourceStateManager.coffee +++ b/services/clsi/app/coffee/ResourceStateManager.coffee @@ -11,10 +11,11 @@ module.exports = ResourceStateManager = # incremental update to be allowed. # # The initial value is passed in and stored on a full - # compile. + # compile, along with the list of resources.. # # Subsequent incremental compiles must come with the same value - if - # not they will be rejected with a 409 Conflict response. + # not they will be rejected with a 409 Conflict response. The + # previous list of resources is returned. # # An incremental compile can only update existing files with new # content. The sync state identifier must change if any docs or @@ -22,7 +23,7 @@ module.exports = ResourceStateManager = SYNC_STATE_FILE: ".project-sync-state" - saveProjectStateHash: (state, basePath, callback) -> + saveProjectStateHash: (state, resources, basePath, callback = (error) ->) -> stateFile = Path.join(basePath, @SYNC_STATE_FILE) if not state? # remove the file if no state passed in logger.log state:state, basePath:basePath, "clearing sync state" @@ -33,14 +34,18 @@ module.exports = ResourceStateManager = return callback() else logger.log state:state, basePath:basePath, "writing sync state" - fs.writeFile stateFile, state, {encoding: 'ascii'}, callback + resourceList = (resource.path for resource in resources) + fs.writeFile stateFile, [resourceList..., "stateHash:#{state}"].join("\n"), callback - checkProjectStateHashMatches: (state, basePath, callback) -> + checkProjectStateHashMatches: (state, basePath, callback = (error, resources) ->) -> stateFile = Path.join(basePath, @SYNC_STATE_FILE) - SafeReader.readFile stateFile, 64, 'ascii', (err, oldState) -> + SafeReader.readFile stateFile, 128*1024, 'utf8', (err, result) -> return callback(err) if err? - logger.log state:state, oldState: oldState, basePath:basePath, stateMatches: !(state isnt oldState), "checking sync state" - if state isnt oldState + [resourceList..., oldState] = result?.toString()?.split("\n") or [] + newState = "stateHash:#{state}" + logger.log state:state, oldState: oldState, basePath:basePath, stateMatches: (newState is oldState), "checking sync state" + if newState isnt oldState return callback new Errors.FilesOutOfSyncError("invalid state for incremental update") else - callback(null) + resources = ({path: path} for path in resourceList) + callback(null, resources) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index 7c3bc732a2..55f15085cd 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -5,7 +5,6 @@ async = require "async" mkdirp = require "mkdirp" OutputFileFinder = require "./OutputFileFinder" ResourceStateManager = require "./ResourceStateManager" -ResourceListManager = require "./ResourceListManager" Metrics = require "./Metrics" logger = require "logger-sharelatex" settings = require("settings-sharelatex") @@ -17,24 +16,20 @@ module.exports = ResourceWriter = syncResourcesToDisk: (request, basePath, callback = (error, resourceList) ->) -> if request.syncType is "incremental" logger.log project_id: request.project_id, user_id: request.user_id, "incremental sync" - ResourceStateManager.checkProjectStateHashMatches request.syncState, basePath, (error) -> + ResourceStateManager.checkProjectStateHashMatches request.syncState, basePath, (error, resourceList) -> return callback(error) if error? - ResourceListManager.loadResourceList basePath, (error, resourceList) -> + ResourceWriter._removeExtraneousFiles resourceList, basePath, (error) => return callback(error) if error? - ResourceWriter._removeExtraneousFiles resourceList, basePath, (error) => + ResourceWriter.saveIncrementalResourcesToDisk request.project_id, request.resources, basePath, (error) -> return callback(error) if error? - ResourceWriter.saveIncrementalResourcesToDisk request.project_id, request.resources, basePath, (error) -> - return callback(error) if error? - callback(null, resourceList) + callback(null, resourceList) else logger.log project_id: request.project_id, user_id: request.user_id, "full sync" @saveAllResourcesToDisk request.project_id, request.resources, basePath, (error) -> return callback(error) if error? - ResourceStateManager.saveProjectStateHash request.syncState, basePath, (error) -> + ResourceStateManager.saveProjectStateHash request.syncState, request.resources, basePath, (error) -> return callback(error) if error? - ResourceListManager.saveResourceList request.resources, basePath, (error) => - return callback(error) if error? - callback(null, request.resources) + callback(null, request.resources) saveIncrementalResourcesToDisk: (project_id, resources, basePath, callback = (error) ->) -> @_createDirectory basePath, (error) => @@ -81,7 +76,7 @@ module.exports = ResourceWriter = should_delete = true if path.match(/^output\./) or path.match(/\.aux$/) or path.match(/^cache\//) # knitr cache should_delete = false - if path in ['.project-resource-list', '.project-sync-state'] + if path == '.project-sync-state' should_delete = false if path == "output.pdf" or path == "output.dvi" or path == "output.log" or path == "output.xdv" should_delete = true diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee index 0804438f3b..520db6fda6 100644 --- a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee +++ b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee @@ -10,7 +10,6 @@ describe "ResourceWriter", -> "fs": @fs = mkdir: sinon.stub().callsArg(1) unlink: sinon.stub().callsArg(1) - "./ResourceListManager": @ResourceListManager = {} "./ResourceStateManager": @ResourceStateManager = {} "wrench": @wrench = {} "./UrlCache" : @UrlCache = {} @@ -34,9 +33,7 @@ describe "ResourceWriter", -> @ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3) @ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2) @ResourceStateManager.checkProjectStateHashMatches = sinon.stub().callsArg(2) - @ResourceStateManager.saveProjectStateHash = sinon.stub().callsArg(2) - @ResourceListManager.saveResourceList = sinon.stub().callsArg(2) - @ResourceListManager.loadResourceList = sinon.stub().callsArg(1) + @ResourceStateManager.saveProjectStateHash = sinon.stub().callsArg(3) @ResourceWriter.syncResourcesToDisk({ project_id: @project_id syncState: @syncState = "0123456789abcdef" @@ -54,14 +51,9 @@ describe "ResourceWriter", -> .calledWith(@project_id, resource, @basePath) .should.equal true - it "should store the sync state", -> + it "should store the sync state and resource list", -> @ResourceStateManager.saveProjectStateHash - .calledWith(@syncState, @basePath) - .should.equal true - - it "should save the resource list", -> - @ResourceListManager.saveResourceList - .calledWith(@resources, @basePath) + .calledWith(@syncState, @resources, @basePath) .should.equal true it "should call the callback", -> @@ -74,10 +66,8 @@ describe "ResourceWriter", -> ] @ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3) @ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2) - @ResourceStateManager.checkProjectStateHashMatches = sinon.stub().callsArg(2) - @ResourceStateManager.saveProjectStateHash = sinon.stub().callsArg(2) - @ResourceListManager.saveResourceList = sinon.stub().callsArg(2) - @ResourceListManager.loadResourceList = sinon.stub().callsArgWith(1, null, @resources) + @ResourceStateManager.checkProjectStateHashMatches = sinon.stub().callsArgWith(2, null, @resources) + @ResourceStateManager.saveProjectStateHash = sinon.stub().callsArg(3) @ResourceWriter.syncResourcesToDisk({ project_id: @project_id, syncType: "incremental", From cbd3e32143e50715502af90518a3fc51b991f36c Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 7 Sep 2017 11:55:50 +0100 Subject: [PATCH 296/754] log error if state file is truncacted --- services/clsi/app/coffee/SafeReader.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/clsi/app/coffee/SafeReader.coffee b/services/clsi/app/coffee/SafeReader.coffee index 7ed3693c1f..56ebe50503 100644 --- a/services/clsi/app/coffee/SafeReader.coffee +++ b/services/clsi/app/coffee/SafeReader.coffee @@ -1,4 +1,5 @@ fs = require "fs" +logger = require "logger-sharelatex" module.exports = SafeReader = @@ -21,4 +22,6 @@ module.exports = SafeReader = fs.read fd, buff, 0, buff.length, 0, (err, bytesRead, buffer) -> return callbackWithClose(err) if err? result = buffer.toString(encoding, 0, bytesRead) + if bytesRead is size + logger.error file:file, size:size, bytesRead:bytesRead, "file truncated" callbackWithClose(null, result) From b8e8530a164a7ba3beb52cb9e3ec4d4e27eb5fb0 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 7 Sep 2017 11:56:03 +0100 Subject: [PATCH 297/754] fix whitespace --- services/clsi/app/coffee/SafeReader.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/SafeReader.coffee b/services/clsi/app/coffee/SafeReader.coffee index 56ebe50503..91bf8a12da 100644 --- a/services/clsi/app/coffee/SafeReader.coffee +++ b/services/clsi/app/coffee/SafeReader.coffee @@ -8,7 +8,7 @@ module.exports = SafeReader = readFile: (file, size, encoding, callback = (error, result) ->) -> fs.open file, 'r', (err, fd) -> - return callback() if err? and err.code is 'ENOENT' + return callback() if err? and err.code is 'ENOENT' return callback(err) if err? # safely return always closing the file From 66431fc2b82d6dd57cdeefdd0c0990354f46c324 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 7 Sep 2017 13:51:36 +0100 Subject: [PATCH 298/754] log any missing files --- .../clsi/app/coffee/OutputFileFinder.coffee | 5 ++--- .../app/coffee/ResourceStateManager.coffee | 12 +++++++++++ .../clsi/app/coffee/ResourceWriter.coffee | 20 +++++++++++-------- .../unit/coffee/ResourceWriterTests.coffee | 9 +++++++-- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/services/clsi/app/coffee/OutputFileFinder.coffee b/services/clsi/app/coffee/OutputFileFinder.coffee index c89cc8ad7c..80bd07f2cb 100644 --- a/services/clsi/app/coffee/OutputFileFinder.coffee +++ b/services/clsi/app/coffee/OutputFileFinder.coffee @@ -5,7 +5,7 @@ spawn = require("child_process").spawn logger = require "logger-sharelatex" module.exports = OutputFileFinder = - findOutputFiles: (resources, directory, callback = (error, outputFiles) ->) -> + findOutputFiles: (resources, directory, callback = (error, outputFiles, allFiles) ->) -> incomingResources = {} for resource in resources incomingResources[resource.path] = true @@ -16,7 +16,6 @@ module.exports = OutputFileFinder = if error? logger.err err:error, "error finding all output files" return callback(error) - jobs = [] outputFiles = [] for file in allFiles if !incomingResources[file] @@ -24,7 +23,7 @@ module.exports = OutputFileFinder = path: file type: file.match(/\.([^\.]+)$/)?[1] } - callback null, outputFiles + callback null, outputFiles, allFiles _getAllFiles: (directory, _callback = (error, fileList) ->) -> callback = (error, fileList) -> diff --git a/services/clsi/app/coffee/ResourceStateManager.coffee b/services/clsi/app/coffee/ResourceStateManager.coffee index 7f97405066..247c929746 100644 --- a/services/clsi/app/coffee/ResourceStateManager.coffee +++ b/services/clsi/app/coffee/ResourceStateManager.coffee @@ -49,3 +49,15 @@ module.exports = ResourceStateManager = else resources = ({path: path} for path in resourceList) callback(null, resources) + + checkResourceFiles: (resources, allFiles, directory, callback = (error) ->) -> + # check if any of the input files are not present in list of files + seenFile = {} + for file in allFiles + seenFile[file] = true + missingFiles = (resource.path for resource in resources when not seenFile[resource.path]) + if missingFiles.length > 0 + logger.err missingFiles:missingFiles, dir:directory, allFiles:allFiles, resources:resources, "missing input files for project" + return callback new Errors.FilesOutOfSyncError("resource files missing in incremental update") + else + callback() diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index 55f15085cd..fa5b010125 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -18,11 +18,13 @@ module.exports = ResourceWriter = logger.log project_id: request.project_id, user_id: request.user_id, "incremental sync" ResourceStateManager.checkProjectStateHashMatches request.syncState, basePath, (error, resourceList) -> return callback(error) if error? - ResourceWriter._removeExtraneousFiles resourceList, basePath, (error) => + ResourceWriter._removeExtraneousFiles resourceList, basePath, (error, outputFiles, allFiles) -> return callback(error) if error? - ResourceWriter.saveIncrementalResourcesToDisk request.project_id, request.resources, basePath, (error) -> + ResourceStateManager.checkResourceFiles resourceList, allFiles, basePath, (error) -> return callback(error) if error? - callback(null, resourceList) + ResourceWriter.saveIncrementalResourcesToDisk request.project_id, request.resources, basePath, (error) -> + return callback(error) if error? + callback(null, resourceList) else logger.log project_id: request.project_id, user_id: request.user_id, "full sync" @saveAllResourcesToDisk request.project_id, request.resources, basePath, (error) -> @@ -60,13 +62,13 @@ module.exports = ResourceWriter = else return callback() - _removeExtraneousFiles: (resources, basePath, _callback = (error) ->) -> + _removeExtraneousFiles: (resources, basePath, _callback = (error, outputFiles, allFiles) ->) -> timer = new Metrics.Timer("unlink-output-files") - callback = (error) -> + callback = (error, result...) -> timer.done() - _callback(error) + _callback(error, result...) - OutputFileFinder.findOutputFiles resources, basePath, (error, outputFiles) -> + OutputFileFinder.findOutputFiles resources, basePath, (error, outputFiles, allFiles) -> return callback(error) if error? jobs = [] @@ -85,7 +87,9 @@ module.exports = ResourceWriter = if should_delete jobs.push (callback) -> ResourceWriter._deleteFileIfNotDirectory Path.join(basePath, path), callback - async.series jobs, callback + async.series jobs, (error) -> + return callback(error) if error? + callback(null, outputFiles, allFiles) _deleteFileIfNotDirectory: (path, callback = (error) ->) -> fs.stat path, (error, stat) -> diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee index 520db6fda6..dee0952337 100644 --- a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee +++ b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee @@ -32,7 +32,6 @@ describe "ResourceWriter", -> ] @ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3) @ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2) - @ResourceStateManager.checkProjectStateHashMatches = sinon.stub().callsArg(2) @ResourceStateManager.saveProjectStateHash = sinon.stub().callsArg(3) @ResourceWriter.syncResourcesToDisk({ project_id: @project_id @@ -65,9 +64,10 @@ describe "ResourceWriter", -> "resource-1-mock" ] @ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3) - @ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2) + @ResourceWriter._removeExtraneousFiles = sinon.stub().callsArgWith(2, null, @outputFiles = [], @allFiles = []) @ResourceStateManager.checkProjectStateHashMatches = sinon.stub().callsArgWith(2, null, @resources) @ResourceStateManager.saveProjectStateHash = sinon.stub().callsArg(3) + @ResourceStateManager.checkResourceFiles = sinon.stub().callsArg(3) @ResourceWriter.syncResourcesToDisk({ project_id: @project_id, syncType: "incremental", @@ -85,6 +85,11 @@ describe "ResourceWriter", -> .calledWith(@resources, @basePath) .should.equal true + it "should check each resource exists", -> + @ResourceStateManager.checkResourceFiles + .calledWith(@resources, @allFiles, @basePath) + .should.equal true + it "should write each resource to disk", -> for resource in @resources @ResourceWriter._writeResourceToDisk From 0a242bac3c433e1a4f7c47da3ea6b7682dae010f Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 8 Sep 2017 13:56:40 +0100 Subject: [PATCH 299/754] rename saveProjectStateHash to saveProjectState --- .../clsi/app/coffee/ResourceStateManager.coffee | 4 ++-- services/clsi/app/coffee/ResourceWriter.coffee | 4 ++-- .../test/unit/coffee/ResourceWriterTests.coffee | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/services/clsi/app/coffee/ResourceStateManager.coffee b/services/clsi/app/coffee/ResourceStateManager.coffee index 247c929746..b894701a6a 100644 --- a/services/clsi/app/coffee/ResourceStateManager.coffee +++ b/services/clsi/app/coffee/ResourceStateManager.coffee @@ -23,7 +23,7 @@ module.exports = ResourceStateManager = SYNC_STATE_FILE: ".project-sync-state" - saveProjectStateHash: (state, resources, basePath, callback = (error) ->) -> + saveProjectState: (state, resources, basePath, callback = (error) ->) -> stateFile = Path.join(basePath, @SYNC_STATE_FILE) if not state? # remove the file if no state passed in logger.log state:state, basePath:basePath, "clearing sync state" @@ -37,7 +37,7 @@ module.exports = ResourceStateManager = resourceList = (resource.path for resource in resources) fs.writeFile stateFile, [resourceList..., "stateHash:#{state}"].join("\n"), callback - checkProjectStateHashMatches: (state, basePath, callback = (error, resources) ->) -> + checkProjectStateMatches: (state, basePath, callback = (error, resources) ->) -> stateFile = Path.join(basePath, @SYNC_STATE_FILE) SafeReader.readFile stateFile, 128*1024, 'utf8', (err, result) -> return callback(err) if err? diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index fa5b010125..f9e90b036a 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -16,7 +16,7 @@ module.exports = ResourceWriter = syncResourcesToDisk: (request, basePath, callback = (error, resourceList) ->) -> if request.syncType is "incremental" logger.log project_id: request.project_id, user_id: request.user_id, "incremental sync" - ResourceStateManager.checkProjectStateHashMatches request.syncState, basePath, (error, resourceList) -> + ResourceStateManager.checkProjectStateMatches request.syncState, basePath, (error, resourceList) -> return callback(error) if error? ResourceWriter._removeExtraneousFiles resourceList, basePath, (error, outputFiles, allFiles) -> return callback(error) if error? @@ -29,7 +29,7 @@ module.exports = ResourceWriter = logger.log project_id: request.project_id, user_id: request.user_id, "full sync" @saveAllResourcesToDisk request.project_id, request.resources, basePath, (error) -> return callback(error) if error? - ResourceStateManager.saveProjectStateHash request.syncState, request.resources, basePath, (error) -> + ResourceStateManager.saveProjectState request.syncState, request.resources, basePath, (error) -> return callback(error) if error? callback(null, request.resources) diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee index dee0952337..fbc8916c31 100644 --- a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee +++ b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee @@ -32,7 +32,7 @@ describe "ResourceWriter", -> ] @ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3) @ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2) - @ResourceStateManager.saveProjectStateHash = sinon.stub().callsArg(3) + @ResourceStateManager.saveProjectState = sinon.stub().callsArg(3) @ResourceWriter.syncResourcesToDisk({ project_id: @project_id syncState: @syncState = "0123456789abcdef" @@ -51,7 +51,7 @@ describe "ResourceWriter", -> .should.equal true it "should store the sync state and resource list", -> - @ResourceStateManager.saveProjectStateHash + @ResourceStateManager.saveProjectState .calledWith(@syncState, @resources, @basePath) .should.equal true @@ -65,8 +65,8 @@ describe "ResourceWriter", -> ] @ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3) @ResourceWriter._removeExtraneousFiles = sinon.stub().callsArgWith(2, null, @outputFiles = [], @allFiles = []) - @ResourceStateManager.checkProjectStateHashMatches = sinon.stub().callsArgWith(2, null, @resources) - @ResourceStateManager.saveProjectStateHash = sinon.stub().callsArg(3) + @ResourceStateManager.checkProjectStateMatches = sinon.stub().callsArgWith(2, null, @resources) + @ResourceStateManager.saveProjectState = sinon.stub().callsArg(3) @ResourceStateManager.checkResourceFiles = sinon.stub().callsArg(3) @ResourceWriter.syncResourcesToDisk({ project_id: @project_id, @@ -76,7 +76,7 @@ describe "ResourceWriter", -> }, @basePath, @callback) it "should check the sync state matches", -> - @ResourceStateManager.checkProjectStateHashMatches + @ResourceStateManager.checkProjectStateMatches .calledWith(@syncState, @basePath) .should.equal true @@ -104,7 +104,7 @@ describe "ResourceWriter", -> @resources = [ "resource-1-mock" ] - @ResourceStateManager.checkProjectStateHashMatches = sinon.stub().callsArgWith(2, @error = new Error()) + @ResourceStateManager.checkProjectStateMatches = sinon.stub().callsArgWith(2, @error = new Error()) @ResourceWriter.syncResourcesToDisk({ project_id: @project_id, syncType: "incremental", @@ -113,7 +113,7 @@ describe "ResourceWriter", -> }, @basePath, @callback) it "should check whether the sync state matches", -> - @ResourceStateManager.checkProjectStateHashMatches + @ResourceStateManager.checkProjectStateMatches .calledWith(@syncState, @basePath) .should.equal true From 41e442d403dbcca4b8c3d4d518b7cb8a52834783 Mon Sep 17 00:00:00 2001 From: Joe Green <joe.green0991@gmail.com> Date: Fri, 8 Sep 2017 14:06:04 +0100 Subject: [PATCH 300/754] Add jenkinsfile (#72) * create Jenkinsfile * allow textlive image to be set with env vars * log error message in test * use sandboxed compiles variables * Add SANDBOXED_COMPILES_HOST_DIR var to test config * add SIBLING_CONTAINER_USER env var --- services/clsi/Jenkinsfile | 97 +++++++++++++++++++ .../coffee/ExampleDocumentTests.coffee | 4 +- .../acceptance/scripts/settings.test.coffee | 5 +- 3 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 services/clsi/Jenkinsfile diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile new file mode 100644 index 0000000000..97efd51f20 --- /dev/null +++ b/services/clsi/Jenkinsfile @@ -0,0 +1,97 @@ +pipeline { + + agent any + + triggers { + pollSCM('* * * * *') + cron('@daily') + } + + stages { + stage('Clean') { + steps { + // This is a terrible hack to set the file ownership to jenkins:jenkins so we can cleanup the directory + sh 'docker run -v $(pwd):/app --rm busybox /bin/chown -R 111:119 /app' + } + } + stage('Install') { + agent { + docker { + image 'node:4.2.1' + args "-v /var/lib/jenkins/.npm:/tmp/.npm -e HOME=/tmp" + reuseNode true + } + } + steps { + sh 'git config --global core.logallrefupdates false' + sh 'rm -fr node_modules' + checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: '_docker-runner'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/docker-runner-sharelatex']]]) + sh 'npm install ./_docker-runner' + sh 'rm -fr ./_docker-runner' + sh 'npm install' + sh 'npm rebuild' + sh 'npm install --quiet grunt-cli sqlite3' + } + } + stage('Compile and Test') { + agent { + docker { + image 'node:4.2.1' + reuseNode true + } + } + steps { + sh 'node_modules/.bin/grunt compile:app' + sh 'node_modules/.bin/grunt compile:acceptance_tests' + sh 'NODE_ENV=development node_modules/.bin/grunt test:unit' + } + } + stage('Acceptance Tests') { + environment { + TEXLIVE_IMAGE="quay.io/sharelatex/texlive-full:2017.1" + } + steps { + sh 'mkdir -p compiles' + // Not yet running, due to volumes/sibling containers + sh 'docker container prune -f' + sh 'docker pull $TEXLIVE_IMAGE' + sh 'docker pull sharelatex/acceptance-test-runner:clsi-4.2.1' + sh 'docker run --rm -e SIBLING_CONTAINER_USER=root -e SANDBOXED_COMPILES_HOST_DIR=$(pwd)/compiles -e SANDBOXED_COMPILES_SIBLING_CONTAINERS=true -e TEXLIVE_IMAGE=$TEXLIVE_IMAGE -v /var/run/docker.sock:/var/run/docker.sock -v $(pwd):/app sharelatex/acceptance-test-runner:clsi-4.2.1' + } + } + stage('Package') { + steps { + sh 'echo ${BUILD_NUMBER} > build_number.txt' + sh 'touch build.tar.gz' // Avoid tar warning about files changing during read + sh 'tar -czf build.tar.gz --exclude=build.tar.gz --exclude-vcs .' + } + } + stage('Publish') { + steps { + 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") + // 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 { + failure { + mail(from: "${EMAIL_ALERT_FROM}", + to: "${EMAIL_ALERT_TO}", + subject: "Jenkins build failed: ${JOB_NAME}:${BUILD_NUMBER}", + body: "Build: ${BUILD_URL}") + } + } + + // 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/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee index a49f5d62cb..4e36c6ac05 100644 --- a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee +++ b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee @@ -92,14 +92,14 @@ describe "Example Documents", -> it "should generate the correct pdf", (done) -> Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => - if body?.compile?.status is "failure" + if error || body?.compile?.status is "failure" console.log "DEBUG: error", error, "body", JSON.stringify(body) pdf = Client.getOutputFile body, "pdf" downloadAndComparePdf(@project_id, example_dir, pdf.url, done) it "should generate the correct pdf on the second run as well", (done) -> Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => - if body?.compile?.status is "failure" + if error || body?.compile?.status is "failure" console.log "DEBUG: error", error, "body", JSON.stringify(body) pdf = Client.getOutputFile body, "pdf" downloadAndComparePdf(@project_id, example_dir, pdf.url, done) diff --git a/services/clsi/test/acceptance/scripts/settings.test.coffee b/services/clsi/test/acceptance/scripts/settings.test.coffee index cf84c6baba..e3d3b70bb3 100644 --- a/services/clsi/test/acceptance/scripts/settings.test.coffee +++ b/services/clsi/test/acceptance/scripts/settings.test.coffee @@ -16,6 +16,7 @@ module.exports = clsiCacheDir: Path.resolve(__dirname + "/../../../cache") #synctexBaseDir: (project_id) -> Path.join(@compilesDir, project_id) synctexBaseDir: () -> "/compile" + sandboxedCompilesHostDir: process.env['SANDBOXED_COMPILES_HOST_DIR'] clsi: #strace: true @@ -23,13 +24,13 @@ module.exports = commandRunner: "docker-runner-sharelatex" latexmkCommandPrefix: ["/usr/bin/time", "-v"] # on Linux docker: - image: "texlive-full:2017.1-opt" + image: process.env.TEXLIVE_IMAGE || "texlive-full:2017.1-opt" env: PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2017/bin/x86_64-linux/" HOME: "/tmp" modem: socketPath: false - user: "111" + user: process.env.SIBLING_CONTAINER_USER ||"111" internal: clsi: From 81e824382764fb1307102e4fcf17a5e7033bb4c8 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 15 Sep 2017 13:41:56 +0100 Subject: [PATCH 301/754] fallback check for missing files dot files are not examined by OutputFileFinder, so do an extra check to make sure those exist also check for any relative paths in the resources --- .../app/coffee/ResourceStateManager.coffee | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/services/clsi/app/coffee/ResourceStateManager.coffee b/services/clsi/app/coffee/ResourceStateManager.coffee index b894701a6a..e250e6447c 100644 --- a/services/clsi/app/coffee/ResourceStateManager.coffee +++ b/services/clsi/app/coffee/ResourceStateManager.coffee @@ -4,6 +4,7 @@ logger = require "logger-sharelatex" settings = require("settings-sharelatex") Errors = require "./Errors" SafeReader = require "./SafeReader" +async = require "async" module.exports = ResourceStateManager = @@ -50,14 +51,31 @@ module.exports = ResourceStateManager = resources = ({path: path} for path in resourceList) callback(null, resources) - checkResourceFiles: (resources, allFiles, directory, callback = (error) ->) -> + checkResourceFiles: (resources, allFiles, basePath, callback = (error) ->) -> + # check the paths are all relative to current directory + for file in resources or [] + for dir in file?.path?.split('/') + if dir == '..' + return callback new Error("relative path in resource file list") # check if any of the input files are not present in list of files seenFile = {} for file in allFiles seenFile[file] = true - missingFiles = (resource.path for resource in resources when not seenFile[resource.path]) - if missingFiles.length > 0 - logger.err missingFiles:missingFiles, dir:directory, allFiles:allFiles, resources:resources, "missing input files for project" - return callback new Errors.FilesOutOfSyncError("resource files missing in incremental update") + missingFileCandidates = (resource.path for resource in resources when not seenFile[resource.path]) + # now check if they are really missing + ResourceStateManager._checkMissingFiles missingFileCandidates, basePath, (missingFiles) -> + if missingFiles?.length > 0 + logger.err missingFiles:missingFiles, basePath:basePath, allFiles:allFiles, resources:resources, "missing input files for project" + return callback new Errors.FilesOutOfSyncError("resource files missing in incremental update") + else + callback() + + _checkMissingFiles: (missingFileCandidates, basePath, callback = (missingFiles) ->) -> + if missingFileCandidates.length > 0 + fileDoesNotExist = (file, cb) -> + fs.stat Path.join(basePath, file), (err) -> + logger.log file:file, err:err, result: err?, "stating potential missing file" + cb(err?) + async.filterSeries missingFileCandidates, fileDoesNotExist, callback else - callback() + callback([]) From b03271edee0e2aac188a28ccf6c042f1b363b229 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 15 Sep 2017 13:42:57 +0100 Subject: [PATCH 302/754] unit tests for ResourceStateManager --- .../coffee/ResourceStateManagerTests.coffee | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 services/clsi/test/unit/coffee/ResourceStateManagerTests.coffee diff --git a/services/clsi/test/unit/coffee/ResourceStateManagerTests.coffee b/services/clsi/test/unit/coffee/ResourceStateManagerTests.coffee new file mode 100644 index 0000000000..6c55a1d0be --- /dev/null +++ b/services/clsi/test/unit/coffee/ResourceStateManagerTests.coffee @@ -0,0 +1,124 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +should = require('chai').should() +modulePath = require('path').join __dirname, '../../../app/js/ResourceStateManager' +Path = require "path" +Errors = require "../../../app/js/Errors" + +describe "ResourceStateManager", -> + beforeEach -> + @ResourceStateManager = SandboxedModule.require modulePath, requires: + "fs": @fs = {} + "logger-sharelatex": {log: sinon.stub(), err: sinon.stub()} + "./SafeReader": @SafeReader = {} + @basePath = "/path/to/write/files/to" + @resources = [ + {path: "resource-1-mock"} + {path: "resource-2-mock"} + {path: "resource-3-mock"} + ] + @state = "1234567890" + @resourceFileName = "#{@basePath}/.project-sync-state" + @resourceFileContents = "#{@resources[0].path}\n#{@resources[1].path}\n#{@resources[2].path}\nstateHash:#{@state}" + @callback = sinon.stub() + + describe "saveProjectState", -> + beforeEach -> + @fs.writeFile = sinon.stub().callsArg(2) + + describe "when the state is specified", -> + beforeEach -> + @ResourceStateManager.saveProjectState(@state, @resources, @basePath, @callback) + + it "should write the resource list to disk", -> + @fs.writeFile + .calledWith(@resourceFileName, @resourceFileContents) + .should.equal true + + it "should call the callback", -> + @callback.called.should.equal true + + describe "when the state is undefined", -> + beforeEach -> + @state = undefined + @fs.unlink = sinon.stub().callsArg(1) + @ResourceStateManager.saveProjectState(@state, @resources, @basePath, @callback) + + it "should unlink the resource file", -> + @fs.unlink + .calledWith(@resourceFileName) + .should.equal true + + it "should not write the resource list to disk", -> + @fs.writeFile.called.should.equal false + + it "should call the callback", -> + @callback.called.should.equal true + + describe "checkProjectStateMatches", -> + + describe "when the state matches", -> + beforeEach -> + @SafeReader.readFile = sinon.stub().callsArgWith(3, null, @resourceFileContents) + @ResourceStateManager.checkProjectStateMatches(@state, @basePath, @callback) + + it "should read the resource file", -> + @SafeReader.readFile + .calledWith(@resourceFileName) + .should.equal true + + it "should call the callback with the results", -> + @callback.calledWithMatch(null, @resources).should.equal true + + describe "when the state does not match", -> + beforeEach -> + @SafeReader.readFile = sinon.stub().callsArgWith(3, null, @resourceFileContents) + @ResourceStateManager.checkProjectStateMatches("not-the-original-state", @basePath, @callback) + + it "should call the callback with an error", -> + error = new Errors.FilesOutOfSyncError("invalid state for incremental update") + @callback.calledWith(error).should.equal true + + describe "checkResourceFiles", -> + describe "when all the files are present", -> + beforeEach -> + @allFiles = [ @resources[0].path, @resources[1].path, @resources[2].path] + @ResourceStateManager.checkResourceFiles(@resources, @allFiles, @basePath, @callback) + + it "should call the callback", -> + @callback.calledWithExactly().should.equal true + + describe "when there is a file missing from the outputFileFinder but present on disk", -> + beforeEach -> + @allFiles = [ @resources[0].path, @resources[1].path] + @fs.stat = sinon.stub().callsArg(1) + @ResourceStateManager.checkResourceFiles(@resources, @allFiles, @basePath, @callback) + + it "should stat the file to see if it is present", -> + @fs.stat.called.should.equal true + + it "should call the callback", -> + @callback.calledWithExactly().should.equal true + + describe "when there is a missing file", -> + beforeEach -> + @allFiles = [ @resources[0].path, @resources[1].path] + @fs.stat = sinon.stub().callsArgWith(1, new Error()) + @ResourceStateManager.checkResourceFiles(@resources, @allFiles, @basePath, @callback) + + it "should stat the file to see if it is present", -> + @fs.stat.called.should.equal true + + it "should call the callback with an error", -> + error = new Errors.FilesOutOfSyncError("resource files missing in incremental update") + @callback.calledWith(error).should.equal true + + describe "when a resource contains a relative path", -> + beforeEach -> + @resources[0].path = "../foo/bar.tex" + @allFiles = [ @resources[0].path, @resources[1].path, @resources[2].path] + @ResourceStateManager.checkResourceFiles(@resources, @allFiles, @basePath, @callback) + + it "should call the callback with an error", -> + @callback.calledWith(new Error("relative path in resource file list")).should.equal true + From 7f0e6f3eec781e776fc3305d05d279d2dcad9b53 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 22 Sep 2017 16:19:33 +0100 Subject: [PATCH 303/754] lock compile directory --- .../clsi/app/coffee/CompileController.coffee | 7 ++- .../clsi/app/coffee/CompileManager.coffee | 13 +++++ services/clsi/app/coffee/Errors.coffee | 8 +++ services/clsi/app/coffee/LockManager.coffee | 23 ++++++++ .../clsi/app/coffee/ResourceWriter.coffee | 2 +- services/clsi/package.json | 1 + .../unit/coffee/CompileControllerTests.coffee | 10 ++-- .../unit/coffee/CompileManagerTests.coffee | 41 ++++++++++++++ .../clsi/test/unit/coffee/LockManager.coffee | 54 +++++++++++++++++++ 9 files changed, 151 insertions(+), 8 deletions(-) create mode 100644 services/clsi/app/coffee/LockManager.coffee create mode 100644 services/clsi/test/unit/coffee/LockManager.coffee diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index f4dcb0ec66..60c5c64c0b 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -15,8 +15,11 @@ module.exports = CompileController = request.user_id = req.params.user_id if req.params.user_id? ProjectPersistenceManager.markProjectAsJustAccessed request.project_id, (error) -> return next(error) if error? - CompileManager.doCompile request, (error, outputFiles = []) -> - if error instanceof Errors.FilesOutOfSyncError + CompileManager.doCompileWithLock request, (error, outputFiles = []) -> + if error instanceof Errors.AlreadyCompilingError + code = 423 # Http 443 Locked + status = "compile-in-progress" + else if error instanceof Errors.FilesOutOfSyncError code = 409 # Http 409 Conflict status = "retry" else if error?.terminated diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 56d04db737..c4f3c7bfda 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -9,6 +9,7 @@ Metrics = require "./Metrics" child_process = require "child_process" DraftModeManager = require "./DraftModeManager" TikzManager = require "./TikzManager" +LockManager = require "./LockManager" fs = require("fs") fse = require "fs-extra" os = require("os") @@ -26,6 +27,18 @@ getCompileDir = (project_id, user_id) -> Path.join(Settings.path.compilesDir, getCompileName(project_id, user_id)) module.exports = CompileManager = + + doCompileWithLock: (request, callback = (error, outputFiles) ->) -> + compileDir = getCompileDir(request.project_id, request.user_id) + lockFile = Path.join(compileDir, ".project-lock") + # use a .project-lock file in the compile directory to prevent + # simultaneous compiles + fse.ensureDir compileDir, (error) -> + return callback(error) if error? + LockManager.runWithLock lockFile, (releaseLock) -> + CompileManager.doCompile(request, releaseLock) + , callback + doCompile: (request, callback = (error, outputFiles) ->) -> compileDir = getCompileDir(request.project_id, request.user_id) diff --git a/services/clsi/app/coffee/Errors.coffee b/services/clsi/app/coffee/Errors.coffee index 2e3ae7597d..b375513e1c 100644 --- a/services/clsi/app/coffee/Errors.coffee +++ b/services/clsi/app/coffee/Errors.coffee @@ -12,6 +12,14 @@ FilesOutOfSyncError = (message) -> return error FilesOutOfSyncError.prototype.__proto__ = Error.prototype +AlreadyCompilingError = (message) -> + error = new Error(message) + error.name = "AlreadyCompilingError" + error.__proto__ = AlreadyCompilingError.prototype + return error +AlreadyCompilingError.prototype.__proto__ = Error.prototype + module.exports = Errors = NotFoundError: NotFoundError FilesOutOfSyncError: FilesOutOfSyncError + AlreadyCompilingError: AlreadyCompilingError diff --git a/services/clsi/app/coffee/LockManager.coffee b/services/clsi/app/coffee/LockManager.coffee new file mode 100644 index 0000000000..5d6fa4626d --- /dev/null +++ b/services/clsi/app/coffee/LockManager.coffee @@ -0,0 +1,23 @@ +Settings = require('settings-sharelatex') +logger = require "logger-sharelatex" +Lockfile = require('lockfile') # from https://github.com/npm/lockfile +Errors = require "./Errors" + +module.exports = LockManager = + LOCK_TEST_INTERVAL: 1000 # 50ms between each test of the lock + MAX_LOCK_WAIT_TIME: 15000 # 10s maximum time to spend trying to get the lock + LOCK_STALE: 5*60*1000 # 5 mins time until lock auto expires + + runWithLock: (path, runner = ((releaseLock = (error) ->) ->), callback = ((error) ->)) -> + lockOpts = + wait: @MAX_LOCK_WAIT_TIME + pollPeriod: @LOCK_TEST_INTERVAL + stale: @LOCK_STALE + Lockfile.lock path, lockOpts, (error) -> + return callback new Errors.AlreadyCompilingError("compile in progress") if error?.code is 'EEXIST' + return callback(error) if error? + runner (error1, args...) -> + Lockfile.unlock path, (error2) -> + error = error1 or error2 + return callback(error) if error? + callback(null, args...) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index f9e90b036a..06d5692f0a 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -78,7 +78,7 @@ module.exports = ResourceWriter = should_delete = true if path.match(/^output\./) or path.match(/\.aux$/) or path.match(/^cache\//) # knitr cache should_delete = false - if path == '.project-sync-state' + if path == '.project-sync-state' or path == '.project-lock' should_delete = false if path == "output.pdf" or path == "output.dvi" or path == "output.log" or path == "output.xdv" should_delete = true diff --git a/services/clsi/package.json b/services/clsi/package.json index 56843026e7..71711be1a7 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -14,6 +14,7 @@ "fs-extra": "^0.16.3", "grunt-mkdir": "^1.0.0", "heapdump": "^0.3.5", + "lockfile": "^1.0.3", "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", "lynx": "0.0.11", "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.5.0", diff --git a/services/clsi/test/unit/coffee/CompileControllerTests.coffee b/services/clsi/test/unit/coffee/CompileControllerTests.coffee index 1fc6a99bd5..7b6001d0fa 100644 --- a/services/clsi/test/unit/coffee/CompileControllerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileControllerTests.coffee @@ -49,7 +49,7 @@ describe "CompileController", -> describe "successfully", -> beforeEach -> - @CompileManager.doCompile = sinon.stub().callsArgWith(1, null, @output_files) + @CompileManager.doCompileWithLock = sinon.stub().callsArgWith(1, null, @output_files) @CompileController.compile @req, @res it "should parse the request", -> @@ -58,7 +58,7 @@ describe "CompileController", -> .should.equal true it "should run the compile for the specified project", -> - @CompileManager.doCompile + @CompileManager.doCompileWithLock .calledWith(@request_with_project_id) .should.equal true @@ -84,7 +84,7 @@ describe "CompileController", -> describe "with an error", -> beforeEach -> - @CompileManager.doCompile = sinon.stub().callsArgWith(1, new Error(@message = "error message"), null) + @CompileManager.doCompileWithLock = sinon.stub().callsArgWith(1, new Error(@message = "error message"), null) @CompileController.compile @req, @res it "should return the JSON response with the error", -> @@ -102,7 +102,7 @@ describe "CompileController", -> beforeEach -> @error = new Error(@message = "container timed out") @error.timedout = true - @CompileManager.doCompile = sinon.stub().callsArgWith(1, @error, null) + @CompileManager.doCompileWithLock = sinon.stub().callsArgWith(1, @error, null) @CompileController.compile @req, @res it "should return the JSON response with the timeout status", -> @@ -118,7 +118,7 @@ describe "CompileController", -> describe "when the request returns no output files", -> beforeEach -> - @CompileManager.doCompile = sinon.stub().callsArgWith(1, null, []) + @CompileManager.doCompileWithLock = sinon.stub().callsArgWith(1, null, []) @CompileController.compile @req, @res it "should return the JSON response with the failure status", -> diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index ff671b27c3..b07a02cd85 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -19,9 +19,50 @@ describe "CompileManager", -> "./CommandRunner": @CommandRunner = {} "./DraftModeManager": @DraftModeManager = {} "./TikzManager": @TikzManager = {} + "./LockManager": @LockManager = {} "fs": @fs = {} @callback = sinon.stub() + describe "doCompileWithLock", -> + beforeEach -> + @request = + resources: @resources = "mock-resources" + project_id: @project_id = "project-id-123" + user_id: @user_id = "1234" + @output_files = ["foo", "bar"] + @CompileManager.doCompile = sinon.stub().callsArgWith(1, null, @output_files) + @LockManager.runWithLock = (lockFile, runner, callback) -> + runner (err, result...) -> + callback(err, result...) + + describe "when the project is not locked", -> + beforeEach -> + @CompileManager.doCompileWithLock @request, @callback + + it "should call doCompile with the request", -> + @CompileManager.doCompile + .calledWith(@request) + .should.equal true + + it "should call the callback with the output files", -> + @callback.calledWithExactly(null, @output_files) + .should.equal true + + describe "when the project is locked", -> + beforeEach -> + @error = new Error("locked") + @LockManager.runWithLock = (lockFile, runner, callback) => + callback(@error) + @CompileManager.doCompileWithLock @request, @callback + + it "should not call doCompile with the request", -> + @CompileManager.doCompile + .called.should.equal false + + it "should call the callback with the error", -> + @callback.calledWithExactly(@error) + .should.equal true + describe "doCompile", -> beforeEach -> @output_files = [{ diff --git a/services/clsi/test/unit/coffee/LockManager.coffee b/services/clsi/test/unit/coffee/LockManager.coffee new file mode 100644 index 0000000000..c1071a5841 --- /dev/null +++ b/services/clsi/test/unit/coffee/LockManager.coffee @@ -0,0 +1,54 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +require('chai').should() +modulePath = require('path').join __dirname, '../../../app/js/LockManager' +Path = require "path" +Errors = require "../../../app/js/Errors" + +describe "LockManager", -> + beforeEach -> + @LockManager = SandboxedModule.require modulePath, requires: + "settings-sharelatex": {} + "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } + "lockfile": @Lockfile = {} + @lockFile = "/local/compile/directory/.project-lock" + + describe "runWithLock", -> + beforeEach -> + @runner = sinon.stub().callsArgWith(0, null, "foo", "bar") + @callback = sinon.stub() + + describe "normally", -> + beforeEach -> + @Lockfile.lock = sinon.stub().callsArgWith(2, null) + @Lockfile.unlock = sinon.stub().callsArgWith(1, null) + @LockManager.runWithLock @lockFile, @runner, @callback + + it "should run the compile", -> + @runner + .calledWith() + .should.equal true + + it "should call the callback with the response from the compile", -> + @callback + .calledWithExactly(null, "foo", "bar") + .should.equal true + + describe "when the project is locked", -> + beforeEach -> + @error = new Error() + @error.code = "EEXIST" + @Lockfile.lock = sinon.stub().callsArgWith(2,@error) + @Lockfile.unlock = sinon.stub().callsArgWith(1, null) + @LockManager.runWithLock @lockFile, @runner, @callback + + it "should not run the compile", -> + @runner + .called + .should.equal false + + it "should return an error", -> + error = new Errors.AlreadyCompilingError() + @callback + .calledWithExactly(error) + .should.equal true From 8685b774ee02f9e7f2891e634189dfc6c9dc2df7 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Mon, 25 Sep 2017 15:28:31 +0100 Subject: [PATCH 304/754] fix unit tests for use of fs-extra --- .../clsi/test/unit/coffee/CompileManagerTests.coffee | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index b07a02cd85..591939d68e 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -21,6 +21,7 @@ describe "CompileManager", -> "./TikzManager": @TikzManager = {} "./LockManager": @LockManager = {} "fs": @fs = {} + "fs-extra": @fse = { ensureDir: sinon.stub().callsArg(1) } @callback = sinon.stub() describe "doCompileWithLock", -> @@ -30,6 +31,8 @@ describe "CompileManager", -> project_id: @project_id = "project-id-123" user_id: @user_id = "1234" @output_files = ["foo", "bar"] + @Settings.compileDir = "compiles" + @compileDir = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}" @CompileManager.doCompile = sinon.stub().callsArgWith(1, null, @output_files) @LockManager.runWithLock = (lockFile, runner, callback) -> runner (err, result...) -> @@ -39,6 +42,10 @@ describe "CompileManager", -> beforeEach -> @CompileManager.doCompileWithLock @request, @callback + it "should ensure that the compile directory exists", -> + @fse.ensureDir.calledWith(@compileDir) + .should.equal true + it "should call doCompile with the request", -> @CompileManager.doCompile .calledWith(@request) @@ -55,6 +62,10 @@ describe "CompileManager", -> callback(@error) @CompileManager.doCompileWithLock @request, @callback + it "should ensure that the compile directory exists", -> + @fse.ensureDir.calledWith(@compileDir) + .should.equal true + it "should not call doCompile with the request", -> @CompileManager.doCompile .called.should.equal false From d464556f745d5194f4e5a4b898cece9e66846d4a Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Mon, 25 Sep 2017 16:06:45 +0100 Subject: [PATCH 305/754] fix comment --- services/clsi/app/coffee/CompileController.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 60c5c64c0b..99973fdd10 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -17,7 +17,7 @@ module.exports = CompileController = return next(error) if error? CompileManager.doCompileWithLock request, (error, outputFiles = []) -> if error instanceof Errors.AlreadyCompilingError - code = 423 # Http 443 Locked + code = 423 # Http 423 Locked status = "compile-in-progress" else if error instanceof Errors.FilesOutOfSyncError code = 409 # Http 409 Conflict From d46943a7bb9fe3add4694b004f7cf4e87c5c33c6 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 26 Sep 2017 09:47:29 +0100 Subject: [PATCH 306/754] only exclude clsi-specific files from output list --- services/clsi/app/coffee/OutputFileFinder.coffee | 6 ++++-- services/clsi/app/coffee/ResourceWriter.coffee | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/clsi/app/coffee/OutputFileFinder.coffee b/services/clsi/app/coffee/OutputFileFinder.coffee index 80bd07f2cb..4b07f6e13a 100644 --- a/services/clsi/app/coffee/OutputFileFinder.coffee +++ b/services/clsi/app/coffee/OutputFileFinder.coffee @@ -29,8 +29,10 @@ module.exports = OutputFileFinder = callback = (error, fileList) -> _callback(error, fileList) _callback = () -> - - args = [directory, "-name", ".*", "-prune", "-o", "-type", "f", "-print"] + + # don't include clsi-specific files/directories in the output list + EXCLUDE_DIRS = ["-name", ".cache", "-o", "-name", ".archive","-o", "-name", ".project-*"] + args = [directory, "(", EXCLUDE_DIRS..., ")", "-prune", "-o", "-type", "f", "-print"] logger.log args: args, "running find command" proc = spawn("find", args) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index f9e90b036a..55970ee850 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -78,8 +78,6 @@ module.exports = ResourceWriter = should_delete = true if path.match(/^output\./) or path.match(/\.aux$/) or path.match(/^cache\//) # knitr cache should_delete = false - if path == '.project-sync-state' - should_delete = false if path == "output.pdf" or path == "output.dvi" or path == "output.log" or path == "output.xdv" should_delete = true if path == "output.tex" # created by TikzManager if present in output files From 2a23082c4e899dfa89ffd0e5f82893d644838c4f Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 26 Sep 2017 09:48:09 +0100 Subject: [PATCH 307/754] remove stat test for missing files --- .../app/coffee/ResourceStateManager.coffee | 23 ++++--------------- .../coffee/ResourceStateManagerTests.coffee | 15 ------------ 2 files changed, 5 insertions(+), 33 deletions(-) diff --git a/services/clsi/app/coffee/ResourceStateManager.coffee b/services/clsi/app/coffee/ResourceStateManager.coffee index e250e6447c..fbd4c67736 100644 --- a/services/clsi/app/coffee/ResourceStateManager.coffee +++ b/services/clsi/app/coffee/ResourceStateManager.coffee @@ -4,7 +4,6 @@ logger = require "logger-sharelatex" settings = require("settings-sharelatex") Errors = require "./Errors" SafeReader = require "./SafeReader" -async = require "async" module.exports = ResourceStateManager = @@ -61,21 +60,9 @@ module.exports = ResourceStateManager = seenFile = {} for file in allFiles seenFile[file] = true - missingFileCandidates = (resource.path for resource in resources when not seenFile[resource.path]) - # now check if they are really missing - ResourceStateManager._checkMissingFiles missingFileCandidates, basePath, (missingFiles) -> - if missingFiles?.length > 0 - logger.err missingFiles:missingFiles, basePath:basePath, allFiles:allFiles, resources:resources, "missing input files for project" - return callback new Errors.FilesOutOfSyncError("resource files missing in incremental update") - else - callback() - - _checkMissingFiles: (missingFileCandidates, basePath, callback = (missingFiles) ->) -> - if missingFileCandidates.length > 0 - fileDoesNotExist = (file, cb) -> - fs.stat Path.join(basePath, file), (err) -> - logger.log file:file, err:err, result: err?, "stating potential missing file" - cb(err?) - async.filterSeries missingFileCandidates, fileDoesNotExist, callback + missingFiles = (resource.path for resource in resources when not seenFile[resource.path]) + if missingFiles?.length > 0 + logger.err missingFiles:missingFiles, basePath:basePath, allFiles:allFiles, resources:resources, "missing input files for project" + return callback new Errors.FilesOutOfSyncError("resource files missing in incremental update") else - callback([]) + callback() diff --git a/services/clsi/test/unit/coffee/ResourceStateManagerTests.coffee b/services/clsi/test/unit/coffee/ResourceStateManagerTests.coffee index 6c55a1d0be..e5e1c13011 100644 --- a/services/clsi/test/unit/coffee/ResourceStateManagerTests.coffee +++ b/services/clsi/test/unit/coffee/ResourceStateManagerTests.coffee @@ -88,27 +88,12 @@ describe "ResourceStateManager", -> it "should call the callback", -> @callback.calledWithExactly().should.equal true - describe "when there is a file missing from the outputFileFinder but present on disk", -> - beforeEach -> - @allFiles = [ @resources[0].path, @resources[1].path] - @fs.stat = sinon.stub().callsArg(1) - @ResourceStateManager.checkResourceFiles(@resources, @allFiles, @basePath, @callback) - - it "should stat the file to see if it is present", -> - @fs.stat.called.should.equal true - - it "should call the callback", -> - @callback.calledWithExactly().should.equal true - describe "when there is a missing file", -> beforeEach -> @allFiles = [ @resources[0].path, @resources[1].path] @fs.stat = sinon.stub().callsArgWith(1, new Error()) @ResourceStateManager.checkResourceFiles(@resources, @allFiles, @basePath, @callback) - it "should stat the file to see if it is present", -> - @fs.stat.called.should.equal true - it "should call the callback with an error", -> error = new Errors.FilesOutOfSyncError("resource files missing in incremental update") @callback.calledWith(error).should.equal true From ef0db811e195f269af33f0b83b5159319026dff4 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 26 Sep 2017 10:42:59 +0100 Subject: [PATCH 308/754] exclude hidden files from output express static server doesn't serve them and rejects with 404 --- services/clsi/app/coffee/OutputCacheManager.coffee | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee index 465b0431d9..41786af905 100644 --- a/services/clsi/app/coffee/OutputCacheManager.coffee +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -63,6 +63,11 @@ module.exports = OutputCacheManager = # copy all the output files into the new cache directory results = [] async.mapSeries outputFiles, (file, cb) -> + # don't send dot files as output, express doesn't serve them + if file?.path?.match(/^\.|\/./) + logger.warn compileDir: compileDir, path: file.path, "ignoring dotfile in output" + return cb() + # copy other files into cache directory if valid newFile = _.clone(file) [src, dst] = [Path.join(compileDir, file.path), Path.join(cacheDir, file.path)] OutputCacheManager._checkFileIsSafe src, (err, isSafe) -> From a7cb7e6e4c46d4b0974fd728c0fa1572279adcfa Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 26 Sep 2017 11:03:20 +0100 Subject: [PATCH 309/754] use a separate function for hidden file check --- services/clsi/app/coffee/OutputCacheManager.coffee | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee index 41786af905..23e179c425 100644 --- a/services/clsi/app/coffee/OutputCacheManager.coffee +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -64,7 +64,7 @@ module.exports = OutputCacheManager = results = [] async.mapSeries outputFiles, (file, cb) -> # don't send dot files as output, express doesn't serve them - if file?.path?.match(/^\.|\/./) + if OutputCacheManager._fileIsHidden(file.path) logger.warn compileDir: compileDir, path: file.path, "ignoring dotfile in output" return cb() # copy other files into cache directory if valid @@ -149,6 +149,9 @@ module.exports = OutputCacheManager = removeDir dir, cb , callback + _fileIsHidden: (path) -> + return path?.match(/^\.|\/./)? + _checkFileIsSafe: (src, callback = (error, isSafe) ->) -> # check if we have a valid file to copy into the cache fs.stat src, (err, stats) -> From 33dfe5b2a2c2ce59b915c9ae6c6375b908d34148 Mon Sep 17 00:00:00 2001 From: Joe Green <joe.green0991@gmail.com> Date: Tue, 26 Sep 2017 11:44:48 +0100 Subject: [PATCH 310/754] Update Jenkinsfile --- services/clsi/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index 97efd51f20..9360d1f336 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -30,7 +30,7 @@ pipeline { sh 'rm -fr ./_docker-runner' sh 'npm install' sh 'npm rebuild' - sh 'npm install --quiet grunt-cli sqlite3' + sh 'npm install --quiet grunt-cli' } } stage('Compile and Test') { From 4ff6cd3006f7d05a2c73fc9e6099051370ac0f04 Mon Sep 17 00:00:00 2001 From: Joe Green <joe.green0991@gmail.com> Date: Thu, 28 Sep 2017 11:50:33 +0100 Subject: [PATCH 311/754] Jg jenkinsfile cleanup (#75) * Update Jenkinsfile make sure we don't ship unneeded build files * Update ExampleDocumentTests.coffee * use node 6.11.2 in jenkins file --- services/clsi/Jenkinsfile | 19 +++++++++++-------- .../coffee/ExampleDocumentTests.coffee | 3 +++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index 9360d1f336..e713fa7344 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -12,13 +12,14 @@ pipeline { steps { // This is a terrible hack to set the file ownership to jenkins:jenkins so we can cleanup the directory sh 'docker run -v $(pwd):/app --rm busybox /bin/chown -R 111:119 /app' + sh 'rm -fr node_modules' } } stage('Install') { agent { docker { - image 'node:4.2.1' - args "-v /var/lib/jenkins/.npm:/tmp/.npm -e HOME=/tmp" + image 'node:6.11.2' + args "-e HOME=/tmp" reuseNode true } } @@ -27,16 +28,15 @@ pipeline { sh 'rm -fr node_modules' checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: '_docker-runner'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/docker-runner-sharelatex']]]) sh 'npm install ./_docker-runner' - sh 'rm -fr ./_docker-runner' + sh 'rm -fr ./_docker-runner ./_docker-runner@tmp' sh 'npm install' - sh 'npm rebuild' sh 'npm install --quiet grunt-cli' } } stage('Compile and Test') { agent { docker { - image 'node:4.2.1' + image 'node:6.11.2' reuseNode true } } @@ -51,12 +51,15 @@ pipeline { TEXLIVE_IMAGE="quay.io/sharelatex/texlive-full:2017.1" } steps { - sh 'mkdir -p compiles' + sh 'mkdir -p compiles cache' // Not yet running, due to volumes/sibling containers sh 'docker container prune -f' sh 'docker pull $TEXLIVE_IMAGE' - sh 'docker pull sharelatex/acceptance-test-runner:clsi-4.2.1' - sh 'docker run --rm -e SIBLING_CONTAINER_USER=root -e SANDBOXED_COMPILES_HOST_DIR=$(pwd)/compiles -e SANDBOXED_COMPILES_SIBLING_CONTAINERS=true -e TEXLIVE_IMAGE=$TEXLIVE_IMAGE -v /var/run/docker.sock:/var/run/docker.sock -v $(pwd):/app sharelatex/acceptance-test-runner:clsi-4.2.1' + sh 'docker pull sharelatex/acceptance-test-runner:clsi-6.11.2' + sh 'docker run --rm -e SIBLING_CONTAINER_USER=root -e SANDBOXED_COMPILES_HOST_DIR=$(pwd)/compiles -e SANDBOXED_COMPILES_SIBLING_CONTAINERS=true -e TEXLIVE_IMAGE=$TEXLIVE_IMAGE -v /var/run/docker.sock:/var/run/docker.sock -v $(pwd):/app sharelatex/acceptance-test-runner:clsi-6.11.2' + // This is a terrible hack to set the file ownership to jenkins:jenkins so we can cleanup the directory + sh 'docker run -v $(pwd):/app --rm busybox /bin/chown -R 111:119 /app' + sh 'rm -r compiles cache server.log db.sqlite config/settings.defaults.coffee' } } stage('Package') { diff --git a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee index 4e36c6ac05..4d431c29c2 100644 --- a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee +++ b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee @@ -12,6 +12,9 @@ catch e convertToPng = (pdfPath, pngPath, callback = (error) ->) -> convert = ChildProcess.exec "convert #{fixturePath(pdfPath)} #{fixturePath(pngPath)}" + stdout = "" + convert.stdout.on "data", (chunk) -> console.log "STDOUT", chunk.toString() + convert.stderr.on "data", (chunk) -> console.log "STDERR", chunk.toString() convert.on "exit", () -> callback() From 273de10eb49ee8919e0589c13ed9a2110107a875 Mon Sep 17 00:00:00 2001 From: Joe Green <joe.green0991@gmail.com> Date: Thu, 28 Sep 2017 11:51:41 +0100 Subject: [PATCH 312/754] use npm cache in CI build --- services/clsi/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index e713fa7344..4755748315 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -19,7 +19,7 @@ pipeline { agent { docker { image 'node:6.11.2' - args "-e HOME=/tmp" + args "-v /var/lib/jenkins/.npm:/tmp/.npm -e HOME=/tmp" reuseNode true } } From dc0b7d5dbab06352bbf062eeda0595b23a03a344 Mon Sep 17 00:00:00 2001 From: Joe Green <joe.green0991@gmail.com> Date: Thu, 28 Sep 2017 13:46:01 +0100 Subject: [PATCH 313/754] Update Jenkinsfile --- services/clsi/Jenkinsfile | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index 4755748315..0c289e15da 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -30,6 +30,7 @@ pipeline { sh 'npm install ./_docker-runner' sh 'rm -fr ./_docker-runner ./_docker-runner@tmp' sh 'npm install' + sh 'npm rebuild' sh 'npm install --quiet grunt-cli' } } From 2bdba15bd5aae59a8792a0aba0d9dd7843e4fe17 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Thu, 28 Sep 2017 16:36:25 +0100 Subject: [PATCH 314/754] keep tikzexternalize files --- services/clsi/app/coffee/ResourceWriter.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index 55970ee850..0b6aef5b72 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -78,6 +78,8 @@ module.exports = ResourceWriter = should_delete = true if path.match(/^output\./) or path.match(/\.aux$/) or path.match(/^cache\//) # knitr cache should_delete = false + if path.match(/^output-.*/) # Tikz cached figures + should_delete = false if path == "output.pdf" or path == "output.dvi" or path == "output.log" or path == "output.xdv" should_delete = true if path == "output.tex" # created by TikzManager if present in output files From e209c48bd9f5d36747f111a873160c5abd37cc0d Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 29 Sep 2017 17:00:53 +0100 Subject: [PATCH 315/754] simplify tikzexternalize checks --- .../clsi/app/coffee/CompileManager.coffee | 10 ++- services/clsi/app/coffee/TikzManager.coffee | 27 +++---- .../unit/coffee/CompileManagerTests.coffee | 2 +- .../clsi/test/unit/coffee/TikzManager.coffee | 73 +++++++++++++------ 4 files changed, 70 insertions(+), 42 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index c4f3c7bfda..167a80e135 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -62,10 +62,12 @@ module.exports = CompileManager = callback() createTikzFileIfRequired = (callback) -> - if TikzManager.needsOutputFile(request.rootResourcePath, resourceList) - TikzManager.injectOutputFile compileDir, request.rootResourcePath, callback - else - callback() + TikzManager.checkMainFile compileDir, request.rootResourcePath, resourceList, (error, usesTikzExternalize) -> + return callback(error) if error? + if usesTikzExternalize + TikzManager.injectOutputFile compileDir, request.rootResourcePath, callback + else + callback() # set up environment variables for chktex env = {} diff --git a/services/clsi/app/coffee/TikzManager.coffee b/services/clsi/app/coffee/TikzManager.coffee index 5c08f205fd..07f87e3cff 100644 --- a/services/clsi/app/coffee/TikzManager.coffee +++ b/services/clsi/app/coffee/TikzManager.coffee @@ -1,6 +1,7 @@ fs = require "fs" Path = require "path" ResourceWriter = require "./ResourceWriter" +SafeReader = require "./SafeReader" logger = require "logger-sharelatex" # for \tikzexternalize to work the main file needs to match the @@ -8,25 +9,21 @@ logger = require "logger-sharelatex" # copy of the main file as 'output.tex'. module.exports = TikzManager = - needsOutputFile: (rootResourcePath, resources) -> + + checkMainFile: (compileDir, mainFile, resources, callback = (error, usesTikzExternalize) ->) -> # if there's already an output.tex file, we don't want to touch it for resource in resources if resource.path is "output.tex" - return false + logger.log compileDir: compileDir, mainFile: mainFile, "output.tex already in resources" + return callback(null, false) # if there's no output.tex, see if we are using tikz/pgf in the main file - for resource in resources - if resource.path is rootResourcePath - return TikzManager._includesTikz (resource) - # otherwise false - return false - - _includesTikz: (resource) -> - # check if we are using tikz externalize - content = resource.content?.slice(0,65536) - if content?.indexOf("\\tikzexternalize") >= 0 - return true - else - return false + ResourceWriter.checkPath compileDir, mainFile, (error, path) -> + return callback(error) if error? + SafeReader.readFile path, 65536, "utf8", (error, content) -> + return callback(error) if error? + usesTikzExternalize = content?.indexOf("\\tikzexternalize") >= 0 + logger.log compileDir: compileDir, mainFile: mainFile, usesTikzExternalize:usesTikzExternalize, "checked for tikzexternalize" + callback null, usesTikzExternalize injectOutputFile: (compileDir, mainFile, callback = (error) ->) -> ResourceWriter.checkPath compileDir, mainFile, (error, path) -> diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 591939d68e..341ce2d0fe 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -108,7 +108,7 @@ describe "CompileManager", -> @OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, @output_files) @OutputCacheManager.saveOutputFiles = sinon.stub().callsArgWith(2, null, @build_files) @DraftModeManager.injectDraftMode = sinon.stub().callsArg(1) - @TikzManager.needsOutputFile = sinon.stub().returns(false) + @TikzManager.checkMainFile = sinon.stub().callsArg(3, false) describe "normally", -> beforeEach -> diff --git a/services/clsi/test/unit/coffee/TikzManager.coffee b/services/clsi/test/unit/coffee/TikzManager.coffee index 816b3b10cf..5a3ec5ce5a 100644 --- a/services/clsi/test/unit/coffee/TikzManager.coffee +++ b/services/clsi/test/unit/coffee/TikzManager.coffee @@ -7,34 +7,63 @@ describe 'TikzManager', -> beforeEach -> @TikzManager = SandboxedModule.require modulePath, requires: "./ResourceWriter": @ResourceWriter = {} + "./SafeReader": @SafeReader = {} "fs": @fs = {} "logger-sharelatex": @logger = {log: () ->} - describe "needsOutputFile", -> - it "should return true if there is a \\tikzexternalize", -> - @TikzManager.needsOutputFile("main.tex", [ - { path: 'foo.tex' }, - { path: 'main.tex', content:'foo \\usepackage{tikz} \\tikzexternalize' } - ]).should.equal true + describe "checkMainFile", -> + beforeEach -> + @compileDir = "compile-dir" + @mainFile = "main.tex" + @callback = sinon.stub() - it "should return false if there is no \\tikzexternalize", -> - @TikzManager.needsOutputFile("main.tex", [ - { path: 'foo.tex' }, - { path: 'main.tex', content:'foo \\usepackage{tikz}' } - ]).should.equal false + describe "if there is already an output.tex file in the resources", -> + beforeEach -> + @resources = [{path:"main.tex"},{path:"output.tex"}] + @TikzManager.checkMainFile @compileDir, @mainFile, @resources, @callback - it "should return false if there is already an output.tex file", -> - @TikzManager.needsOutputFile("main.tex", [ - { path: 'foo.tex' }, - { path: 'main.tex', content:'foo \\usepackage{tikz} \\tikzexternalize' }, - { path: 'output.tex' } - ]).should.equal false + it "should call the callback with false ", -> + @callback.calledWithExactly(null, false) + .should.equal true - it "should return false if the file has no content", -> - @TikzManager.needsOutputFile("main.tex", [ - { path: 'foo.tex' }, - { path: 'main.tex' } - ]).should.equal false + describe "if there is no output.tex file in the resources", -> + beforeEach -> + @resources = [{path:"main.tex"}] + @ResourceWriter.checkPath = sinon.stub() + .withArgs(@compileDir, @mainFile) + .callsArgWith(2, null, "#{@compileDir}/#{@mainFile}") + + describe "and the main file contains tikzexternalize", -> + beforeEach -> + @SafeReader.readFile = sinon.stub() + .withArgs("#{@compileDir}/#{@mainFile}") + .callsArgWith(3, null, "hello \\tikzexternalize") + @TikzManager.checkMainFile @compileDir, @mainFile, @resources, @callback + + it "should look at the file on disk", -> + @SafeReader.readFile + .calledWith("#{@compileDir}/#{@mainFile}") + .should.equal true + + it "should call the callback with true ", -> + @callback.calledWithExactly(null, true) + .should.equal true + + describe "and the main file does not contain tikzexternalize", -> + beforeEach -> + @SafeReader.readFile = sinon.stub() + .withArgs("#{@compileDir}/#{@mainFile}") + .callsArgWith(3, null, "hello") + @TikzManager.checkMainFile @compileDir, @mainFile, @resources, @callback + + it "should look at the file on disk", -> + @SafeReader.readFile + .calledWith("#{@compileDir}/#{@mainFile}") + .should.equal true + + it "should call the callback with false", -> + @callback.calledWithExactly(null, false) + .should.equal true describe "injectOutputFile", -> beforeEach -> From 392e96b81d8ca4a04e1d6817772b28a2159c7065 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Mon, 2 Oct 2017 15:44:00 +0100 Subject: [PATCH 316/754] move logging from SafeReader into caller prevent unnecessary logging when looking at headers of files where hitting the end of the file is expected. --- services/clsi/app/coffee/ResourceStateManager.coffee | 6 +++++- services/clsi/app/coffee/SafeReader.coffee | 8 +++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/services/clsi/app/coffee/ResourceStateManager.coffee b/services/clsi/app/coffee/ResourceStateManager.coffee index fbd4c67736..19fea47d72 100644 --- a/services/clsi/app/coffee/ResourceStateManager.coffee +++ b/services/clsi/app/coffee/ResourceStateManager.coffee @@ -22,6 +22,7 @@ module.exports = ResourceStateManager = # files are moved, added, deleted or renamed. SYNC_STATE_FILE: ".project-sync-state" + SYNC_STATE_MAX_SIZE: 128*1024 saveProjectState: (state, resources, basePath, callback = (error) ->) -> stateFile = Path.join(basePath, @SYNC_STATE_FILE) @@ -39,8 +40,11 @@ module.exports = ResourceStateManager = checkProjectStateMatches: (state, basePath, callback = (error, resources) ->) -> stateFile = Path.join(basePath, @SYNC_STATE_FILE) - SafeReader.readFile stateFile, 128*1024, 'utf8', (err, result) -> + size = @SYNC_STATE_MAX_SIZE + SafeReader.readFile stateFile, size, 'utf8', (err, result, bytesRead) -> return callback(err) if err? + if bytesRead is size + logger.error file:stateFile, size:size, bytesRead:bytesRead, "project state file truncated" [resourceList..., oldState] = result?.toString()?.split("\n") or [] newState = "stateHash:#{state}" logger.log state:state, oldState: oldState, basePath:basePath, stateMatches: (newState is oldState), "checking sync state" diff --git a/services/clsi/app/coffee/SafeReader.coffee b/services/clsi/app/coffee/SafeReader.coffee index 91bf8a12da..adb96b1645 100644 --- a/services/clsi/app/coffee/SafeReader.coffee +++ b/services/clsi/app/coffee/SafeReader.coffee @@ -12,16 +12,14 @@ module.exports = SafeReader = return callback(err) if err? # safely return always closing the file - callbackWithClose = (err, result) -> + callbackWithClose = (err, result...) -> fs.close fd, (err1) -> return callback(err) if err? return callback(err1) if err1? - callback(null, result) + callback(null, result...) buff = new Buffer(size, 0) # fill with zeros fs.read fd, buff, 0, buff.length, 0, (err, bytesRead, buffer) -> return callbackWithClose(err) if err? result = buffer.toString(encoding, 0, bytesRead) - if bytesRead is size - logger.error file:file, size:size, bytesRead:bytesRead, "file truncated" - callbackWithClose(null, result) + callbackWithClose(null, result, bytesRead) From 3c937ce2d48e2babab880091f0b767e389258655 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Mon, 2 Oct 2017 15:45:09 +0100 Subject: [PATCH 317/754] fix typo in log message --- services/clsi/app/coffee/TikzManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/TikzManager.coffee b/services/clsi/app/coffee/TikzManager.coffee index 07f87e3cff..7605b0d277 100644 --- a/services/clsi/app/coffee/TikzManager.coffee +++ b/services/clsi/app/coffee/TikzManager.coffee @@ -30,6 +30,6 @@ module.exports = TikzManager = return callback(error) if error? fs.readFile path, "utf8", (error, content) -> return callback(error) if error? - logger.log compileDir: compileDir, mainFile: mainFile, "copied file to ouput.tex for tikz" + logger.log compileDir: compileDir, mainFile: mainFile, "copied file to output.tex for tikz" # use wx flag to ensure that output file does not already exist fs.writeFile Path.join(compileDir, "output.tex"), content, {flag:'wx'}, callback From 75836ecb0a8f09495ce91b12a92961ccbacde837 Mon Sep 17 00:00:00 2001 From: Joe Green <joe.green0991@gmail.com> Date: Thu, 12 Oct 2017 16:54:54 +0100 Subject: [PATCH 318/754] only alert on master --- services/clsi/Jenkinsfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index 0c289e15da..fb859d7235 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -83,6 +83,10 @@ pipeline { post { failure { + when { + branch 'master' + } + mail(from: "${EMAIL_ALERT_FROM}", to: "${EMAIL_ALERT_TO}", subject: "Jenkins build failed: ${JOB_NAME}:${BUILD_NUMBER}", From 45adf904b73b862798534173b0137979bdb32b29 Mon Sep 17 00:00:00 2001 From: Joe Green <joe.green0991@gmail.com> Date: Mon, 16 Oct 2017 14:13:51 +0100 Subject: [PATCH 319/754] Update Jenkinsfile --- services/clsi/Jenkinsfile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index fb859d7235..0c289e15da 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -83,10 +83,6 @@ pipeline { post { failure { - when { - branch 'master' - } - mail(from: "${EMAIL_ALERT_FROM}", to: "${EMAIL_ALERT_TO}", subject: "Jenkins build failed: ${JOB_NAME}:${BUILD_NUMBER}", From f1c59150b71dafc93b591dc5a5323ee535e532f8 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Fri, 20 Oct 2017 15:16:35 +0100 Subject: [PATCH 320/754] exit if mock server fails to start --- services/clsi/test/acceptance/coffee/helpers/Client.coffee | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/clsi/test/acceptance/coffee/helpers/Client.coffee b/services/clsi/test/acceptance/coffee/helpers/Client.coffee index 3e1a5b86d0..c67b4251b1 100644 --- a/services/clsi/test/acceptance/coffee/helpers/Client.coffee +++ b/services/clsi/test/acceptance/coffee/helpers/Client.coffee @@ -30,7 +30,10 @@ module.exports = Client = express = require("express") app = express() app.use express.static(directory) - app.listen(port, host) + app.listen(port, host).on "error", (error) -> + console.error "error starting server:", error.message + process.exit(1) + syncFromCode: (project_id, file, line, column, callback = (error, pdfPositions) ->) -> request.get { From beaa198347d40ddad6312bcc3be7d90dee0a1046 Mon Sep 17 00:00:00 2001 From: Joe Green <joe.green0991@gmail.com> Date: Wed, 29 Nov 2017 11:01:51 +0000 Subject: [PATCH 321/754] Increase smoke test interval to 30 seconds The smoke tests can sometimes take ~20 seconds to complete, which causes the http POST to time out. This should solve that problem. --- services/clsi/app.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index b99b277464..5c79b8ee8a 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -133,7 +133,7 @@ if Settings.smokeTest do runSmokeTest = -> logger.log("running smoke tests") smokeTest.run(require.resolve(__dirname + "/test/smoke/js/SmokeTests.js"))({}, resCacher) - setTimeout(runSmokeTest, 20 * 1000) + setTimeout(runSmokeTest, 30 * 1000) app.get "/health_check", (req, res)-> res.contentType(resCacher?.setContentType) From adaf742a7b07a0b6431a2773d2422ad27c7b6ce9 Mon Sep 17 00:00:00 2001 From: Joe Green <joe.green0991@gmail.com> Date: Tue, 5 Dec 2017 16:51:59 +0000 Subject: [PATCH 322/754] Add a 1 second delay to the smoke tests (#81) * Add a 1 second delay to the smoke tests Fixes a race condition where smoke tests exit before container can be attached to. See here for more info: https://github.com/overleaf/sharelatex/issues/274 * give the smoke tests additional work to do * escape slashes --- .../clsi/test/smoke/coffee/SmokeTests.coffee | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/services/clsi/test/smoke/coffee/SmokeTests.coffee b/services/clsi/test/smoke/coffee/SmokeTests.coffee index 372ca69d40..9ecf09c17b 100644 --- a/services/clsi/test/smoke/coffee/SmokeTests.coffee +++ b/services/clsi/test/smoke/coffee/SmokeTests.coffee @@ -17,10 +17,37 @@ describe "Running a compile", -> resources: [ path: "main.tex" content: """ - \\documentclass{article} - \\begin{document} - Hello world - \\end{document} +% Membrane-like surface +% Author: Yotam Avital +\\documentclass{article} +\\usepackage{tikz} +\\usetikzlibrary{calc,fadings,decorations.pathreplacing} +\\begin{document} +\\begin{tikzpicture} + \\def\\nuPi{3.1459265} + \\foreach \\i in {5,4,...,2}{% This one doesn't matter + \\foreach \\j in {3,2,...,0}{% This will crate a membrane + % with the front lipids visible + % top layer + \\pgfmathsetmacro{\\dx}{rand*0.1}% A random variance in the x coordinate + \\pgfmathsetmacro{\\dy}{rand*0.1}% A random variance in the y coordinate, + % gives a hight fill to the lipid + \\pgfmathsetmacro{\\rot}{rand*0.1}% A random variance in the + % molecule orientation + \\shade[ball color=red] ({\\i+\\dx+\\rot},{0.5*\\j+\\dy+0.4*sin(\\i*\\nuPi*10)}) circle(0.45); + \\shade[ball color=gray] (\\i+\\dx,{0.5*\\j+\\dy+0.4*sin(\\i*\\nuPi*10)-0.9}) circle(0.45); + \\shade[ball color=gray] (\\i+\\dx-\\rot,{0.5*\\j+\\dy+0.4*sin(\\i*\\nuPi*10)-1.8}) circle(0.45); + % bottom layer + \\pgfmathsetmacro{\\dx}{rand*0.1} + \\pgfmathsetmacro{\\dy}{rand*0.1} + \\pgfmathsetmacro{\\rot}{rand*0.1} + \\shade[ball color=gray] (\\i+\\dx+\\rot,{0.5*\\j+\\dy+0.4*sin(\\i*\\nuPi*10)-2.8}) circle(0.45); + \\shade[ball color=gray] (\\i+\\dx,{0.5*\\j+\\dy+0.4*sin(\\i*\\nuPi*10)-3.7}) circle(0.45); + \\shade[ball color=red] (\\i+\\dx-\\rot,{0.5*\\j+\\dy+0.4*sin(\\i*\\nuPi*10)-4.6}) circle(0.45); + } + } +\\end{tikzpicture} +\\end{document} """ ] }, (@error, @response, @body) => From 08a0c6feb4e12caf2853e06f1bc7ee8de8d0ac8d Mon Sep 17 00:00:00 2001 From: James Allen <james@sharelatex.com> Date: Fri, 29 Dec 2017 08:08:19 +0000 Subject: [PATCH 323/754] Provide hosts and siblings container as environment settings and add npm run start script --- services/clsi/config/settings.defaults.coffee | 26 ++++++++++--------- services/clsi/package.json | 4 +++ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index 4b391789c1..fb02c412ea 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -16,21 +16,10 @@ module.exports = clsiCacheDir: Path.resolve(__dirname + "/../cache") synctexBaseDir: (project_id) -> Path.join(@compilesDir, project_id) -# clsi: -# commandRunner: "docker-runner-sharelatex" -# docker: -# image: "quay.io/sharelatex/texlive-full:2017.1" -# env: -# HOME: "/tmp" -# socketPath: "/var/run/docker.sock" -# user: "tex" -# expireProjectAfterIdleMs: 24 * 60 * 60 * 1000 -# checkProjectsIntervalMs: 10 * 60 * 1000 - internal: clsi: port: 3013 - host: "localhost" + host: process.env["LISTEN_ADDRESS"] or "localhost" apis: @@ -40,3 +29,16 @@ module.exports = smokeTest: false project_cache_length_ms: 1000 * 60 * 60 * 24 parallelFileDownloads:1 + +if process.env["COMMAND_RUNNER"] + module.exports.clsi = + commandRunner: process.env["COMMAND_RUNNER"] + docker: + image: process.env["TEXLIVE_IMAGE"] or "quay.io/sharelatex/texlive-full:2017.1" + env: + HOME: "/tmp" + socketPath: "/var/run/docker.sock" + user: "tex" + expireProjectAfterIdleMs: 24 * 60 * 60 * 1000 + checkProjectsIntervalMs: 10 * 60 * 1000 + module.exports.path.sandboxedCompilesHostDir = process.env["COMPILES_HOST_DIR"] diff --git a/services/clsi/package.json b/services/clsi/package.json index 71711be1a7..867bdf2391 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -6,6 +6,10 @@ "type": "git", "url": "https://github.com/sharelatex/clsi-sharelatex.git" }, + "scripts": { + "compile:app": "coffee -o app/js -c app/coffee && coffee -c app.coffee", + "start": "npm run compile:app && node app.js" + }, "author": "James Allen <james@sharelatex.com>", "dependencies": { "async": "0.2.9", From e18538bd3879d1918595c6959dfc3c2f2e905ab3 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Wed, 3 Jan 2018 15:41:31 +0000 Subject: [PATCH 324/754] log an error if core file is found in output --- services/clsi/app/coffee/CompileController.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 99973fdd10..1d90405614 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -39,6 +39,10 @@ module.exports = CompileController = for file in outputFiles if file.path?.match(/output\.pdf$/) status = "success" + # log an error if any core files are found + for file in outputFiles + if file.path is "core" + logger.error project_id:request.project_id, req:req, outputFiles:outputFiles, "core file found in output" timer.done() res.status(code or 200).send { From d0e5fb2d342d018e8415759598d0e5b42da03b92 Mon Sep 17 00:00:00 2001 From: James Allen <james@sharelatex.com> Date: Tue, 16 Jan 2018 10:46:59 +0000 Subject: [PATCH 325/754] Allow texlive image user to be configured --- services/clsi/config/settings.defaults.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index fb02c412ea..0e8f6aa328 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -38,7 +38,7 @@ if process.env["COMMAND_RUNNER"] env: HOME: "/tmp" socketPath: "/var/run/docker.sock" - user: "tex" + user: process.env["TEXLIVE_IMAGE_USER"] or "tex" expireProjectAfterIdleMs: 24 * 60 * 60 * 1000 checkProjectsIntervalMs: 10 * 60 * 1000 module.exports.path.sandboxedCompilesHostDir = process.env["COMPILES_HOST_DIR"] From df21b9de5394d66d30b2364702a1da9e9be71bcf Mon Sep 17 00:00:00 2001 From: James Allen <james@sharelatex.com> Date: Fri, 29 Dec 2017 08:08:19 +0000 Subject: [PATCH 326/754] Provide hosts and siblings container as environment settings and add npm run start script wip acceptence tests run, but don't all pass wip removed npm-debug from git --- services/clsi/.nvmrc | 2 +- services/clsi/Dockerfile | 19 + services/clsi/Jenkinsfile | 77 +- services/clsi/Makefile | 29 + services/clsi/app.coffee | 5 + services/clsi/bin/acceptance_test | 4 + services/clsi/config/settings.defaults.coffee | 29 +- services/clsi/docker-compose.ci.yml | 33 + services/clsi/docker-compose.yml | 51 + services/clsi/docker-runner | 1 + services/clsi/nodemon.json | 15 + services/clsi/package-lock.json | 3162 +++++++++++++++++ services/clsi/package.json | 97 +- .../acceptance/coffee/helpers/Client.coffee | 1 + 14 files changed, 3431 insertions(+), 94 deletions(-) create mode 100644 services/clsi/Dockerfile create mode 100644 services/clsi/Makefile create mode 100644 services/clsi/bin/acceptance_test create mode 100644 services/clsi/docker-compose.ci.yml create mode 100644 services/clsi/docker-compose.yml create mode 160000 services/clsi/docker-runner create mode 100644 services/clsi/nodemon.json create mode 100644 services/clsi/package-lock.json diff --git a/services/clsi/.nvmrc b/services/clsi/.nvmrc index e18a34b9d6..e1e5d1369a 100644 --- a/services/clsi/.nvmrc +++ b/services/clsi/.nvmrc @@ -1 +1 @@ -6.11.2 +6.9.5 diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile new file mode 100644 index 0000000000..d98547cad5 --- /dev/null +++ b/services/clsi/Dockerfile @@ -0,0 +1,19 @@ +FROM node:6.9.5 + +RUN wget -qO- https://get.docker.com/ | sh + +# ---- Copy Files/Build ---- +WORKDIR /app +COPY ./ /app +# Build react/vue/angular bundle static files +# RUN npm run build +RUN npm install + +RUN npm run compile + +EXPOSE 3013 + +ENV SHARELATEX_CONFIG /app/config/settings.production.coffee +ENV NODE_ENV production + +CMD ["node","/app/app.js"] diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index 0c289e15da..ab90aaae29 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -1,79 +1,72 @@ -pipeline { +String cron_string = BRANCH_NAME == "master" ? "@daily" : "" +pipeline { agent any triggers { pollSCM('* * * * *') - cron('@daily') + cron(cron_string) } stages { - stage('Clean') { - steps { - // This is a terrible hack to set the file ownership to jenkins:jenkins so we can cleanup the directory - sh 'docker run -v $(pwd):/app --rm busybox /bin/chown -R 111:119 /app' - sh 'rm -fr node_modules' - } - } stage('Install') { agent { docker { - image 'node:6.11.2' + image 'node:6.9.5' args "-v /var/lib/jenkins/.npm:/tmp/.npm -e HOME=/tmp" reuseNode true } } steps { + // we need to disable logallrefupdates, else git clones + // during the npm install will require git to lookup the + // user id which does not exist in the container's + // /etc/passwd file, causing the clone to fail. sh 'git config --global core.logallrefupdates false' - sh 'rm -fr node_modules' - checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: '_docker-runner'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/docker-runner-sharelatex']]]) - sh 'npm install ./_docker-runner' - sh 'rm -fr ./_docker-runner ./_docker-runner@tmp' - sh 'npm install' - sh 'npm rebuild' - sh 'npm install --quiet grunt-cli' + sh 'rm -rf node_modules' + sh 'npm install && npm rebuild' } } - stage('Compile and Test') { + + stage('Compile') { agent { docker { - image 'node:6.11.2' + image 'node:6.9.5' reuseNode true } } steps { - sh 'node_modules/.bin/grunt compile:app' - sh 'node_modules/.bin/grunt compile:acceptance_tests' - sh 'NODE_ENV=development node_modules/.bin/grunt test:unit' + sh 'npm run compile:all' } } - stage('Acceptance Tests') { - environment { - TEXLIVE_IMAGE="quay.io/sharelatex/texlive-full:2017.1" - } + + stage('Unit Tests') { steps { - sh 'mkdir -p compiles cache' - // Not yet running, due to volumes/sibling containers - sh 'docker container prune -f' - sh 'docker pull $TEXLIVE_IMAGE' - sh 'docker pull sharelatex/acceptance-test-runner:clsi-6.11.2' - sh 'docker run --rm -e SIBLING_CONTAINER_USER=root -e SANDBOXED_COMPILES_HOST_DIR=$(pwd)/compiles -e SANDBOXED_COMPILES_SIBLING_CONTAINERS=true -e TEXLIVE_IMAGE=$TEXLIVE_IMAGE -v /var/run/docker.sock:/var/run/docker.sock -v $(pwd):/app sharelatex/acceptance-test-runner:clsi-6.11.2' - // This is a terrible hack to set the file ownership to jenkins:jenkins so we can cleanup the directory - sh 'docker run -v $(pwd):/app --rm busybox /bin/chown -R 111:119 /app' - sh 'rm -r compiles cache server.log db.sqlite config/settings.defaults.coffee' + sh 'DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" make test_unit' } } - stage('Package') { + + stage('Acceptance Tests') { + steps { + sh 'DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" make test_acceptance' + } + } + + stage('Package and publish build') { steps { sh 'echo ${BUILD_NUMBER} > build_number.txt' sh 'touch build.tar.gz' // Avoid tar warning about files changing during read sh 'tar -czf build.tar.gz --exclude=build.tar.gz --exclude-vcs .' - } - } - stage('Publish') { - steps { 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") + } + } + } + + stage('Publish build number') { + steps { + sh 'echo ${BRANCH_NAME}-${BUILD_NUMBER} > build_number.txt' + 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") } @@ -82,6 +75,10 @@ pipeline { } post { + always { + sh 'DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" make test_clean' + } + failure { mail(from: "${EMAIL_ALERT_FROM}", to: "${EMAIL_ALERT_TO}", diff --git a/services/clsi/Makefile b/services/clsi/Makefile new file mode 100644 index 0000000000..6407885d7a --- /dev/null +++ b/services/clsi/Makefile @@ -0,0 +1,29 @@ +# This file was auto-generated, do not edit it directly. +# Instead run bin/update_build_scripts from +# https://github.com/sharelatex/sharelatex-dev-environment +# Version: 1.0.1 + +BUILD_NUMBER ?= local +BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) +PROJECT_NAME = clsi +DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml +DOCKER_COMPOSE := docker-compose ${DOCKER_COMPOSE_FLAGS} + +clean: + rm -f app.js + rm -rf app/js + rm -rf test/unit/js + rm -rf test/acceptance/js + +test: test_unit test_acceptance + +test_unit: + @[ -d test/unit ] && $(DOCKER_COMPOSE) run --rm test_unit -- ${MOCHA_ARGS} || echo "clsi has no unit tests" + +test_acceptance: test_clean # clear the database before each acceptance test run + @[ -d test/acceptance ] && $(DOCKER_COMPOSE) run --rm test_acceptance -- ${MOCHA_ARGS} || echo "clsi has no acceptance tests" + +test_clean: + $(DOCKER_COMPOSE) down + +.PHONY: clean test test_unit test_acceptance test_clean build publish diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 5c79b8ee8a..ba7d22557b 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -132,6 +132,7 @@ resCacher = if Settings.smokeTest do runSmokeTest = -> logger.log("running smoke tests") + console.log(__dirname, __filename) smokeTest.run(require.resolve(__dirname + "/test/smoke/js/SmokeTests.js"))({}, resCacher) setTimeout(runSmokeTest, 30 * 1000) @@ -139,6 +140,10 @@ app.get "/health_check", (req, res)-> res.contentType(resCacher?.setContentType) res.status(resCacher?.code).send(resCacher?.body) +app.get "/smoke_test_force", (req, res)-> + smokeTest.run(require.resolve(__dirname + "/test/smoke/js/SmokeTests.js"))(req, res) + + profiler = require "v8-profiler" app.get "/profile", (req, res) -> time = parseInt(req.query.time || "1000") diff --git a/services/clsi/bin/acceptance_test b/services/clsi/bin/acceptance_test new file mode 100644 index 0000000000..fd2e5137b5 --- /dev/null +++ b/services/clsi/bin/acceptance_test @@ -0,0 +1,4 @@ +#!/bin/bash +set -e; +MOCHA="node_modules/.bin/mocha --recursive --reporter spec --timeout 15000" +$MOCHA "$@" diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index 4b391789c1..448d13b393 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -16,27 +16,28 @@ module.exports = clsiCacheDir: Path.resolve(__dirname + "/../cache") synctexBaseDir: (project_id) -> Path.join(@compilesDir, project_id) -# clsi: -# commandRunner: "docker-runner-sharelatex" -# docker: -# image: "quay.io/sharelatex/texlive-full:2017.1" -# env: -# HOME: "/tmp" -# socketPath: "/var/run/docker.sock" -# user: "tex" -# expireProjectAfterIdleMs: 24 * 60 * 60 * 1000 -# checkProjectsIntervalMs: 10 * 60 * 1000 - internal: clsi: port: 3013 - host: "localhost" - + host: process.env["LISTEN_ADDRESS"] or "0.0.0.0" apis: clsi: - url: "http://localhost:3013" + url: "http://#{process.env['CLSI_HOST'] or 'localhost'}:3013" smokeTest: false project_cache_length_ms: 1000 * 60 * 60 * 24 parallelFileDownloads:1 + +if process.env["COMMAND_RUNNER"] + module.exports.clsi = + commandRunner: process.env["COMMAND_RUNNER"] + docker: + image: process.env["TEXLIVE_IMAGE"] or "quay.io/sharelatex/texlive-full:2017.1" + env: + HOME: "/tmp" + socketPath: "/var/run/docker.sock" + user: process.env["TEXLIVE_IMAGE_USER"] or "tex" + expireProjectAfterIdleMs: 24 * 60 * 60 * 1000 + checkProjectsIntervalMs: 10 * 60 * 1000 + module.exports.path.sandboxedCompilesHostDir = process.env["COMPILES_HOST_DIR"] diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml new file mode 100644 index 0000000000..9f40ba879d --- /dev/null +++ b/services/clsi/docker-compose.ci.yml @@ -0,0 +1,33 @@ +# This file was auto-generated, do not edit it directly. +# Instead run bin/update_build_scripts from +# https://github.com/sharelatex/sharelatex-dev-environment +# Version: 1.0.1 + +version: "2" + +services: + test_unit: + image: node:6.9.5 + volumes: + - .:/app + working_dir: /app + entrypoint: npm run test:unit:_run + + test_acceptance: + image: node:6.9.5 + volumes: + - .:/app + working_dir: /app + environment: + REDIS_HOST: redis + MONGO_HOST: mongo + depends_on: + - redis + - mongo + entrypoint: npm run test:acceptance:_run + + redis: + image: redis + + mongo: + image: mongo:3.4 diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml new file mode 100644 index 0000000000..4a25bc38a7 --- /dev/null +++ b/services/clsi/docker-compose.yml @@ -0,0 +1,51 @@ +# This file was auto-generated, do not edit it directly. +# Instead run bin/update_build_scripts from +# https://github.com/sharelatex/sharelatex-dev-environment +# Version: 1.0.1 + +version: "2" + +services: + test_unit: + image: node:6.9.5 + volumes: + - .:/app + working_dir: /app + entrypoint: npm run test:unit + + test_acceptance: + image: node:6.9.5 + volumes: + - .:/app + environment: + REDIS_HOST: redis + MONGO_HOST: mongo + CLSI_HOST: clsi + depends_on: + - clsi + - redis + - mongo + working_dir: /app + entrypoint: npm run test:acceptance + + redis: + image: redis + + mongo: + image: mongo:3.4 + + clsi: + image: gcr.io/henry-terraform-admin/clsi + build: . + environment: + TEXLIVE_IMAGE: quay.io/sharelatex/texlive-small:latest + TEXLIVE_IMAGE_USER: root # Not ideal, but makes running in dev very simple + COMMAND_RUNNER: docker-runner-sharelatex + SHARELATEX_CONFIG: /app/config/settings.defaults.coffee + COMPILES_HOST_DIR: $PWD/compiles + volumes: + - .:/app:cached + - /var/run/docker.sock:/var/run/docker.sock + - ./docker-runner:/app/node_modules/docker-runner-sharelatex + ports: + - 3013:3013 \ No newline at end of file diff --git a/services/clsi/docker-runner b/services/clsi/docker-runner new file mode 160000 index 0000000000..f861a1c810 --- /dev/null +++ b/services/clsi/docker-runner @@ -0,0 +1 @@ +Subproject commit f861a1c810ad844a6e00e82f2ebedac26dcad8b7 diff --git a/services/clsi/nodemon.json b/services/clsi/nodemon.json new file mode 100644 index 0000000000..9044f921c6 --- /dev/null +++ b/services/clsi/nodemon.json @@ -0,0 +1,15 @@ +{ + "ignore": [ + ".git", + "node_modules/" + ], + "verbose": true, + "execMap": { + "js": "npm run start" + }, + "watch": [ + "app/coffee/", + "app.coffee" + ], + "ext": "coffee" +} diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json new file mode 100644 index 0000000000..4c110ea513 --- /dev/null +++ b/services/clsi/package-lock.json @@ -0,0 +1,3162 @@ +{ + "name": "node-clsi", + "version": "0.1.4", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "JSONStream": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.10.0.tgz", + "integrity": "sha1-dDSdDYlSK3HzDwoD/5vSDKbxKsA=", + "requires": { + "jsonparse": "0.0.5", + "through": "2.3.8" + } + }, + "abbrev": { + "version": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg=" + }, + "accepts": { + "version": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", + "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", + "requires": { + "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "negotiator": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz" + } + }, + "ajv": { + "version": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "requires": { + "co": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "fast-deep-equal": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "fast-json-stable-stringify": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "json-schema-traverse": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz" + } + }, + "ansi-regex": { + "version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-0.2.0.tgz", + "integrity": "sha1-NZq0sV3NZLptdHNLcsNjYKmvLBk=", + "dev": true + }, + "aproba": { + "version": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=" + }, + "are-we-there-yet": { + "version": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "requires": { + "delegates": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz" + }, + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", + "requires": { + "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + } + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", + "requires": { + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + } + } + } + }, + "argparse": { + "version": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", + "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", + "dev": true, + "requires": { + "underscore": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "underscore.string": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz" + }, + "dependencies": { + "underscore": { + "version": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", + "dev": true + }, + "underscore.string": { + "version": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", + "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=", + "dev": true + } + } + }, + "array-flatten": { + "version": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "asn1": { + "version": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assertion-error": { + "version": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", + "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", + "dev": true + }, + "async": { + "version": "https://registry.npmjs.org/async/-/async-0.2.9.tgz", + "integrity": "sha1-32MGD789Myhqdqr21Vophtn/hhk=" + }, + "asynckit": { + "version": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + }, + "balanced-match": { + "version": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "bcrypt-pbkdf": { + "version": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "optional": true, + "requires": { + "tweetnacl": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" + } + }, + "bignumber.js": { + "version": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.7.tgz", + "integrity": "sha1-husHB89qURCQnSPm6nQ0wU9QDxw=" + }, + "bl": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz", + "integrity": "sha1-ysMo977kVzDUBLaSID/LWQ4XLV4=", + "requires": { + "readable-stream": "2.3.3" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "requires": { + "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "1.0.0", + "process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "string_decoder": "1.0.3", + "util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + } + } + } + }, + "block-stream": { + "version": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "requires": { + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" + } + }, + "bluebird": { + "version": "https://registry.npmjs.org/bluebird/-/bluebird-2.9.34.tgz", + "integrity": "sha1-L3tOyAIWMoqf3evfacjUlC/v99g=" + }, + "body-parser": { + "version": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "requires": { + "bytes": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "content-type": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "depd": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "http-errors": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "iconv-lite": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "qs": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "raw-body": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "type-is": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz" + } + }, + "boom": { + "version": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "requires": { + "hoek": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz" + } + }, + "brace-expansion": { + "version": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "requires": { + "balanced-match": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "concat-map": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + } + }, + "bunyan": { + "version": "https://registry.npmjs.org/bunyan/-/bunyan-0.22.3.tgz", + "integrity": "sha1-ehncG0yMZF90AkGnQPIkUUfGfsI=", + "dev": true, + "requires": { + "dtrace-provider": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.2.8.tgz", + "mv": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz" + } + }, + "buster-core": { + "version": "https://registry.npmjs.org/buster-core/-/buster-core-0.6.4.tgz", + "integrity": "sha1-J79rrWdCROpyDzEdkAoMoct4YFA=", + "dev": true + }, + "buster-format": { + "version": "https://registry.npmjs.org/buster-format/-/buster-format-0.5.6.tgz", + "integrity": "sha1-K4bDIuz14bCubm55Bev884fSq5U=", + "dev": true, + "requires": { + "buster-core": "https://registry.npmjs.org/buster-core/-/buster-core-0.6.4.tgz" + } + }, + "bytes": { + "version": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "caseless": { + "version": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chai": { + "version": "https://registry.npmjs.org/chai/-/chai-1.8.1.tgz", + "integrity": "sha1-zHeGbV5+vKK9dRRLHtw3Coh4X3I=", + "dev": true, + "requires": { + "assertion-error": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", + "deep-eql": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz" + } + }, + "chalk": { + "version": "https://registry.npmjs.org/chalk/-/chalk-0.3.0.tgz", + "integrity": "sha1-HJhDdzfxGZ68wdTEj9Qbn5yOjyM=", + "dev": true, + "requires": { + "ansi-styles": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-0.2.0.tgz", + "has-color": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz" + } + }, + "co": { + "version": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "code-point-at": { + "version": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "coffee-script": { + "version": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", + "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=", + "dev": true + }, + "colors": { + "version": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", + "dev": true + }, + "combined-stream": { + "version": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "requires": { + "delayed-stream": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + } + }, + "commander": { + "version": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz", + "integrity": "sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg=" + }, + "concat-map": { + "version": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", + "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", + "requires": { + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "readable-stream": "2.0.6", + "typedarray": "0.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "requires": { + "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "1.0.0", + "process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + } + } + } + }, + "console-control-strings": { + "version": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "content-disposition": { + "version": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "content-type": { + "version": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=" + }, + "cookie": { + "version": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-signature": { + "version": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "core-util-is": { + "version": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cryptiles": { + "version": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "requires": { + "boom": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz" + }, + "dependencies": { + "boom": { + "version": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha1-XdnabuOl8wIHdDYpDLcX0/SlTgI=", + "requires": { + "hoek": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz" + } + } + } + }, + "dashdash": { + "version": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + } + }, + "dateformat": { + "version": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", + "integrity": "sha1-sCIMAt6YYXQztyhRz0fePfLNvuk=", + "dev": true + }, + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + } + }, + "deep-eql": { + "version": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "dev": true, + "requires": { + "type-detect": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz" + } + }, + "deep-extend": { + "version": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", + "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=" + }, + "delayed-stream": { + "version": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegates": { + "version": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "depd": { + "version": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "detect-libc": { + "version": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, + "diff": { + "version": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", + "integrity": "sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ=" + }, + "docker-modem": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-1.0.4.tgz", + "integrity": "sha512-pkXB9p7KWagegOXm2NsbVDBluQQLCBJzX9uYJzVbL6CHwe4d2sSbcACJ4K8ISX1l1JUUmFSiwNkBKc1uTiU4MA==", + "requires": { + "JSONStream": "0.10.0", + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "readable-stream": "1.0.34", + "split-ca": "1.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + } + } + } + }, + "dockerode": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-2.5.3.tgz", + "integrity": "sha512-LQKXR5jyI+G/+5OhZCi40m0ArY4j46g7Tl71Vtn10Ekt5TiyDzZAoqXOCS6edQpEuGbdFgSDJxleFqLxACpKJg==", + "requires": { + "concat-stream": "1.5.2", + "docker-modem": "1.0.4", + "tar-fs": "1.12.0" + } + }, + "dottie": { + "version": "https://registry.npmjs.org/dottie/-/dottie-0.3.1.tgz", + "integrity": "sha1-QYX3d0iZYKwIQTHeP5oUoXm7dJ8=" + }, + "dtrace-provider": { + "version": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.2.8.tgz", + "integrity": "sha1-4kPxkhmqlfvw2PL/sH9b1k6U/iA=", + "dev": true, + "optional": true + }, + "ecc-jsbn": { + "version": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true, + "requires": { + "jsbn": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz" + } + }, + "ee-first": { + "version": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", + "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "requires": { + "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + } + }, + "escape-html": { + "version": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "esprima": { + "version": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", + "dev": true + }, + "etag": { + "version": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "eventemitter2": { + "version": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", + "dev": true + }, + "exit": { + "version": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "express": { + "version": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", + "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", + "requires": { + "accepts": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", + "array-flatten": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "body-parser": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "content-disposition": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "content-type": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "cookie": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "cookie-signature": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "depd": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "encodeurl": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", + "escape-html": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "etag": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "finalhandler": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "fresh": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "merge-descriptors": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "methods": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "parseurl": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "path-to-regexp": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "proxy-addr": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", + "qs": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "range-parser": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "send": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", + "serve-static": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", + "setprototypeof": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "statuses": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "type-is": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "utils-merge": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "vary": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" + }, + "dependencies": { + "setprototypeof": { + "version": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=" + }, + "statuses": { + "version": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + } + } + }, + "extend": { + "version": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "extsprintf": { + "version": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=" + }, + "fast-json-stable-stringify": { + "version": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "finalhandler": { + "version": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "requires": { + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "encodeurl": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", + "escape-html": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "parseurl": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "statuses": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "unpipe": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + }, + "dependencies": { + "statuses": { + "version": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + } + } + }, + "findup-sync": { + "version": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", + "integrity": "sha1-fz56l7gjksZTvwZYm9hRkOk8NoM=", + "dev": true, + "requires": { + "glob": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz" + }, + "dependencies": { + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "dev": true, + "requires": { + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz" + } + }, + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "minimatch": { + "version": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "dev": true, + "requires": { + "lru-cache": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "sigmund": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz" + } + } + } + }, + "forever-agent": { + "version": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", + "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", + "requires": { + "asynckit": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "combined-stream": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz" + } + }, + "forwarded": { + "version": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs-extra": { + "version": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.16.5.tgz", + "integrity": "sha1-GtZh+myGyWCM0bSe/G/Og0k5p1A=", + "requires": { + "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", + "jsonfile": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "rimraf": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz" + } + }, + "fs.realpath": { + "version": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fstream": { + "version": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "requires": { + "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "rimraf": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz" + }, + "dependencies": { + "graceful-fs": { + "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, + "mkdirp": { + "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + } + } + }, + "fstream-ignore": { + "version": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", + "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", + "requires": { + "fstream": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" + } + }, + "gauge": { + "version": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "console-control-strings": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "has-unicode": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "signal-exit": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "string-width": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "wide-align": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz" + } + }, + "generic-pool": { + "version": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.2.0.tgz", + "integrity": "sha1-i0ZcGnWI6p3SuxM72gu2a/74pj4=" + }, + "getobject": { + "version": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", + "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", + "dev": true + }, + "getpass": { + "version": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + } + }, + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", + "requires": { + "fs.realpath": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "inflight": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + } + }, + "graceful-fs": { + "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", + "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", + "requires": { + "natives": "https://registry.npmjs.org/natives/-/natives-1.1.1.tgz" + } + }, + "growl": { + "version": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", + "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=" + }, + "grunt": { + "version": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", + "integrity": "sha1-VpN81RlDJK3/bSB2MYMqnWuk5/A=", + "dev": true, + "requires": { + "async": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", + "coffee-script": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz", + "colors": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "dateformat": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", + "eventemitter2": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "exit": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "findup-sync": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", + "getobject": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", + "glob": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", + "grunt-legacy-log": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", + "grunt-legacy-util": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", + "hooker": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "iconv-lite": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", + "js-yaml": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "nopt": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "rimraf": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "underscore.string": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", + "which": "https://registry.npmjs.org/which/-/which-1.0.9.tgz" + }, + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", + "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=", + "dev": true + }, + "coffee-script": { + "version": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz", + "integrity": "sha1-FQ1rTLUiiUNp7+1qIQHCC8f0pPQ=", + "dev": true + }, + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", + "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", + "dev": true, + "requires": { + "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz" + } + }, + "graceful-fs": { + "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", + "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", + "dev": true + }, + "iconv-lite": { + "version": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", + "integrity": "sha1-HOYKOleGSiktEyH/RgnKS7llrcg=", + "dev": true + }, + "inherits": { + "version": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", + "dev": true + }, + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", + "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=", + "dev": true + }, + "minimatch": { + "version": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "dev": true, + "requires": { + "lru-cache": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "sigmund": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz" + } + }, + "nopt": { + "version": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" + } + }, + "rimraf": { + "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true + } + } + }, + "grunt-bunyan": { + "version": "https://registry.npmjs.org/grunt-bunyan/-/grunt-bunyan-0.5.0.tgz", + "integrity": "sha1-aCnXbgGZQ9owQTk2MaNuKsgpsWw=", + "dev": true, + "requires": { + "lodash": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz" + }, + "dependencies": { + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + } + } + }, + "grunt-contrib-clean": { + "version": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-0.5.0.tgz", + "integrity": "sha1-9T397ghJsce0Dp67umn0jExgecU=", + "dev": true, + "requires": { + "rimraf": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz" + }, + "dependencies": { + "rimraf": { + "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true + } + } + }, + "grunt-contrib-coffee": { + "version": "https://registry.npmjs.org/grunt-contrib-coffee/-/grunt-contrib-coffee-0.7.0.tgz", + "integrity": "sha1-ixIme3TnM4sfKcW4txj7n4mYLxM=", + "dev": true, + "requires": { + "coffee-script": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.3.tgz" + }, + "dependencies": { + "coffee-script": { + "version": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.3.tgz", + "integrity": "sha1-Y1XTLPGwTN/2tITl5xF4Ky8MOb4=", + "dev": true + } + } + }, + "grunt-execute": { + "version": "https://registry.npmjs.org/grunt-execute/-/grunt-execute-0.1.5.tgz", + "integrity": "sha1-yX64lDYS/vu3L749Mu+VIzxfouk=", + "dev": true + }, + "grunt-legacy-log": { + "version": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", + "integrity": "sha1-7ClCboAwIa9ZAp+H0vnNczWgVTE=", + "dev": true, + "requires": { + "colors": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "grunt-legacy-log-utils": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", + "hooker": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "underscore.string": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz" + }, + "dependencies": { + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "underscore.string": { + "version": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", + "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", + "dev": true + } + } + }, + "grunt-legacy-log-utils": { + "version": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", + "integrity": "sha1-wHBrndkGThFvNvI/5OawSGcsD34=", + "dev": true, + "requires": { + "colors": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "underscore.string": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz" + }, + "dependencies": { + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "underscore.string": { + "version": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", + "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", + "dev": true + } + } + }, + "grunt-legacy-util": { + "version": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", + "integrity": "sha1-kzJIhNv343qf98Am3/RR2UqeVUs=", + "dev": true, + "requires": { + "async": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", + "exit": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "getobject": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", + "hooker": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", + "underscore.string": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", + "which": "https://registry.npmjs.org/which/-/which-1.0.9.tgz" + }, + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", + "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=", + "dev": true + }, + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", + "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=", + "dev": true + } + } + }, + "grunt-mkdir": { + "version": "https://registry.npmjs.org/grunt-mkdir/-/grunt-mkdir-1.0.0.tgz", + "integrity": "sha1-c+GiasJKCFljY/TdlUsNMkheWOk=" + }, + "grunt-mocha-test": { + "version": "https://registry.npmjs.org/grunt-mocha-test/-/grunt-mocha-test-0.8.2.tgz", + "integrity": "sha1-emGEuYhg0Phb3qrWvqob199bvus=", + "dev": true, + "requires": { + "mocha": "https://registry.npmjs.org/mocha/-/mocha-1.14.0.tgz" + }, + "dependencies": { + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", + "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", + "dev": true, + "requires": { + "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz" + } + }, + "graceful-fs": { + "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", + "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=", + "dev": true + }, + "minimatch": { + "version": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "dev": true, + "requires": { + "lru-cache": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "sigmund": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz" + } + }, + "mocha": { + "version": "https://registry.npmjs.org/mocha/-/mocha-1.14.0.tgz", + "integrity": "sha1-cT223FAAGRqdA1gZXQkIeQ7LYVc=", + "dev": true, + "requires": { + "commander": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "diff": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", + "glob": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", + "growl": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", + "jade": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz" + } + } + } + }, + "grunt-shell": { + "version": "https://registry.npmjs.org/grunt-shell/-/grunt-shell-0.6.4.tgz", + "integrity": "sha1-5KbRuSkSd2/ZOimcX2zGTpUlNlw=", + "dev": true, + "requires": { + "chalk": "https://registry.npmjs.org/chalk/-/chalk-0.3.0.tgz" + } + }, + "har-schema": { + "version": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "requires": { + "ajv": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "har-schema": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz" + } + }, + "has-color": { + "version": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", + "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", + "dev": true + }, + "has-unicode": { + "version": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "hawk": { + "version": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha1-r02RTrBl+bXOTZ0RwcshJu7MMDg=", + "requires": { + "boom": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "cryptiles": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "hoek": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", + "sntp": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz" + } + }, + "heapdump": { + "version": "https://registry.npmjs.org/heapdump/-/heapdump-0.3.9.tgz", + "integrity": "sha1-A8dOsN9dZ74Jgug0KbqcnSs7f3g=" + }, + "hoek": { + "version": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", + "integrity": "sha1-ctnQdU9/4lyi0BrY+PmpRJqJUm0=" + }, + "hooker": { + "version": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", + "dev": true + }, + "http-errors": { + "version": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "requires": { + "depd": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "setprototypeof": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "statuses": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz" + }, + "dependencies": { + "depd": { + "version": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + } + } + }, + "http-signature": { + "version": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "jsprim": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "sshpk": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz" + } + }, + "iconv-lite": { + "version": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha1-90aPYBNfXl2tM5nAqBvpoWA6CCs=" + }, + "inflection": { + "version": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", + "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" + }, + "inflight": { + "version": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "wrappy": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + } + }, + "inherits": { + "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=" + }, + "ipaddr.js": { + "version": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz", + "integrity": "sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A=" + }, + "is-fullwidth-code-point": { + "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz" + } + }, + "is-typedarray": { + "version": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "isstream": { + "version": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jade": { + "version": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "requires": { + "commander": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz" + }, + "dependencies": { + "commander": { + "version": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=" + }, + "mkdirp": { + "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=" + } + } + }, + "js-yaml": { + "version": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", + "integrity": "sha1-olrmUJmZ6X3yeMZxnaEb0Gh3Q6g=", + "dev": true, + "requires": { + "argparse": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", + "esprima": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz" + } + }, + "jsbn": { + "version": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "json-schema": { + "version": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, + "json-stable-stringify": { + "version": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "requires": { + "jsonify": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz" + } + }, + "json-stringify-safe": { + "version": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsonfile": { + "version": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "requires": { + "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz" + }, + "dependencies": { + "graceful-fs": { + "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "optional": true + } + } + }, + "jsonify": { + "version": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + }, + "jsonparse": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz", + "integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=" + }, + "jsprim": { + "version": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "extsprintf": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "json-schema": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "verror": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz" + } + }, + "lockfile": { + "version": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.3.tgz", + "integrity": "sha1-Jjj8OaAzHpysGgS3F5mTHJxQ33k=" + }, + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + }, + "logger-sharelatex": { + "version": "git+https://github.com/sharelatex/logger-sharelatex.git#9ee7b52eb2bbd8fcbb1e2c708587c1e93fd4c733", + "integrity": "sha1-aRMA+7GVHSmsRMbyj5cLhnueM9Q=", + "requires": { + "bunyan": "1.5.1", + "coffee-script": "1.4.0", + "raven": "https://registry.npmjs.org/raven/-/raven-1.2.1.tgz" + }, + "dependencies": { + "bunyan": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.5.1.tgz", + "integrity": "sha1-X259RMQ7lS9WsPQTCeOrEjkbTi0=", + "requires": { + "dtrace-provider": "0.6.0", + "mv": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "safe-json-stringify": "1.0.4" + } + }, + "coffee-script": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.4.0.tgz", + "integrity": "sha1-XjvIqsJsAajie/EHcixWVfWtfTY=" + }, + "dtrace-provider": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.6.0.tgz", + "integrity": "sha1-CweNVReTfYcxAUUtkUZzdVe3XlE=", + "optional": true, + "requires": { + "nan": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz" + } + } + } + }, + "lru-cache": { + "version": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" + }, + "lsmod": { + "version": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz", + "integrity": "sha1-mgD3bco26yP6BTUK/htYXUKZ5ks=" + }, + "lynx": { + "version": "https://registry.npmjs.org/lynx/-/lynx-0.0.11.tgz", + "integrity": "sha1-LPoU5EP9LZKlm3efQVZ84cxpZaM=", + "requires": { + "mersenne": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz", + "statsd-parser": "https://registry.npmjs.org/statsd-parser/-/statsd-parser-0.0.4.tgz" + } + }, + "media-typer": { + "version": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "mersenne": { + "version": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz", + "integrity": "sha1-QB/ex+whzbngPNPTAhOY2iGycIU=" + }, + "methods": { + "version": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "metrics-sharelatex": { + "version": "git+https://github.com/sharelatex/metrics-sharelatex.git#05bd57604115ae5efdca2a419fbef4f25e9a780f", + "integrity": "sha1-ruLAc3Tl1GZrAQjP/K4NeRajdW4=", + "requires": { + "coffee-script": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", + "lynx": "https://registry.npmjs.org/lynx/-/lynx-0.1.1.tgz", + "underscore": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz" + }, + "dependencies": { + "coffee-script": { + "version": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", + "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=" + }, + "lynx": { + "version": "https://registry.npmjs.org/lynx/-/lynx-0.1.1.tgz", + "integrity": "sha1-Mxjc7xaQi4KG6Bisz9sxzXQkj50=", + "requires": { + "mersenne": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz", + "statsd-parser": "https://registry.npmjs.org/statsd-parser/-/statsd-parser-0.0.4.tgz" + } + }, + "underscore": { + "version": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" + } + } + }, + "mime": { + "version": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha1-Eh+evEnjdm8xGnbh+hyAA8SwOqY=" + }, + "mime-db": { + "version": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + }, + "mime-types": { + "version": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "requires": { + "mime-db": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz" + } + }, + "minimatch": { + "version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "requires": { + "brace-expansion": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz" + } + }, + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=" + }, + "mocha": { + "version": "https://registry.npmjs.org/mocha/-/mocha-1.10.0.tgz", + "integrity": "sha1-8WrMlQ75Vm+/kIvPttXQQWw50No=", + "dev": true, + "requires": { + "commander": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "diff": "https://registry.npmjs.org/diff/-/diff-1.0.2.tgz", + "glob": "https://registry.npmjs.org/glob/-/glob-3.2.1.tgz", + "growl": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", + "jade": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.3.tgz", + "ms": "https://registry.npmjs.org/ms/-/ms-0.3.0.tgz" + }, + "dependencies": { + "commander": { + "version": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", + "dev": true + }, + "diff": { + "version": "https://registry.npmjs.org/diff/-/diff-1.0.2.tgz", + "integrity": "sha1-Suc/Gu6Nb89ITxoc53zmUdm38Mk=", + "dev": true + }, + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-3.2.1.tgz", + "integrity": "sha1-V69w7HO6IyO/4/KaBndl22TF11g=", + "dev": true, + "requires": { + "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz" + } + }, + "graceful-fs": { + "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", + "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", + "dev": true + }, + "inherits": { + "version": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", + "dev": true + }, + "minimatch": { + "version": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "dev": true, + "requires": { + "lru-cache": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "sigmund": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz" + } + }, + "mkdirp": { + "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.3.tgz", + "integrity": "sha1-WV4lHBNww6aLqyE20ONIuBBa3xM=", + "dev": true + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.3.0.tgz", + "integrity": "sha1-A+3DSNYT5mpWSGz9rFO8vomcvWE=", + "dev": true + } + } + }, + "moment": { + "version": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz", + "integrity": "sha1-1usaRsvMFKKy+UNBEsH/iQfzE/0=" + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "mv": { + "version": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", + "optional": true, + "requires": { + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "ncp": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "rimraf": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz" + }, + "dependencies": { + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "optional": true, + "requires": { + "inflight": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + } + }, + "mkdirp": { + "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "optional": true, + "requires": { + "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + }, + "rimraf": { + "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", + "optional": true, + "requires": { + "glob": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz" + } + } + } + }, + "mysql": { + "version": "https://registry.npmjs.org/mysql/-/mysql-2.6.2.tgz", + "integrity": "sha1-k3Gd0yT1fUHET7bX+GDPjmOFk7A=", + "requires": { + "bignumber.js": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.7.tgz", + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "require-all": "https://registry.npmjs.org/require-all/-/require-all-1.0.0.tgz" + } + }, + "nan": { + "version": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", + "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=" + }, + "natives": { + "version": "https://registry.npmjs.org/natives/-/natives-1.1.1.tgz", + "integrity": "sha1-ARrM4ffL2H97prMJPWzZOSvhxXQ=" + }, + "ncp": { + "version": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "optional": true + }, + "negotiator": { + "version": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "node-pre-gyp": { + "version": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz", + "integrity": "sha1-wA6WhgsjwOFCCse+/FBE4deNhkk=", + "requires": { + "detect-libc": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "hawk": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "nopt": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "npmlog": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "rc": "https://registry.npmjs.org/rc/-/rc-1.2.3.tgz", + "request": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "rimraf": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "semver": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "tar": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "tar-pack": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.1.tgz" + }, + "dependencies": { + "ajv": { + "version": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "requires": { + "co": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "json-stable-stringify": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz" + } + }, + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" + }, + "aws-sign2": { + "version": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" + }, + "boom": { + "version": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "requires": { + "hoek": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" + } + }, + "cryptiles": { + "version": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "requires": { + "boom": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz" + } + }, + "form-data": { + "version": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "requires": { + "asynckit": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "combined-stream": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz" + } + }, + "har-schema": { + "version": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" + }, + "har-validator": { + "version": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "requires": { + "ajv": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "har-schema": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz" + } + }, + "hawk": { + "version": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "requires": { + "boom": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "cryptiles": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "hoek": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "sntp": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" + } + }, + "hoek": { + "version": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + }, + "http-signature": { + "version": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "requires": { + "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "jsprim": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "sshpk": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz" + } + }, + "mkdirp": { + "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + }, + "performance-now": { + "version": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" + }, + "qs": { + "version": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" + }, + "request": { + "version": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "requires": { + "aws-sign2": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "aws4": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "caseless": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "combined-stream": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "extend": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "forever-agent": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "form-data": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "har-validator": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "hawk": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "http-signature": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "is-typedarray": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "isstream": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "json-stringify-safe": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "oauth-sign": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "performance-now": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "qs": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "stringstream": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "tough-cookie": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", + "tunnel-agent": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "uuid": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz" + } + }, + "sntp": { + "version": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "requires": { + "hoek": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" + } + } + } + }, + "nopt": { + "version": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "requires": { + "abbrev": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "osenv": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz" + } + }, + "npmlog": { + "version": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=", + "requires": { + "are-we-there-yet": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", + "console-control-strings": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "gauge": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "set-blocking": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" + } + }, + "number-is-nan": { + "version": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "oauth-sign": { + "version": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "object-assign": { + "version": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "on-finished": { + "version": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" + } + }, + "once": { + "version": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + } + }, + "os-homedir": { + "version": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", + "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", + "requires": { + "os-homedir": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "os-tmpdir": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" + } + }, + "parseurl": { + "version": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, + "path-is-absolute": { + "version": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-to-regexp": { + "version": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "performance-now": { + "version": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "process-nextick-args": { + "version": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + }, + "proxy-addr": { + "version": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", + "integrity": "sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=", + "requires": { + "forwarded": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "ipaddr.js": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz" + } + }, + "pump": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", + "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", + "requires": { + "end-of-stream": "1.4.1", + "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + } + }, + "punycode": { + "version": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "qs": { + "version": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha1-NJzfbu+J7EXBLX1es/wMhwNDptg=" + }, + "range-parser": { + "version": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "raven": { + "version": "https://registry.npmjs.org/raven/-/raven-1.2.1.tgz", + "integrity": "sha1-lJwTTbAooZC3u/j3kKrlQbfAIL0=", + "requires": { + "cookie": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "json-stringify-safe": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "lsmod": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz", + "stack-trace": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", + "uuid": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz" + } + }, + "raw-body": { + "version": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "requires": { + "bytes": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "http-errors": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "iconv-lite": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "unpipe": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + } + }, + "rc": { + "version": "https://registry.npmjs.org/rc/-/rc-1.2.3.tgz", + "integrity": "sha1-UVdakA+N1oOBxxC0cSwhVMPiA1s=", + "requires": { + "deep-extend": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", + "ini": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "minimist": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "strip-json-comments": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" + }, + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + } + }, + "request": { + "version": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", + "integrity": "sha1-ygtl2gLtYpNYh4COb1EDgQNOM1Y=", + "requires": { + "aws-sign2": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "aws4": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "caseless": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "combined-stream": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "extend": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "forever-agent": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "form-data": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", + "har-validator": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "hawk": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "http-signature": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "is-typedarray": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "isstream": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "json-stringify-safe": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "oauth-sign": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "performance-now": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "qs": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "stringstream": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "tough-cookie": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", + "tunnel-agent": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "uuid": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz" + }, + "dependencies": { + "uuid": { + "version": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", + "integrity": "sha1-PdPT55Crwk17DToDT/q6vijrvAQ=" + } + } + }, + "require-all": { + "version": "https://registry.npmjs.org/require-all/-/require-all-1.0.0.tgz", + "integrity": "sha1-hINwjnzkxt+tmItQgPl4KbktIic=" + }, + "require-like": { + "version": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", + "integrity": "sha1-rW8wwTvs15cBDEaK+ndcDAprR/o=", + "dev": true + }, + "rimraf": { + "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha1-LtgVDSShbqhlHm1u8PR8QVjOejY=", + "requires": { + "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz" + } + }, + "safe-buffer": { + "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" + }, + "safe-json-stringify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz", + "integrity": "sha1-gaCY9Efku8P/MxKiQ1IbwGDvWRE=", + "optional": true + }, + "sandboxed-module": { + "version": "https://registry.npmjs.org/sandboxed-module/-/sandboxed-module-0.3.0.tgz", + "integrity": "sha1-8fvvvYCaT2kHO9B8rm/H2y6vX2o=", + "dev": true, + "requires": { + "require-like": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", + "stack-trace": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.6.tgz" + }, + "dependencies": { + "stack-trace": { + "version": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.6.tgz", + "integrity": "sha1-HnGb1qJin/CcGJ4Xqe+QKpT8XbA=", + "dev": true + } + } + }, + "semver": { + "version": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha1-4FnAnYVx8FQII3M0M1BdOi8AsY4=" + }, + "send": { + "version": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", + "integrity": "sha1-pw4coh0TgsEdDZ9iMd6ygQgNerM=", + "requires": { + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "depd": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "destroy": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "encodeurl": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", + "escape-html": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "etag": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "fresh": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "http-errors": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "mime": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "range-parser": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "statuses": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz" + }, + "dependencies": { + "statuses": { + "version": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + } + } + }, + "sequelize": { + "version": "https://registry.npmjs.org/sequelize/-/sequelize-2.1.3.tgz", + "integrity": "sha1-FrpROxNnlY/oIzhYEelRFFHt6tE=", + "requires": { + "bluebird": "https://registry.npmjs.org/bluebird/-/bluebird-2.9.34.tgz", + "dottie": "https://registry.npmjs.org/dottie/-/dottie-0.3.1.tgz", + "generic-pool": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.2.0.tgz", + "inflection": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "moment": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz", + "node-uuid": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "toposort-class": "https://registry.npmjs.org/toposort-class/-/toposort-class-0.3.1.tgz", + "validator": "https://registry.npmjs.org/validator/-/validator-3.43.0.tgz" + }, + "dependencies": { + "node-uuid": { + "version": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + } + } + }, + "serve-static": { + "version": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", + "integrity": "sha1-TFfVNASnYdjy58HooYpH2/J4pxk=", + "requires": { + "encodeurl": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", + "escape-html": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "parseurl": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "send": "https://registry.npmjs.org/send/-/send-0.16.1.tgz" + } + }, + "set-blocking": { + "version": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "setprototypeof": { + "version": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + }, + "settings-sharelatex": { + "version": "git+https://github.com/sharelatex/settings-sharelatex.git#cbc5e41c1dbe6789721a14b3fdae05bf22546559", + "integrity": "sha1-RatFGqtZ7wuhO78vGSB60S3H9Rc=", + "requires": { + "coffee-script": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz" + }, + "dependencies": { + "coffee-script": { + "version": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", + "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=" + } + } + }, + "sigmund": { + "version": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" + }, + "signal-exit": { + "version": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "sinon": { + "version": "https://registry.npmjs.org/sinon/-/sinon-1.7.3.tgz", + "integrity": "sha1-emnWnNApRYbHQyVO7/G1g6UJl/I=", + "dev": true, + "requires": { + "buster-format": "https://registry.npmjs.org/buster-format/-/buster-format-0.5.6.tgz" + } + }, + "smoke-test-sharelatex": { + "version": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#bc3e93d18ccee219c0d99e8b02c984ccdd842e1c", + "integrity": "sha1-s6idlk0vuV2Kz+u6rn3ITbaMEQ4=", + "requires": { + "mocha": "1.17.1" + }, + "dependencies": { + "glob": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", + "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", + "requires": { + "graceful-fs": "2.0.3", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "minimatch": "0.2.14" + } + }, + "graceful-fs": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", + "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=" + }, + "minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "requires": { + "lru-cache": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "sigmund": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz" + } + }, + "mocha": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.17.1.tgz", + "integrity": "sha1-f3Zx1oUm0HS3uuZgyQmfh+DqHMs=", + "requires": { + "commander": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "diff": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", + "glob": "3.2.3", + "growl": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", + "jade": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz" + } + } + } + }, + "sntp": { + "version": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha1-LGzsFP7cIiJznK+bXD2F0cxaLMg=", + "requires": { + "hoek": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz" + } + }, + "split-ca": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", + "integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY=" + }, + "sqlite3": { + "version": "https://registry.npmjs.org/sqlite3/-/sqlite3-3.1.13.tgz", + "integrity": "sha1-2ZCgVic5J2jeYni6/Rox/f6Qfdk=", + "requires": { + "nan": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", + "node-pre-gyp": "0.6.38" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.3" + } + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "requires": { + "inherits": "2.0.3" + } + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "requires": { + "hoek": "2.16.3" + } + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "requires": { + "delayed-stream": "1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "requires": { + "boom": "2.10.1" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", + "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.2" + } + }, + "fstream-ignore": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", + "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" + } + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, + "har-schema": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" + }, + "har-validator": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.1", + "sshpk": "1.13.1" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", + "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "mime-db": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + }, + "mime-types": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "requires": { + "mime-db": "1.30.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "nan": { + "version": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", + "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=" + }, + "node-pre-gyp": { + "version": "0.6.38", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.38.tgz", + "integrity": "sha1-6Sog+DQWQVu0CG9tH7eLPac9ET0=", + "requires": { + "hawk": "3.1.3", + "mkdirp": "0.5.1", + "nopt": "4.0.1", + "npmlog": "4.1.2", + "rc": "1.2.1", + "request": "2.81.0", + "rimraf": "2.6.2", + "semver": "5.4.1", + "tar": "2.2.1", + "tar-pack": "3.4.0" + } + }, + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "requires": { + "abbrev": "1.1.1", + "osenv": "0.1.4" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", + "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "qs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" + }, + "rc": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", + "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=", + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "request": { + "version": "2.81.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.17", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", + "tunnel-agent": "0.6.0", + "uuid": "3.1.0" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "requires": { + "hoek": "2.16.3" + } + }, + "sshpk": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "tar-pack": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.0.tgz", + "integrity": "sha1-I74tf2cagzk3bL2wuP4/3r8xeYQ=", + "requires": { + "debug": "2.6.9", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.4.0", + "readable-stream": "2.3.3", + "rimraf": "2.6.2", + "tar": "2.2.1", + "uid-number": "0.0.6" + } + }, + "tough-cookie": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", + "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", + "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "wide-align": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + } + }, + "sshpk": { + "version": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "requires": { + "asn1": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "bcrypt-pbkdf": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "dashdash": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "ecc-jsbn": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "getpass": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "jsbn": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "tweetnacl": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" + } + }, + "stack-trace": { + "version": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", + "integrity": "sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU=" + }, + "statsd-parser": { + "version": "https://registry.npmjs.org/statsd-parser/-/statsd-parser-0.0.4.tgz", + "integrity": "sha1-y9JDlTzELv/VSLXSI4jtaJ7GOb0=" + }, + "statuses": { + "version": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha1-u3PURtonlhBu/MG2AaJT1sRr0Ic=" + }, + "string-width": { + "version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "is-fullwidth-code-point": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" + } + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "stringstream": { + "version": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + }, + "strip-ansi": { + "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" + } + }, + "strip-json-comments": { + "version": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "tar": { + "version": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "requires": { + "block-stream": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "fstream": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" + } + }, + "tar-fs": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.12.0.tgz", + "integrity": "sha1-pqgFU9ilTHPeHQrg553ncDVgXh0=", + "requires": { + "mkdirp": "0.5.1", + "pump": "1.0.3", + "tar-stream": "1.5.5" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + } + } + }, + "tar-pack": { + "version": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.1.tgz", + "integrity": "sha1-4dvAOpudO6B+iWrQJzF+tnmhCh8=", + "requires": { + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "fstream": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "fstream-ignore": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", + "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "rimraf": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "tar": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "uid-number": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz" + }, + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", + "requires": { + "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + } + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", + "requires": { + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + } + } + } + }, + "tar-stream": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.5.tgz", + "integrity": "sha512-mQdgLPc/Vjfr3VWqWbfxW8yQNiJCbAZ+Gf6GDu1Cy0bdb33ofyiNGBtAY96jHFhDuivCwgW1H9DgTON+INiXgg==", + "requires": { + "bl": "1.2.1", + "end-of-stream": "1.4.1", + "readable-stream": "2.3.3", + "xtend": "4.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "requires": { + "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "1.0.0", + "process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "string_decoder": "1.0.3", + "util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + } + } + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "timekeeper": { + "version": "https://registry.npmjs.org/timekeeper/-/timekeeper-0.0.4.tgz", + "integrity": "sha1-kNt58X2Ni1NiFUOJSSuXJ2LP0nY=", + "dev": true + }, + "toposort-class": { + "version": "https://registry.npmjs.org/toposort-class/-/toposort-class-0.3.1.tgz", + "integrity": "sha1-0TM67XNPhgjO0ShiGL0sRfYO/0g=" + }, + "tough-cookie": { + "version": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "requires": { + "punycode": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" + } + }, + "tunnel-agent": { + "version": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + } + }, + "tweetnacl": { + "version": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, + "type-detect": { + "version": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "dev": true + }, + "type-is": { + "version": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "requires": { + "media-typer": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "uid-number": { + "version": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", + "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" + }, + "underscore": { + "version": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" + }, + "underscore.string": { + "version": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", + "integrity": "sha1-18D6KvXVoaZ/QlPa7pgTLnM/Dxk=", + "dev": true + }, + "unpipe": { + "version": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "util-deprecate": { + "version": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz", + "integrity": "sha1-Zyj8BFnEUNeWqZwxg3VpvfZy1yg=" + }, + "v8-profiler": { + "version": "https://registry.npmjs.org/v8-profiler/-/v8-profiler-5.7.0.tgz", + "integrity": "sha1-6DgcvrtbX9DKjSsJ9qAYGhWNs00=", + "requires": { + "nan": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", + "node-pre-gyp": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz" + } + }, + "validator": { + "version": "https://registry.npmjs.org/validator/-/validator-3.43.0.tgz", + "integrity": "sha1-lkZLmS1BloM9l6GUv0Cxn/VLrgU=" + }, + "vary": { + "version": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "verror": { + "version": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "extsprintf": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" + } + }, + "which": { + "version": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", + "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=", + "dev": true + }, + "wide-align": { + "version": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", + "integrity": "sha1-Vx4PGwYEY268DfwhsDObvjE0FxA=", + "requires": { + "string-width": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz" + } + }, + "wrappy": { + "version": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "wrench": { + "version": "https://registry.npmjs.org/wrench/-/wrench-1.5.9.tgz", + "integrity": "sha1-QRaRxjqbJTGxcAJnJ5veyiOyFCo=" + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + } + } +} diff --git a/services/clsi/package.json b/services/clsi/package.json index 71711be1a7..efc7aa0df7 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -1,48 +1,67 @@ { - "name": "node-clsi", - "description": "A Node.js implementation of the CLSI LaTeX web-API", - "version": "0.1.4", + "name": "node-clsi", + "description": "A Node.js implementation of the CLSI LaTeX web-API", + "version": "0.1.4", "repository": { - "type": "git", + "type": "git", "url": "https://github.com/sharelatex/clsi-sharelatex.git" - }, - "author": "James Allen <james@sharelatex.com>", + }, + "scripts": { + "test:acceptance:wait_for_app": "echo 'Waiting for app to be accessible' && while (! curl -s -o /dev/null localhost:3013/status) do sleep 1; done", + "test:acceptance:run": "mocha --recursive --reporter spec --timeout 15000", + "test:acceptance:dir": "npm -q run test:acceptance:wait_for_app && npm -q run test:acceptance:run -- $@", + "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- $@", + "compile:test:acceptance": "coffee -o test/acceptance/js -c test/acceptance/coffee", + "compile:test:smoke": "coffee -o test/smoke/js -c test/smoke/coffee", + "compile:app": "coffee -o app/js -c app/coffee && coffee -c app.coffee", + "compile": "npm run compile:app && npm run compile:test:acceptance && npm run compile:test:smoke", + "start": "npm run compile:app && node app.js", + "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 $@ test/acceptance/js", + "test:unit:_run": "mocha --recursive --reporter spec $@ test/unit/js", + "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- $@", + "compile:unit_tests": "[ -e test/unit ] && coffee -o test/unit/js -c test/unit/coffee || echo 'No unit tests to compile'", + "compile:acceptance_tests": "[ -e test/acceptance ] && coffee -o test/acceptance/js -c test/acceptance/coffee || echo 'No acceptance tests to compile'", + "compile:all": "npm run compile:app && npm run compile:unit_tests && npm run compile:acceptance_tests && compile:test:smoke", + "nodemon": "nodemon --config nodemon.json" + }, + "author": "James Allen <james@sharelatex.com>", "dependencies": { - "async": "0.2.9", - "body-parser": "^1.2.0", - "express": "^4.2.0", - "fs-extra": "^0.16.3", - "grunt-mkdir": "^1.0.0", - "heapdump": "^0.3.5", - "lockfile": "^1.0.3", - "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", - "lynx": "0.0.11", - "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.5.0", - "mkdirp": "0.3.5", - "mysql": "2.6.2", - "request": "^2.21.0", - "sequelize": "^2.1.3", - "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", - "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", - "sqlite3": "~3.1.8", - "underscore": "^1.8.2", - "v8-profiler": "^5.2.4", + "async": "0.2.9", + "body-parser": "^1.2.0", + "dockerode": "^2.5.3", + "express": "^4.2.0", + "fs-extra": "^0.16.3", + "grunt-mkdir": "^1.0.0", + "heapdump": "^0.3.5", + "lockfile": "^1.0.3", + "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", + "lynx": "0.0.11", + "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.5.0", + "mkdirp": "0.3.5", + "mysql": "2.6.2", + "request": "^2.21.0", + "sequelize": "^2.1.3", + "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", + "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", + "sqlite3": "~3.1.8", + "underscore": "^1.8.2", + "v8-profiler": "^5.2.4", "wrench": "~1.5.4" - }, + }, "devDependencies": { - "mocha": "1.10.0", - "coffee-script": "1.6.0", - "chai": "~1.8.1", - "sinon": "~1.7.3", - "grunt": "~0.4.2", - "grunt-contrib-coffee": "~0.7.0", - "grunt-contrib-clean": "~0.5.0", - "grunt-shell": "~0.6.1", - "grunt-mocha-test": "~0.8.1", - "sandboxed-module": "~0.3.0", - "timekeeper": "0.0.4", - "grunt-execute": "^0.1.5", - "bunyan": "^0.22.1", + "mocha": "1.10.0", + "coffee-script": "1.6.0", + "chai": "~1.8.1", + "sinon": "~1.7.3", + "grunt": "~0.4.2", + "grunt-contrib-coffee": "~0.7.0", + "grunt-contrib-clean": "~0.5.0", + "grunt-shell": "~0.6.1", + "grunt-mocha-test": "~0.8.1", + "sandboxed-module": "~0.3.0", + "timekeeper": "0.0.4", + "grunt-execute": "^0.1.5", + "bunyan": "^0.22.1", "grunt-bunyan": "^0.5.0" } } diff --git a/services/clsi/test/acceptance/coffee/helpers/Client.coffee b/services/clsi/test/acceptance/coffee/helpers/Client.coffee index c67b4251b1..76634966af 100644 --- a/services/clsi/test/acceptance/coffee/helpers/Client.coffee +++ b/services/clsi/test/acceptance/coffee/helpers/Client.coffee @@ -11,6 +11,7 @@ module.exports = Client = Math.random().toString(16).slice(2) compile: (project_id, data, callback = (error, res, body) ->) -> + console.log("#{@host}/project/#{project_id}/compile") request.post { url: "#{@host}/project/#{project_id}/compile" json: From 5cf3c904cb3f028201b26c527dedbbb989006046 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 13 Feb 2018 12:05:10 +0000 Subject: [PATCH 327/754] mvp needs hacked pacth in docker runner wip most tests pass --- services/clsi/.gitignore | 1 + services/clsi/Dockerfile | 16 +- services/clsi/Jenkinsfile | 36 +- services/clsi/Makefile | 5 + services/clsi/app/coffee/CommandRunner.coffee | 2 + .../clsi/app/coffee/CompileController.coffee | 3 + .../clsi/app/coffee/CompileManager.coffee | 12 +- services/clsi/app/coffee/LatexRunner.coffee | 1 + .../clsi/app/coffee/OutputFileFinder.coffee | 2 - .../clsi/app/coffee/ResourceWriter.coffee | 1 + services/clsi/bin/synctex | Bin 0 -> 90472 bytes services/clsi/config/settings.defaults.coffee | 3 + services/clsi/docker-compose.ci.yml | 10 +- services/clsi/docker-compose.yml | 8 +- services/clsi/docker-runner | 2 +- services/clsi/package-lock.json | 4702 ++++++++++------- services/clsi/package.json | 112 +- .../coffee/ExampleDocumentTests.coffee | 3 +- .../acceptance/coffee/helpers/Client.coffee | 1 + 19 files changed, 3008 insertions(+), 1912 deletions(-) create mode 100755 services/clsi/bin/synctex diff --git a/services/clsi/.gitignore b/services/clsi/.gitignore index 99e9760877..476f2d1feb 100644 --- a/services/clsi/.gitignore +++ b/services/clsi/.gitignore @@ -14,3 +14,4 @@ cache db.sqlite config/* bin/synctex +npm-debug.log diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index d98547cad5..83e452d392 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -2,17 +2,21 @@ FROM node:6.9.5 RUN wget -qO- https://get.docker.com/ | sh -# ---- Copy Files/Build ---- -WORKDIR /app +run apt-get install poppler-utils vim ghostscript --yes + +# run git build-essential --yes +# RUN git clone https://github.com/netblue30/firejail.git +# RUN cd firejail && ./configure && make && make install-strip +# run mkdir /data + COPY ./ /app -# Build react/vue/angular bundle static files -# RUN npm run build + +WORKDIR /app + RUN npm install RUN npm run compile -EXPOSE 3013 - ENV SHARELATEX_CONFIG /app/config/settings.production.coffee ENV NODE_ENV production diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index ab90aaae29..bc9ba0142f 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -9,34 +9,9 @@ pipeline { } stages { - stage('Install') { - agent { - docker { - image 'node:6.9.5' - args "-v /var/lib/jenkins/.npm:/tmp/.npm -e HOME=/tmp" - reuseNode true - } - } + stage('Build') { steps { - // we need to disable logallrefupdates, else git clones - // during the npm install will require git to lookup the - // user id which does not exist in the container's - // /etc/passwd file, causing the clone to fail. - sh 'git config --global core.logallrefupdates false' - sh 'rm -rf node_modules' - sh 'npm install && npm rebuild' - } - } - - stage('Compile') { - agent { - docker { - image 'node:6.9.5' - reuseNode true - } - } - steps { - sh 'npm run compile:all' + sh 'make build' } } @@ -54,12 +29,7 @@ pipeline { stage('Package and publish build') { steps { - sh 'echo ${BUILD_NUMBER} > build_number.txt' - sh 'touch build.tar.gz' // Avoid tar warning about files changing during read - sh 'tar -czf build.tar.gz --exclude=build.tar.gz --exclude-vcs .' - 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") - } + sh 'make publish' } } diff --git a/services/clsi/Makefile b/services/clsi/Makefile index 6407885d7a..6e7fca5f18 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -25,5 +25,10 @@ test_acceptance: test_clean # clear the database before each acceptance test run test_clean: $(DOCKER_COMPOSE) down +build: + docker build --pull --tag quay.io/sharelatex/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) . + +publish: + docker push quay.io/sharelatex/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) .PHONY: clean test test_unit test_acceptance test_clean build publish diff --git a/services/clsi/app/coffee/CommandRunner.coffee b/services/clsi/app/coffee/CommandRunner.coffee index f47af00177..969b55f3a6 100644 --- a/services/clsi/app/coffee/CommandRunner.coffee +++ b/services/clsi/app/coffee/CommandRunner.coffee @@ -5,7 +5,9 @@ logger.info "using standard command runner" module.exports = CommandRunner = run: (project_id, command, directory, image, timeout, environment, callback = (error) ->) -> + console.log("Command runner", directory) command = (arg.replace('$COMPILE_DIR', directory) for arg in command) + console.log("Command runner 2", command) logger.log project_id: project_id, command: command, directory: directory, "running command" logger.warn "timeouts and sandboxing are not enabled with CommandRunner" diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 99973fdd10..52c0e147bf 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -34,11 +34,14 @@ module.exports = CompileController = status = "error" code = 500 logger.error err: error, project_id: request.project_id, "error running compile" + else status = "failure" for file in outputFiles if file.path?.match(/output\.pdf$/) status = "success" + if status == "failure" + logger.err project_id: request.project_id, outputFiles:outputFiles, "project failed to compile successfully, no output.pdf generated" timer.done() res.status(code or 200).send { diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 167a80e135..d3d319af94 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -41,6 +41,7 @@ module.exports = CompileManager = doCompile: (request, callback = (error, outputFiles) ->) -> compileDir = getCompileDir(request.project_id, request.user_id) + console.log("doCompile",compileDir ) timer = new Metrics.Timer("write-to-disk") logger.log project_id: request.project_id, user_id: request.user_id, "syncing resources to disk" @@ -206,9 +207,14 @@ module.exports = CompileManager = file_path = base_dir + "/" + file_name compileDir = getCompileDir(project_id, user_id) synctex_path = Path.join(compileDir, "output.pdf") - CompileManager._runSynctex ["code", synctex_path, file_path, line, column], (error, stdout) -> + command = ["code", synctex_path, file_path, line, column] + CompileManager._runSynctex command, (error, stdout) -> return callback(error) if error? - logger.log project_id: project_id, user_id:user_id, file_name: file_name, line: line, column: column, stdout: stdout, "synctex code output" + if stdout.toLowerCase().indexOf("warning") == -1 + logType = "log" + else + logType = "err" + logger[logType] project_id: project_id, user_id:user_id, file_name: file_name, line: line, column: column, command:command, stdout: stdout, "synctex code output" callback null, CompileManager._parseSynctexFromCodeOutput(stdout) syncFromPdf: (project_id, user_id, page, h, v, callback = (error, filePositions) ->) -> @@ -216,6 +222,7 @@ module.exports = CompileManager = base_dir = Settings.path.synctexBaseDir(compileName) compileDir = getCompileDir(project_id, user_id) synctex_path = Path.join(compileDir, "output.pdf") + logger.log({base_dir, project_id, synctex_path}, "base diiir") CompileManager._runSynctex ["pdf", synctex_path, page, h, v], (error, stdout) -> return callback(error) if error? logger.log project_id: project_id, user_id:user_id, page: page, h: h, v:v, stdout: stdout, "synctex pdf output" @@ -243,6 +250,7 @@ module.exports = CompileManager = return callback(error) if error? if Settings.clsi?.synctexCommandWrapper? [bin_path, args] = Settings.clsi?.synctexCommandWrapper bin_path, args + logger.log({bin_path, args}, "synctex being run") child_process.execFile bin_path, args, timeout: 10 * seconds, (error, stdout, stderr) -> if error? logger.err err:error, args:args, "error running synctex" diff --git a/services/clsi/app/coffee/LatexRunner.coffee b/services/clsi/app/coffee/LatexRunner.coffee index 6a5a4f6ae2..11e71e53bd 100644 --- a/services/clsi/app/coffee/LatexRunner.coffee +++ b/services/clsi/app/coffee/LatexRunner.coffee @@ -8,6 +8,7 @@ ProcessTable = {} # table of currently running jobs (pids or docker container n module.exports = LatexRunner = runLatex: (project_id, options, callback = (error) ->) -> + console.log("LatexRunner", options.directory) {directory, mainFile, compiler, timeout, image, environment} = options compiler ||= "pdflatex" timeout ||= 60000 # milliseconds diff --git a/services/clsi/app/coffee/OutputFileFinder.coffee b/services/clsi/app/coffee/OutputFileFinder.coffee index 4b07f6e13a..662440b576 100644 --- a/services/clsi/app/coffee/OutputFileFinder.coffee +++ b/services/clsi/app/coffee/OutputFileFinder.coffee @@ -10,8 +10,6 @@ module.exports = OutputFileFinder = for resource in resources incomingResources[resource.path] = true - logger.log directory: directory, "getting output files" - OutputFileFinder._getAllFiles directory, (error, allFiles = []) -> if error? logger.err err:error, "error finding all output files" diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index 0b6aef5b72..66cfbfa0f1 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -109,6 +109,7 @@ module.exports = ResourceWriter = callback() _writeResourceToDisk: (project_id, resource, basePath, callback = (error) ->) -> + console.log("_writeResourceToDisk", basePath, resource.path) ResourceWriter.checkPath basePath, resource.path, (error, path) -> return callback(error) if error? mkdirp Path.dirname(path), (error) -> diff --git a/services/clsi/bin/synctex b/services/clsi/bin/synctex new file mode 100755 index 0000000000000000000000000000000000000000..99044b97a0259b30afbf0bfb34365e4f1270479e GIT binary patch literal 90472 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:nwe6m+2D>* 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 Ww?EQ8J?~lm(LUq<N}>K2<Nq%(A5^6P literal 0 HcmV?d00001 diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index 448d13b393..4835b4feb9 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -40,4 +40,7 @@ if process.env["COMMAND_RUNNER"] user: process.env["TEXLIVE_IMAGE_USER"] or "tex" expireProjectAfterIdleMs: 24 * 60 * 60 * 1000 checkProjectsIntervalMs: 10 * 60 * 1000 + module.exports.path.sandboxedCompilesHostDir = process.env["COMPILES_HOST_DIR"] + +console.log module.exports \ No newline at end of file diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 9f40ba879d..f34280d803 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -7,17 +7,11 @@ version: "2" services: test_unit: - image: node:6.9.5 - volumes: - - .:/app - working_dir: /app + image: quay.io/sharelatex/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER entrypoint: npm run test:unit:_run test_acceptance: - image: node:6.9.5 - volumes: - - .:/app - working_dir: /app + image: quay.io/sharelatex/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER environment: REDIS_HOST: redis MONGO_HOST: mongo diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 4a25bc38a7..d09ea6e0a6 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -20,9 +20,7 @@ services: environment: REDIS_HOST: redis MONGO_HOST: mongo - CLSI_HOST: clsi depends_on: - - clsi - redis - mongo working_dir: /app @@ -34,18 +32,18 @@ services: mongo: image: mongo:3.4 - clsi: + app: image: gcr.io/henry-terraform-admin/clsi build: . environment: - TEXLIVE_IMAGE: quay.io/sharelatex/texlive-small:latest + TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2017.1 TEXLIVE_IMAGE_USER: root # Not ideal, but makes running in dev very simple COMMAND_RUNNER: docker-runner-sharelatex SHARELATEX_CONFIG: /app/config/settings.defaults.coffee COMPILES_HOST_DIR: $PWD/compiles volumes: - - .:/app:cached - /var/run/docker.sock:/var/run/docker.sock - ./docker-runner:/app/node_modules/docker-runner-sharelatex + - /Users/henry/Projects/sharelatex-dev-environment/clsi/compiles:/app/compiles ports: - 3013:3013 \ No newline at end of file diff --git a/services/clsi/docker-runner b/services/clsi/docker-runner index f861a1c810..9a732b2594 160000 --- a/services/clsi/docker-runner +++ b/services/clsi/docker-runner @@ -1 +1 @@ -Subproject commit f861a1c810ad844a6e00e82f2ebedac26dcad8b7 +Subproject commit 9a732b2594f014a722f0c16150cf848b9512430f diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index 4c110ea513..5f30ca5464 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -13,145 +13,11 @@ "through": "2.3.8" } }, - "abbrev": { - "version": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg=" - }, - "accepts": { - "version": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", - "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", - "requires": { - "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "negotiator": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz" - } - }, - "ajv": { - "version": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "requires": { - "co": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "fast-deep-equal": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", - "fast-json-stable-stringify": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "json-schema-traverse": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz" - } - }, - "ansi-regex": { - "version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-0.2.0.tgz", - "integrity": "sha1-NZq0sV3NZLptdHNLcsNjYKmvLBk=", - "dev": true - }, - "aproba": { - "version": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=" - }, - "are-we-there-yet": { - "version": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", - "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", - "requires": { - "delegates": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz" - }, - "dependencies": { - "isarray": { - "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "readable-stream": { - "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", - "requires": { - "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "isarray": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" - } - }, - "string_decoder": { - "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", - "requires": { - "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" - } - } - } - }, - "argparse": { - "version": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", - "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", - "dev": true, - "requires": { - "underscore": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", - "underscore.string": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz" - }, - "dependencies": { - "underscore": { - "version": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", - "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", - "dev": true - }, - "underscore.string": { - "version": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", - "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=", - "dev": true - } - } - }, - "array-flatten": { - "version": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "asn1": { - "version": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" - }, - "assert-plus": { - "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "assertion-error": { - "version": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", - "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", - "dev": true - }, "async": { - "version": "https://registry.npmjs.org/async/-/async-0.2.9.tgz", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.9.tgz", "integrity": "sha1-32MGD789Myhqdqr21Vophtn/hhk=" }, - "asynckit": { - "version": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "aws-sign2": { - "version": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" - }, - "balanced-match": { - "version": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "bcrypt-pbkdf": { - "version": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "optional": true, - "requires": { - "tweetnacl": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" - } - }, - "bignumber.js": { - "version": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.7.tgz", - "integrity": "sha1-husHB89qURCQnSPm6nQ0wU9QDxw=" - }, "bl": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz", @@ -196,122 +62,343 @@ "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" } }, - "bluebird": { - "version": "https://registry.npmjs.org/bluebird/-/bluebird-2.9.34.tgz", - "integrity": "sha1-L3tOyAIWMoqf3evfacjUlC/v99g=" - }, "body-parser": { - "version": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", "requires": { - "bytes": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "content-type": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "depd": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "http-errors": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "iconv-lite": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "qs": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "raw-body": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "type-is": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz" + "bytes": "3.0.0", + "content-type": "1.0.4", + "debug": "2.6.9", + "depd": "1.1.2", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "on-finished": "2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "type-is": "1.6.15" + }, + "dependencies": { + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": "1.4.0" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + } + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "mime-db": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + }, + "mime-types": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "requires": { + "mime-db": "1.30.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + } + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + }, + "type-is": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "requires": { + "media-typer": "0.3.0", + "mime-types": "2.1.17" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + } } }, - "boom": { - "version": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", - "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", - "requires": { - "hoek": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz" - } - }, - "brace-expansion": { - "version": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", - "requires": { - "balanced-match": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "concat-map": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" - } + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true }, "bunyan": { - "version": "https://registry.npmjs.org/bunyan/-/bunyan-0.22.3.tgz", + "version": "0.22.3", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-0.22.3.tgz", "integrity": "sha1-ehncG0yMZF90AkGnQPIkUUfGfsI=", "dev": true, "requires": { - "dtrace-provider": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.2.8.tgz", - "mv": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz" + "mv": "2.1.1" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "optional": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true, + "optional": true + }, + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "dev": true, + "optional": true, + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "optional": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true, + "optional": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", + "dev": true, + "optional": true, + "requires": { + "mkdirp": "0.5.1", + "ncp": "2.0.0", + "rimraf": "2.4.5" + } + }, + "ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "optional": true + }, + "rimraf": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", + "dev": true, + "optional": true, + "requires": { + "glob": "6.0.4" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } } }, - "buster-core": { - "version": "https://registry.npmjs.org/buster-core/-/buster-core-0.6.4.tgz", - "integrity": "sha1-J79rrWdCROpyDzEdkAoMoct4YFA=", - "dev": true - }, - "buster-format": { - "version": "https://registry.npmjs.org/buster-format/-/buster-format-0.5.6.tgz", - "integrity": "sha1-K4bDIuz14bCubm55Bev884fSq5U=", - "dev": true, - "requires": { - "buster-core": "https://registry.npmjs.org/buster-core/-/buster-core-0.6.4.tgz" - } - }, - "bytes": { - "version": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" - }, - "caseless": { - "version": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, "chai": { - "version": "https://registry.npmjs.org/chai/-/chai-1.8.1.tgz", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-1.8.1.tgz", "integrity": "sha1-zHeGbV5+vKK9dRRLHtw3Coh4X3I=", "dev": true, "requires": { - "assertion-error": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", - "deep-eql": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz" + "assertion-error": "1.0.0", + "deep-eql": "0.1.3" + }, + "dependencies": { + "assertion-error": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", + "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", + "dev": true + }, + "deep-eql": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "dev": true, + "requires": { + "type-detect": "0.1.1" + } + }, + "type-detect": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "dev": true + } } }, - "chalk": { - "version": "https://registry.npmjs.org/chalk/-/chalk-0.3.0.tgz", - "integrity": "sha1-HJhDdzfxGZ68wdTEj9Qbn5yOjyM=", - "dev": true, - "requires": { - "ansi-styles": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-0.2.0.tgz", - "has-color": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz" - } - }, - "co": { - "version": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, - "code-point-at": { - "version": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, "coffee-script": { - "version": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=", "dev": true }, - "colors": { - "version": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", - "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", - "dev": true - }, - "combined-stream": { - "version": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", - "requires": { - "delayed-stream": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" - } - }, - "commander": { - "version": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz", - "integrity": "sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg=" - }, - "concat-map": { - "version": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, "concat-stream": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", @@ -342,58 +429,10 @@ } } }, - "console-control-strings": { - "version": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, - "content-disposition": { - "version": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" - }, - "content-type": { - "version": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=" - }, - "cookie": { - "version": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" - }, - "cookie-signature": { - "version": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, "core-util-is": { "version": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, - "cryptiles": { - "version": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", - "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", - "requires": { - "boom": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz" - }, - "dependencies": { - "boom": { - "version": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", - "integrity": "sha1-XdnabuOl8wIHdDYpDLcX0/SlTgI=", - "requires": { - "hoek": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz" - } - } - } - }, - "dashdash": { - "version": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - } - }, - "dateformat": { - "version": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", - "integrity": "sha1-sCIMAt6YYXQztyhRz0fePfLNvuk=", - "dev": true - }, "debug": { "version": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", @@ -401,42 +440,6 @@ "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" } }, - "deep-eql": { - "version": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", - "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", - "dev": true, - "requires": { - "type-detect": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz" - } - }, - "deep-extend": { - "version": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", - "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=" - }, - "delayed-stream": { - "version": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "delegates": { - "version": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" - }, - "depd": { - "version": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "destroy": { - "version": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "detect-libc": { - "version": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" - }, - "diff": { - "version": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", - "integrity": "sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ=" - }, "docker-modem": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-1.0.4.tgz", @@ -471,32 +474,6 @@ "tar-fs": "1.12.0" } }, - "dottie": { - "version": "https://registry.npmjs.org/dottie/-/dottie-0.3.1.tgz", - "integrity": "sha1-QYX3d0iZYKwIQTHeP5oUoXm7dJ8=" - }, - "dtrace-provider": { - "version": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.2.8.tgz", - "integrity": "sha1-4kPxkhmqlfvw2PL/sH9b1k6U/iA=", - "dev": true, - "optional": true - }, - "ecc-jsbn": { - "version": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "optional": true, - "requires": { - "jsbn": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz" - } - }, - "ee-first": { - "version": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "encodeurl": { - "version": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", - "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" - }, "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", @@ -505,760 +482,1051 @@ "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz" } }, - "escape-html": { - "version": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "esprima": { - "version": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", - "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", - "dev": true - }, - "etag": { - "version": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "eventemitter2": { - "version": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", - "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", - "dev": true - }, - "exit": { - "version": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, "express": { - "version": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", "requires": { - "accepts": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", - "array-flatten": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "body-parser": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "content-disposition": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "content-type": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "cookie": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "cookie-signature": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "depd": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "encodeurl": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", - "escape-html": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "etag": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "finalhandler": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", - "fresh": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "merge-descriptors": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "methods": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "parseurl": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "path-to-regexp": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "proxy-addr": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", - "qs": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "range-parser": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "send": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", - "serve-static": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", - "setprototypeof": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "statuses": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "type-is": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", - "utils-merge": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "vary": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" + "accepts": "1.3.4", + "array-flatten": "1.1.1", + "body-parser": "1.18.2", + "content-disposition": "0.5.2", + "content-type": "1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "1.1.2", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "etag": "1.8.1", + "finalhandler": "1.1.0", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "1.1.2", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "2.0.2", + "qs": "6.5.1", + "range-parser": "1.2.0", + "safe-buffer": "5.1.1", + "send": "0.16.1", + "serve-static": "1.13.1", + "setprototypeof": "1.1.0", + "statuses": "1.3.1", + "type-is": "1.6.15", + "utils-merge": "1.0.1", + "vary": "1.1.2" }, "dependencies": { + "accepts": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", + "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", + "requires": { + "mime-types": "2.1.17", + "negotiator": "0.6.1" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "finalhandler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "requires": { + "debug": "2.6.9", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "statuses": "1.3.1", + "unpipe": "1.0.0" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": "1.3.1" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + } + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ipaddr.js": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz", + "integrity": "sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + }, + "mime-db": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + }, + "mime-types": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "requires": { + "mime-db": "1.30.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "proxy-addr": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", + "integrity": "sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=", + "requires": { + "forwarded": "0.1.2", + "ipaddr.js": "1.5.2" + } + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "send": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", + "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", + "requires": { + "debug": "2.6.9", + "depd": "1.1.2", + "destroy": "1.0.4", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "etag": "1.8.1", + "fresh": "0.5.2", + "http-errors": "1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "2.3.0", + "range-parser": "1.2.0", + "statuses": "1.3.1" + } + }, + "serve-static": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", + "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", + "requires": { + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "parseurl": "1.3.2", + "send": "0.16.1" + } + }, "setprototypeof": { - "version": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, "statuses": { - "version": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" - } - } - }, - "extend": { - "version": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" - }, - "extsprintf": { - "version": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fast-deep-equal": { - "version": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", - "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=" - }, - "fast-json-stable-stringify": { - "version": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" - }, - "finalhandler": { - "version": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", - "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", - "requires": { - "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "encodeurl": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", - "escape-html": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "parseurl": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "statuses": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "unpipe": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" - }, - "dependencies": { - "statuses": { - "version": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" - } - } - }, - "findup-sync": { - "version": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", - "integrity": "sha1-fz56l7gjksZTvwZYm9hRkOk8NoM=", - "dev": true, - "requires": { - "glob": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", - "lodash": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz" - }, - "dependencies": { - "glob": { - "version": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", - "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", - "dev": true, + }, + "type-is": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", "requires": { - "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz" + "media-typer": "0.3.0", + "mime-types": "2.1.17" } }, - "lodash": { - "version": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", - "dev": true + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, - "minimatch": { - "version": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", - "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", - "dev": true, - "requires": { - "lru-cache": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "sigmund": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz" - } + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" } } }, - "forever-agent": { - "version": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", - "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", - "requires": { - "asynckit": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "combined-stream": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz" - } - }, - "forwarded": { - "version": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" - }, - "fresh": { - "version": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, "fs-extra": { - "version": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.16.5.tgz", + "version": "0.16.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.16.5.tgz", "integrity": "sha1-GtZh+myGyWCM0bSe/G/Og0k5p1A=", "requires": { - "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", - "jsonfile": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "rimraf": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz" - } - }, - "fs.realpath": { - "version": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "fstream": { - "version": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", - "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", - "requires": { - "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "rimraf": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz" + "graceful-fs": "3.0.11", + "jsonfile": "2.4.0", + "rimraf": "2.6.2" }, "dependencies": { - "graceful-fs": { - "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, - "mkdirp": { - "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { - "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + "balanced-match": "1.0.0", + "concat-map": "0.0.1" } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-fs": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", + "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", + "requires": { + "natives": "1.1.1" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "requires": { + "graceful-fs": "4.1.11" + }, + "dependencies": { + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "optional": true + } + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "1.1.11" + } + }, + "natives": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.1.tgz", + "integrity": "sha512-8eRaxn8u/4wN8tGkhlc2cgwwvOLMLUMUn4IYTexMgWd+LyUDfeXVkk2ygQR0hvIHbJQXgHujia3ieUUDwNGkEA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "requires": { + "glob": "7.1.2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" } } }, - "fstream-ignore": { - "version": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", - "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", - "requires": { - "fstream": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", - "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" - } - }, - "gauge": { - "version": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "requires": { - "aproba": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "console-control-strings": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "has-unicode": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "signal-exit": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "string-width": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "wide-align": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz" - } - }, - "generic-pool": { - "version": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.2.0.tgz", - "integrity": "sha1-i0ZcGnWI6p3SuxM72gu2a/74pj4=" - }, - "getobject": { - "version": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", - "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", - "dev": true - }, - "getpass": { - "version": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - } - }, - "glob": { - "version": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", - "requires": { - "fs.realpath": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "inflight": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" - } - }, - "graceful-fs": { - "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", - "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", - "requires": { - "natives": "https://registry.npmjs.org/natives/-/natives-1.1.1.tgz" - } - }, - "growl": { - "version": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", - "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=" - }, "grunt": { - "version": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", "integrity": "sha1-VpN81RlDJK3/bSB2MYMqnWuk5/A=", "dev": true, "requires": { - "async": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", - "coffee-script": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz", - "colors": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", - "dateformat": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", - "eventemitter2": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", - "exit": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "findup-sync": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", - "getobject": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", - "glob": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", - "grunt-legacy-log": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", - "grunt-legacy-util": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", - "hooker": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", - "iconv-lite": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", - "js-yaml": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", - "lodash": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", - "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "nopt": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "rimraf": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "underscore.string": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", - "which": "https://registry.npmjs.org/which/-/which-1.0.9.tgz" + "async": "0.1.22", + "coffee-script": "1.3.3", + "colors": "0.6.2", + "dateformat": "1.0.2-1.2.3", + "eventemitter2": "0.4.14", + "exit": "0.1.2", + "findup-sync": "0.1.3", + "getobject": "0.1.0", + "glob": "3.1.21", + "grunt-legacy-log": "0.1.3", + "grunt-legacy-util": "0.2.0", + "hooker": "0.2.3", + "iconv-lite": "0.2.11", + "js-yaml": "2.0.5", + "lodash": "0.9.2", + "minimatch": "0.2.14", + "nopt": "1.0.10", + "rimraf": "2.2.8", + "underscore.string": "2.2.1", + "which": "1.0.9" }, "dependencies": { + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "argparse": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", + "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", + "dev": true, + "requires": { + "underscore": "1.7.0", + "underscore.string": "2.4.0" + }, + "dependencies": { + "underscore.string": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", + "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=", + "dev": true + } + } + }, "async": { - "version": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=", "dev": true }, "coffee-script": { - "version": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz", "integrity": "sha1-FQ1rTLUiiUNp7+1qIQHCC8f0pPQ=", "dev": true }, + "colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", + "dev": true + }, + "dateformat": { + "version": "1.0.2-1.2.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", + "integrity": "sha1-sCIMAt6YYXQztyhRz0fePfLNvuk=", + "dev": true + }, + "esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", + "dev": true + }, + "eventemitter2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", + "dev": true + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "findup-sync": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", + "integrity": "sha1-fz56l7gjksZTvwZYm9hRkOk8NoM=", + "dev": true, + "requires": { + "glob": "3.2.11", + "lodash": "2.4.2" + }, + "dependencies": { + "glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "minimatch": "0.3.0" + } + }, + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "dev": true, + "requires": { + "lru-cache": "2.7.3", + "sigmund": "1.0.1" + } + } + } + }, + "getobject": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", + "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", + "dev": true + }, "glob": { - "version": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", + "version": "3.1.21", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", "dev": true, "requires": { - "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", - "inherits": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", - "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz" + "graceful-fs": "1.2.3", + "inherits": "1.0.2", + "minimatch": "0.2.14" + }, + "dependencies": { + "inherits": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", + "dev": true + } } }, "graceful-fs": { - "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", "dev": true }, + "grunt-legacy-log": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", + "integrity": "sha1-7ClCboAwIa9ZAp+H0vnNczWgVTE=", + "dev": true, + "requires": { + "colors": "0.6.2", + "grunt-legacy-log-utils": "0.1.1", + "hooker": "0.2.3", + "lodash": "2.4.2", + "underscore.string": "2.3.3" + }, + "dependencies": { + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "underscore.string": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", + "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", + "dev": true + } + } + }, + "grunt-legacy-log-utils": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", + "integrity": "sha1-wHBrndkGThFvNvI/5OawSGcsD34=", + "dev": true, + "requires": { + "colors": "0.6.2", + "lodash": "2.4.2", + "underscore.string": "2.3.3" + }, + "dependencies": { + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "underscore.string": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", + "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", + "dev": true + } + } + }, + "grunt-legacy-util": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", + "integrity": "sha1-kzJIhNv343qf98Am3/RR2UqeVUs=", + "dev": true, + "requires": { + "async": "0.1.22", + "exit": "0.1.2", + "getobject": "0.1.0", + "hooker": "0.2.3", + "lodash": "0.9.2", + "underscore.string": "2.2.1", + "which": "1.0.9" + } + }, + "hooker": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", + "dev": true + }, "iconv-lite": { - "version": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", "integrity": "sha1-HOYKOleGSiktEyH/RgnKS7llrcg=", "dev": true }, "inherits": { - "version": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", - "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, + "js-yaml": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", + "integrity": "sha1-olrmUJmZ6X3yeMZxnaEb0Gh3Q6g=", + "dev": true, + "requires": { + "argparse": "0.1.16", + "esprima": "1.0.4" + } + }, "lodash": { - "version": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=", "dev": true }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, "minimatch": { - "version": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", "dev": true, "requires": { - "lru-cache": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "sigmund": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz" + "lru-cache": "2.7.3", + "sigmund": "1.0.1" } }, "nopt": { - "version": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", "dev": true, "requires": { - "abbrev": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" + "abbrev": "1.1.1" } }, "rimraf": { - "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", "dev": true + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "underscore": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", + "dev": true + }, + "underscore.string": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", + "integrity": "sha1-18D6KvXVoaZ/QlPa7pgTLnM/Dxk=", + "dev": true + }, + "which": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", + "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=", + "dev": true } } }, "grunt-bunyan": { - "version": "https://registry.npmjs.org/grunt-bunyan/-/grunt-bunyan-0.5.0.tgz", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/grunt-bunyan/-/grunt-bunyan-0.5.0.tgz", "integrity": "sha1-aCnXbgGZQ9owQTk2MaNuKsgpsWw=", "dev": true, "requires": { - "lodash": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz" + "lodash": "2.4.2" }, "dependencies": { "lodash": { - "version": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", "dev": true } } }, "grunt-contrib-clean": { - "version": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-0.5.0.tgz", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-0.5.0.tgz", "integrity": "sha1-9T397ghJsce0Dp67umn0jExgecU=", "dev": true, "requires": { - "rimraf": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz" + "rimraf": "2.2.8" }, "dependencies": { "rimraf": { - "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", "dev": true } } }, "grunt-contrib-coffee": { - "version": "https://registry.npmjs.org/grunt-contrib-coffee/-/grunt-contrib-coffee-0.7.0.tgz", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-coffee/-/grunt-contrib-coffee-0.7.0.tgz", "integrity": "sha1-ixIme3TnM4sfKcW4txj7n4mYLxM=", "dev": true, "requires": { - "coffee-script": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.3.tgz" + "coffee-script": "1.6.3" }, "dependencies": { "coffee-script": { - "version": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.3.tgz", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.3.tgz", "integrity": "sha1-Y1XTLPGwTN/2tITl5xF4Ky8MOb4=", "dev": true } } }, "grunt-execute": { - "version": "https://registry.npmjs.org/grunt-execute/-/grunt-execute-0.1.5.tgz", + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/grunt-execute/-/grunt-execute-0.1.5.tgz", "integrity": "sha1-yX64lDYS/vu3L749Mu+VIzxfouk=", "dev": true }, - "grunt-legacy-log": { - "version": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", - "integrity": "sha1-7ClCboAwIa9ZAp+H0vnNczWgVTE=", - "dev": true, - "requires": { - "colors": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", - "grunt-legacy-log-utils": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", - "hooker": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", - "lodash": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "underscore.string": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz" - }, - "dependencies": { - "lodash": { - "version": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", - "dev": true - }, - "underscore.string": { - "version": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", - "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", - "dev": true - } - } - }, - "grunt-legacy-log-utils": { - "version": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", - "integrity": "sha1-wHBrndkGThFvNvI/5OawSGcsD34=", - "dev": true, - "requires": { - "colors": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", - "lodash": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "underscore.string": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz" - }, - "dependencies": { - "lodash": { - "version": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", - "dev": true - }, - "underscore.string": { - "version": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", - "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", - "dev": true - } - } - }, - "grunt-legacy-util": { - "version": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", - "integrity": "sha1-kzJIhNv343qf98Am3/RR2UqeVUs=", - "dev": true, - "requires": { - "async": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", - "exit": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "getobject": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", - "hooker": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", - "lodash": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", - "underscore.string": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", - "which": "https://registry.npmjs.org/which/-/which-1.0.9.tgz" - }, - "dependencies": { - "async": { - "version": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", - "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=", - "dev": true - }, - "lodash": { - "version": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", - "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=", - "dev": true - } - } - }, "grunt-mkdir": { - "version": "https://registry.npmjs.org/grunt-mkdir/-/grunt-mkdir-1.0.0.tgz", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-mkdir/-/grunt-mkdir-1.0.0.tgz", "integrity": "sha1-c+GiasJKCFljY/TdlUsNMkheWOk=" }, "grunt-mocha-test": { - "version": "https://registry.npmjs.org/grunt-mocha-test/-/grunt-mocha-test-0.8.2.tgz", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/grunt-mocha-test/-/grunt-mocha-test-0.8.2.tgz", "integrity": "sha1-emGEuYhg0Phb3qrWvqob199bvus=", "dev": true, "requires": { - "mocha": "https://registry.npmjs.org/mocha/-/mocha-1.14.0.tgz" + "mocha": "1.14.0" }, "dependencies": { + "commander": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz", + "integrity": "sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg=", + "dev": true + }, + "diff": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", + "integrity": "sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ=", + "dev": true + }, "glob": { - "version": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", "dev": true, "requires": { - "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", - "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz" + "graceful-fs": "2.0.3", + "inherits": "2.0.3", + "minimatch": "0.2.14" } }, "graceful-fs": { - "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=", "dev": true }, + "growl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", + "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=", + "dev": true + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "dev": true, + "requires": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "dependencies": { + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", + "dev": true + }, + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", + "dev": true + } + } + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, "minimatch": { - "version": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", "dev": true, "requires": { - "lru-cache": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "sigmund": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz" + "lru-cache": "2.7.3", + "sigmund": "1.0.1" } }, "mocha": { - "version": "https://registry.npmjs.org/mocha/-/mocha-1.14.0.tgz", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.14.0.tgz", "integrity": "sha1-cT223FAAGRqdA1gZXQkIeQ7LYVc=", "dev": true, "requires": { - "commander": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz", + "commander": "2.0.0", "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "diff": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", - "glob": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", - "growl": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", - "jade": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", - "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz" + "diff": "1.0.7", + "glob": "3.2.3", + "growl": "1.7.0", + "jade": "0.26.3", + "mkdirp": "0.3.5" } + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true } } }, "grunt-shell": { - "version": "https://registry.npmjs.org/grunt-shell/-/grunt-shell-0.6.4.tgz", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/grunt-shell/-/grunt-shell-0.6.4.tgz", "integrity": "sha1-5KbRuSkSd2/ZOimcX2zGTpUlNlw=", "dev": true, "requires": { - "chalk": "https://registry.npmjs.org/chalk/-/chalk-0.3.0.tgz" - } - }, - "har-schema": { - "version": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", - "requires": { - "ajv": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "har-schema": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz" - } - }, - "has-color": { - "version": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", - "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", - "dev": true - }, - "has-unicode": { - "version": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" - }, - "hawk": { - "version": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", - "integrity": "sha1-r02RTrBl+bXOTZ0RwcshJu7MMDg=", - "requires": { - "boom": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", - "cryptiles": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", - "hoek": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", - "sntp": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz" - } - }, - "heapdump": { - "version": "https://registry.npmjs.org/heapdump/-/heapdump-0.3.9.tgz", - "integrity": "sha1-A8dOsN9dZ74Jgug0KbqcnSs7f3g=" - }, - "hoek": { - "version": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", - "integrity": "sha1-ctnQdU9/4lyi0BrY+PmpRJqJUm0=" - }, - "hooker": { - "version": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", - "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", - "dev": true - }, - "http-errors": { - "version": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "requires": { - "depd": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "setprototypeof": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "statuses": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz" + "chalk": "0.3.0" }, "dependencies": { - "depd": { - "version": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + "ansi-styles": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-0.2.0.tgz", + "integrity": "sha1-NZq0sV3NZLptdHNLcsNjYKmvLBk=", + "dev": true + }, + "chalk": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.3.0.tgz", + "integrity": "sha1-HJhDdzfxGZ68wdTEj9Qbn5yOjyM=", + "dev": true, + "requires": { + "ansi-styles": "0.2.0", + "has-color": "0.1.7" + } + }, + "has-color": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", + "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", + "dev": true } } }, - "http-signature": { - "version": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "jsprim": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "sshpk": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz" - } + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true }, - "iconv-lite": { - "version": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha1-90aPYBNfXl2tM5nAqBvpoWA6CCs=" + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true }, - "inflection": { - "version": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", - "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" - }, - "inflight": { - "version": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "wrappy": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" - } + "heapdump": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/heapdump/-/heapdump-0.3.9.tgz", + "integrity": "sha1-A8dOsN9dZ74Jgug0KbqcnSs7f3g=" }, "inherits": { "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, - "ini": { - "version": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=" - }, - "ipaddr.js": { - "version": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz", - "integrity": "sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A=" - }, - "is-fullwidth-code-point": { - "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz" - } - }, - "is-typedarray": { - "version": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, "isarray": { "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, - "isstream": { - "version": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "jade": { - "version": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", - "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", - "requires": { - "commander": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", - "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz" - }, - "dependencies": { - "commander": { - "version": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", - "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=" - }, - "mkdirp": { - "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", - "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=" - } - } - }, - "js-yaml": { - "version": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", - "integrity": "sha1-olrmUJmZ6X3yeMZxnaEb0Gh3Q6g=", - "dev": true, - "requires": { - "argparse": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", - "esprima": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz" - } - }, - "jsbn": { - "version": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true - }, - "json-schema": { - "version": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-schema-traverse": { - "version": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" - }, - "json-stable-stringify": { - "version": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "requires": { - "jsonify": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz" - } - }, - "json-stringify-safe": { - "version": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsonfile": { - "version": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "requires": { - "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz" - }, - "dependencies": { - "graceful-fs": { - "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "optional": true - } - } - }, - "jsonify": { - "version": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" - }, "jsonparse": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz", "integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=" }, - "jsprim": { - "version": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "extsprintf": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "json-schema": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "verror": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz" - } - }, "lockfile": { - "version": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.3.tgz", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.3.tgz", "integrity": "sha1-Jjj8OaAzHpysGgS3F5mTHJxQ33k=" }, - "lodash": { - "version": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" - }, "logger-sharelatex": { "version": "git+https://github.com/sharelatex/logger-sharelatex.git#9ee7b52eb2bbd8fcbb1e2c708587c1e93fd4c733", - "integrity": "sha1-aRMA+7GVHSmsRMbyj5cLhnueM9Q=", "requires": { "bunyan": "1.5.1", "coffee-script": "1.4.0", - "raven": "https://registry.npmjs.org/raven/-/raven-1.2.1.tgz" + "raven": "1.2.1" }, "dependencies": { + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "optional": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, "bunyan": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.5.1.tgz", "integrity": "sha1-X259RMQ7lS9WsPQTCeOrEjkbTi0=", "requires": { "dtrace-provider": "0.6.0", - "mv": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "mv": "2.1.1", "safe-json-stringify": "1.0.4" } }, @@ -1267,425 +1535,450 @@ "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.4.0.tgz", "integrity": "sha1-XjvIqsJsAajie/EHcixWVfWtfTY=" }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "optional": true + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, "dtrace-provider": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.6.0.tgz", "integrity": "sha1-CweNVReTfYcxAUUtkUZzdVe3XlE=", "optional": true, "requires": { - "nan": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz" + "nan": "2.8.0" } + }, + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "optional": true, + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "optional": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "optional": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "lsmod": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz", + "integrity": "sha1-mgD3bco26yP6BTUK/htYXUKZ5ks=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "optional": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "optional": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", + "optional": true, + "requires": { + "mkdirp": "0.5.1", + "ncp": "2.0.0", + "rimraf": "2.4.5" + } + }, + "nan": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", + "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=", + "optional": true + }, + "ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "optional": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "optional": true + }, + "raven": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/raven/-/raven-1.2.1.tgz", + "integrity": "sha1-lJwTTbAooZC3u/j3kKrlQbfAIL0=", + "requires": { + "cookie": "0.3.1", + "json-stringify-safe": "5.0.1", + "lsmod": "1.0.0", + "stack-trace": "0.0.9", + "uuid": "3.0.0" + } + }, + "rimraf": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", + "optional": true, + "requires": { + "glob": "6.0.4" + } + }, + "stack-trace": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", + "integrity": "sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU=" + }, + "uuid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz", + "integrity": "sha1-Zyj8BFnEUNeWqZwxg3VpvfZy1yg=" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" } } }, - "lru-cache": { - "version": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" - }, - "lsmod": { - "version": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz", - "integrity": "sha1-mgD3bco26yP6BTUK/htYXUKZ5ks=" - }, "lynx": { - "version": "https://registry.npmjs.org/lynx/-/lynx-0.0.11.tgz", + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.0.11.tgz", "integrity": "sha1-LPoU5EP9LZKlm3efQVZ84cxpZaM=", "requires": { - "mersenne": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz", - "statsd-parser": "https://registry.npmjs.org/statsd-parser/-/statsd-parser-0.0.4.tgz" + "mersenne": "0.0.4", + "statsd-parser": "0.0.4" + }, + "dependencies": { + "mersenne": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz", + "integrity": "sha1-QB/ex+whzbngPNPTAhOY2iGycIU=" + }, + "statsd-parser": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/statsd-parser/-/statsd-parser-0.0.4.tgz", + "integrity": "sha1-y9JDlTzELv/VSLXSI4jtaJ7GOb0=" + } } }, - "media-typer": { - "version": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "merge-descriptors": { - "version": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "mersenne": { - "version": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz", - "integrity": "sha1-QB/ex+whzbngPNPTAhOY2iGycIU=" - }, - "methods": { - "version": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, "metrics-sharelatex": { "version": "git+https://github.com/sharelatex/metrics-sharelatex.git#05bd57604115ae5efdca2a419fbef4f25e9a780f", - "integrity": "sha1-ruLAc3Tl1GZrAQjP/K4NeRajdW4=", "requires": { - "coffee-script": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", - "lynx": "https://registry.npmjs.org/lynx/-/lynx-0.1.1.tgz", - "underscore": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz" + "coffee-script": "1.6.0", + "lynx": "0.1.1", + "underscore": "1.6.0" }, "dependencies": { "coffee-script": { - "version": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=" }, "lynx": { - "version": "https://registry.npmjs.org/lynx/-/lynx-0.1.1.tgz", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.1.1.tgz", "integrity": "sha1-Mxjc7xaQi4KG6Bisz9sxzXQkj50=", "requires": { - "mersenne": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz", - "statsd-parser": "https://registry.npmjs.org/statsd-parser/-/statsd-parser-0.0.4.tgz" + "mersenne": "0.0.4", + "statsd-parser": "0.0.4" } }, + "mersenne": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz", + "integrity": "sha1-QB/ex+whzbngPNPTAhOY2iGycIU=" + }, + "statsd-parser": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/statsd-parser/-/statsd-parser-0.0.4.tgz", + "integrity": "sha1-y9JDlTzELv/VSLXSI4jtaJ7GOb0=" + }, "underscore": { - "version": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" } } }, - "mime": { - "version": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha1-Eh+evEnjdm8xGnbh+hyAA8SwOqY=" - }, - "mime-db": { - "version": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" - }, - "mime-types": { - "version": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", - "requires": { - "mime-db": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz" - } - }, - "minimatch": { - "version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", - "requires": { - "brace-expansion": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz" - } - }, "minimist": { "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "mkdirp": { - "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=" }, "mocha": { - "version": "https://registry.npmjs.org/mocha/-/mocha-1.10.0.tgz", - "integrity": "sha1-8WrMlQ75Vm+/kIvPttXQQWw50No=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz", + "integrity": "sha512-0RVnjg1HJsXY2YFDoTNzcc1NKhYuXKRrBAG2gDygmJJA136Cs2QlRliZG1mA0ap7cuaT30mw16luAeln+4RiNA==", "dev": true, "requires": { - "commander": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", - "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "diff": "https://registry.npmjs.org/diff/-/diff-1.0.2.tgz", - "glob": "https://registry.npmjs.org/glob/-/glob-3.2.1.tgz", - "growl": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", - "jade": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", - "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.3.tgz", - "ms": "https://registry.npmjs.org/ms/-/ms-0.3.0.tgz" + "browser-stdout": "1.3.0", + "commander": "2.11.0", + "debug": "3.1.0", + "diff": "3.3.1", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.3", + "he": "1.1.1", + "mkdirp": "0.5.1", + "supports-color": "4.4.0" }, "dependencies": { - "commander": { - "version": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", - "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "diff": { - "version": "https://registry.npmjs.org/diff/-/diff-1.0.2.tgz", - "integrity": "sha1-Suc/Gu6Nb89ITxoc53zmUdm38Mk=", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, "glob": { - "version": "https://registry.npmjs.org/glob/-/glob-3.2.1.tgz", - "integrity": "sha1-V69w7HO6IyO/4/KaBndl22TF11g=", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", - "inherits": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", - "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, - "graceful-fs": { - "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", - "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", + "growl": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", "dev": true }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, "inherits": { - "version": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", - "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, "minimatch": { - "version": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "lru-cache": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "sigmund": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz" + "brace-expansion": "1.1.11" } }, - "mkdirp": { - "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.3.tgz", - "integrity": "sha1-WV4lHBNww6aLqyE20ONIuBBa3xM=", + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, "ms": { - "version": "https://registry.npmjs.org/ms/-/ms-0.3.0.tgz", - "integrity": "sha1-A+3DSNYT5mpWSGz9rFO8vomcvWE=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true } } }, - "moment": { - "version": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz", - "integrity": "sha1-1usaRsvMFKKy+UNBEsH/iQfzE/0=" - }, "ms": { "version": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, - "mv": { - "version": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", - "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", - "optional": true, - "requires": { - "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "ncp": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "rimraf": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz" - }, - "dependencies": { - "glob": { - "version": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "optional": true, - "requires": { - "inflight": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" - } - }, - "mkdirp": { - "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "optional": true, - "requires": { - "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" - } - }, - "rimraf": { - "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", - "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", - "optional": true, - "requires": { - "glob": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz" - } - } - } - }, "mysql": { - "version": "https://registry.npmjs.org/mysql/-/mysql-2.6.2.tgz", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.6.2.tgz", "integrity": "sha1-k3Gd0yT1fUHET7bX+GDPjmOFk7A=", "requires": { - "bignumber.js": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.7.tgz", - "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "require-all": "https://registry.npmjs.org/require-all/-/require-all-1.0.0.tgz" - } - }, - "nan": { - "version": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", - "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=" - }, - "natives": { - "version": "https://registry.npmjs.org/natives/-/natives-1.1.1.tgz", - "integrity": "sha1-ARrM4ffL2H97prMJPWzZOSvhxXQ=" - }, - "ncp": { - "version": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", - "optional": true - }, - "negotiator": { - "version": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" - }, - "node-pre-gyp": { - "version": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz", - "integrity": "sha1-wA6WhgsjwOFCCse+/FBE4deNhkk=", - "requires": { - "detect-libc": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "hawk": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "nopt": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "npmlog": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "rc": "https://registry.npmjs.org/rc/-/rc-1.2.3.tgz", - "request": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", - "rimraf": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "semver": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "tar": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "tar-pack": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.1.tgz" + "bignumber.js": "2.0.7", + "readable-stream": "1.1.14", + "require-all": "1.0.0" }, "dependencies": { - "ajv": { - "version": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "bignumber.js": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.7.tgz", + "integrity": "sha1-husHB89qURCQnSPm6nQ0wU9QDxw=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "requires": { - "co": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "json-stable-stringify": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" } }, - "assert-plus": { - "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" + "require-all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/require-all/-/require-all-1.0.0.tgz", + "integrity": "sha1-hINwjnzkxt+tmItQgPl4KbktIic=" }, - "aws-sign2": { - "version": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" - }, - "boom": { - "version": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "requires": { - "hoek": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" - } - }, - "cryptiles": { - "version": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", - "requires": { - "boom": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz" - } - }, - "form-data": { - "version": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", - "requires": { - "asynckit": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "combined-stream": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz" - } - }, - "har-schema": { - "version": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", - "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" - }, - "har-validator": { - "version": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", - "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", - "requires": { - "ajv": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "har-schema": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz" - } - }, - "hawk": { - "version": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", - "requires": { - "boom": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "cryptiles": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "hoek": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "sntp": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" - } - }, - "hoek": { - "version": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" - }, - "http-signature": { - "version": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", - "requires": { - "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "jsprim": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "sshpk": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz" - } - }, - "mkdirp": { - "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" - } - }, - "performance-now": { - "version": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" - }, - "qs": { - "version": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" - }, - "request": { - "version": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", - "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", - "requires": { - "aws-sign2": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "aws4": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "caseless": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "combined-stream": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "extend": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "forever-agent": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "form-data": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "har-validator": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", - "hawk": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "http-signature": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "is-typedarray": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "isstream": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "json-stringify-safe": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "oauth-sign": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "performance-now": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "qs": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "stringstream": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "tough-cookie": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", - "tunnel-agent": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "uuid": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz" - } - }, - "sntp": { - "version": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", - "requires": { - "hoek": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" - } + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" } } }, - "nopt": { - "version": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", - "requires": { - "abbrev": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "osenv": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz" - } - }, - "npmlog": { - "version": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=", - "requires": { - "are-we-there-yet": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", - "console-control-strings": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "gauge": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "set-blocking": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" - } - }, - "number-is-nan": { - "version": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "oauth-sign": { - "version": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" - }, - "object-assign": { - "version": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "on-finished": { - "version": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" - } + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" }, "once": { "version": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1694,50 +1987,10 @@ "wrappy": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" } }, - "os-homedir": { - "version": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, - "os-tmpdir": { - "version": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "osenv": { - "version": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", - "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", - "requires": { - "os-homedir": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "os-tmpdir": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" - } - }, - "parseurl": { - "version": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" - }, - "path-is-absolute": { - "version": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-to-regexp": { - "version": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "performance-now": { - "version": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, "process-nextick-args": { "version": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" }, - "proxy-addr": { - "version": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", - "integrity": "sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=", - "requires": { - "forwarded": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "ipaddr.js": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz" - } - }, "pump": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", @@ -1747,115 +2000,381 @@ "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz" } }, - "punycode": { - "version": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, - "qs": { - "version": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha1-NJzfbu+J7EXBLX1es/wMhwNDptg=" - }, - "range-parser": { - "version": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" - }, - "raven": { - "version": "https://registry.npmjs.org/raven/-/raven-1.2.1.tgz", - "integrity": "sha1-lJwTTbAooZC3u/j3kKrlQbfAIL0=", - "requires": { - "cookie": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "json-stringify-safe": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "lsmod": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz", - "stack-trace": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", - "uuid": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz" - } - }, - "raw-body": { - "version": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", - "requires": { - "bytes": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "http-errors": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "iconv-lite": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "unpipe": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" - } - }, - "rc": { - "version": "https://registry.npmjs.org/rc/-/rc-1.2.3.tgz", - "integrity": "sha1-UVdakA+N1oOBxxC0cSwhVMPiA1s=", - "requires": { - "deep-extend": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", - "ini": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "minimist": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "strip-json-comments": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" - }, - "dependencies": { - "minimist": { - "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - } - } - }, - "readable-stream": { - "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "requires": { - "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "isarray": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" - } - }, "request": { - "version": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", - "integrity": "sha1-ygtl2gLtYpNYh4COb1EDgQNOM1Y=", + "version": "2.83.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", + "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", "requires": { - "aws-sign2": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "aws4": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "caseless": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "combined-stream": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "extend": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "forever-agent": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "form-data": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", - "har-validator": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "hawk": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", - "http-signature": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "is-typedarray": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "isstream": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "json-stringify-safe": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "oauth-sign": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "performance-now": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "qs": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "stringstream": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "tough-cookie": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", - "tunnel-agent": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "uuid": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz" + "aws-sign2": "0.7.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.17", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.1", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", + "tunnel-agent": "0.6.0", + "uuid": "3.2.1" }, "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "boom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "requires": { + "hoek": "4.2.0" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "requires": { + "delayed-stream": "1.0.0" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "requires": { + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "requires": { + "hoek": "4.2.0" + } + } + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.17" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "requires": { + "ajv": "5.5.2", + "har-schema": "2.0.0" + } + }, + "hawk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "requires": { + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.0", + "sntp": "2.1.0" + } + }, + "hoek": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", + "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.13.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "mime-db": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + }, + "mime-types": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "requires": { + "mime-db": "1.30.0" + } + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "sntp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "requires": { + "hoek": "4.2.0" + } + }, + "sshpk": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + } + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + }, + "tough-cookie": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, "uuid": { - "version": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha1-PdPT55Crwk17DToDT/q6vijrvAQ=" + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + } } } }, - "require-all": { - "version": "https://registry.npmjs.org/require-all/-/require-all-1.0.0.tgz", - "integrity": "sha1-hINwjnzkxt+tmItQgPl4KbktIic=" - }, - "require-like": { - "version": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", - "integrity": "sha1-rW8wwTvs15cBDEaK+ndcDAprR/o=", - "dev": true - }, - "rimraf": { - "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha1-LtgVDSShbqhlHm1u8PR8QVjOejY=", - "requires": { - "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz" - } - }, "safe-buffer": { "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" @@ -1867,132 +2386,149 @@ "optional": true }, "sandboxed-module": { - "version": "https://registry.npmjs.org/sandboxed-module/-/sandboxed-module-0.3.0.tgz", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/sandboxed-module/-/sandboxed-module-0.3.0.tgz", "integrity": "sha1-8fvvvYCaT2kHO9B8rm/H2y6vX2o=", "dev": true, "requires": { - "require-like": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", - "stack-trace": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.6.tgz" + "require-like": "0.1.2", + "stack-trace": "0.0.6" }, "dependencies": { + "require-like": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", + "integrity": "sha1-rW8wwTvs15cBDEaK+ndcDAprR/o=", + "dev": true + }, "stack-trace": { - "version": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.6.tgz", + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.6.tgz", "integrity": "sha1-HnGb1qJin/CcGJ4Xqe+QKpT8XbA=", "dev": true } } }, - "semver": { - "version": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha1-4FnAnYVx8FQII3M0M1BdOi8AsY4=" - }, - "send": { - "version": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", - "integrity": "sha1-pw4coh0TgsEdDZ9iMd6ygQgNerM=", - "requires": { - "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "depd": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "destroy": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "encodeurl": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", - "escape-html": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "etag": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "fresh": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "http-errors": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "mime": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "on-finished": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "range-parser": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "statuses": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz" - }, - "dependencies": { - "statuses": { - "version": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" - } - } - }, "sequelize": { - "version": "https://registry.npmjs.org/sequelize/-/sequelize-2.1.3.tgz", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-2.1.3.tgz", "integrity": "sha1-FrpROxNnlY/oIzhYEelRFFHt6tE=", "requires": { - "bluebird": "https://registry.npmjs.org/bluebird/-/bluebird-2.9.34.tgz", - "dottie": "https://registry.npmjs.org/dottie/-/dottie-0.3.1.tgz", - "generic-pool": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.2.0.tgz", - "inflection": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", - "lodash": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "moment": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz", - "node-uuid": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", - "toposort-class": "https://registry.npmjs.org/toposort-class/-/toposort-class-0.3.1.tgz", - "validator": "https://registry.npmjs.org/validator/-/validator-3.43.0.tgz" + "bluebird": "2.9.34", + "dottie": "0.3.1", + "generic-pool": "2.2.0", + "inflection": "1.12.0", + "lodash": "3.10.1", + "moment": "2.20.1", + "node-uuid": "1.4.8", + "toposort-class": "0.3.1", + "validator": "3.43.0" }, "dependencies": { - "node-uuid": { - "version": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", - "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + "bluebird": { + "version": "2.9.34", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.9.34.tgz", + "integrity": "sha1-L3tOyAIWMoqf3evfacjUlC/v99g=" + }, + "dottie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-0.3.1.tgz", + "integrity": "sha1-QYX3d0iZYKwIQTHeP5oUoXm7dJ8=" + }, + "generic-pool": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.2.0.tgz", + "integrity": "sha1-i0ZcGnWI6p3SuxM72gu2a/74pj4=" + }, + "inflection": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", + "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" + }, + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + }, + "moment": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz", + "integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg==" + }, + "toposort-class": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-0.3.1.tgz", + "integrity": "sha1-0TM67XNPhgjO0ShiGL0sRfYO/0g=" + }, + "validator": { + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-3.43.0.tgz", + "integrity": "sha1-lkZLmS1BloM9l6GUv0Cxn/VLrgU=" } } }, - "serve-static": { - "version": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", - "integrity": "sha1-TFfVNASnYdjy58HooYpH2/J4pxk=", - "requires": { - "encodeurl": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", - "escape-html": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "parseurl": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "send": "https://registry.npmjs.org/send/-/send-0.16.1.tgz" - } - }, - "set-blocking": { - "version": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "setprototypeof": { - "version": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" - }, "settings-sharelatex": { "version": "git+https://github.com/sharelatex/settings-sharelatex.git#cbc5e41c1dbe6789721a14b3fdae05bf22546559", - "integrity": "sha1-RatFGqtZ7wuhO78vGSB60S3H9Rc=", "requires": { - "coffee-script": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz" + "coffee-script": "1.6.0" }, "dependencies": { "coffee-script": { - "version": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=" } } }, - "sigmund": { - "version": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" - }, - "signal-exit": { - "version": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, "sinon": { - "version": "https://registry.npmjs.org/sinon/-/sinon-1.7.3.tgz", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.7.3.tgz", "integrity": "sha1-emnWnNApRYbHQyVO7/G1g6UJl/I=", "dev": true, "requires": { - "buster-format": "https://registry.npmjs.org/buster-format/-/buster-format-0.5.6.tgz" + "buster-format": "0.5.6" + }, + "dependencies": { + "buster-core": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/buster-core/-/buster-core-0.6.4.tgz", + "integrity": "sha1-J79rrWdCROpyDzEdkAoMoct4YFA=", + "dev": true + }, + "buster-format": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/buster-format/-/buster-format-0.5.6.tgz", + "integrity": "sha1-K4bDIuz14bCubm55Bev884fSq5U=", + "dev": true, + "requires": { + "buster-core": "0.6.4" + } + } } }, "smoke-test-sharelatex": { "version": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#bc3e93d18ccee219c0d99e8b02c984ccdd842e1c", - "integrity": "sha1-s6idlk0vuV2Kz+u6rn3ITbaMEQ4=", "requires": { "mocha": "1.17.1" }, "dependencies": { + "commander": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz", + "integrity": "sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg=" + }, + "diff": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", + "integrity": "sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ=" + }, "glob": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", "requires": { "graceful-fs": "2.0.3", - "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "inherits": "2.0.3", "minimatch": "0.2.14" } }, @@ -2001,13 +2537,49 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=" }, + "growl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", + "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "requires": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "dependencies": { + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=" + }, + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=" + } + } + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" + }, "minimatch": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", "requires": { - "lru-cache": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "sigmund": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz" + "lru-cache": "2.7.3", + "sigmund": "1.0.1" } }, "mocha": { @@ -2015,36 +2587,850 @@ "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.17.1.tgz", "integrity": "sha1-f3Zx1oUm0HS3uuZgyQmfh+DqHMs=", "requires": { - "commander": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz", + "commander": "2.0.0", "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "diff": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", + "diff": "1.0.7", "glob": "3.2.3", - "growl": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", - "jade": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", - "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz" + "growl": "1.7.0", + "jade": "0.26.3", + "mkdirp": "0.3.5" } + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" } } }, - "sntp": { - "version": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", - "integrity": "sha1-LGzsFP7cIiJznK+bXD2F0cxaLMg=", - "requires": { - "hoek": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz" - } - }, "split-ca": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", "integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY=" }, "sqlite3": { - "version": "https://registry.npmjs.org/sqlite3/-/sqlite3-3.1.13.tgz", - "integrity": "sha1-2ZCgVic5J2jeYni6/Rox/f6Qfdk=", + "version": "3.1.13", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-3.1.13.tgz", + "integrity": "sha512-JxXKPJnkZ6NuHRojq+g2WXWBt3M1G9sjZaYiHEWSTGijDM3cwju/0T2XbWqMXFmPqDgw+iB7zKQvnns4bvzXlw==", "requires": { - "nan": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", + "nan": "2.7.0", "node-pre-gyp": "0.6.38" }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true + }, + "ajv": { + "version": "4.11.8", + "bundled": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.3" + } + }, + "asn1": { + "version": "0.2.3", + "bundled": true + }, + "assert-plus": { + "version": "0.2.0", + "bundled": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true + }, + "aws-sign2": { + "version": "0.6.0", + "bundled": true + }, + "aws4": { + "version": "1.6.0", + "bundled": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "bundled": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "block-stream": { + "version": "0.0.9", + "bundled": true, + "requires": { + "inherits": "2.0.3" + } + }, + "boom": { + "version": "2.10.1", + "bundled": true, + "requires": { + "hoek": "2.16.3" + } + }, + "brace-expansion": { + "version": "1.1.8", + "bundled": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "caseless": { + "version": "0.12.0", + "bundled": true + }, + "co": { + "version": "4.6.0", + "bundled": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "combined-stream": { + "version": "1.0.5", + "bundled": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "cryptiles": { + "version": "2.0.5", + "bundled": true, + "requires": { + "boom": "2.10.1" + } + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true + } + } + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.4.2", + "bundled": true + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "extend": { + "version": "3.0.1", + "bundled": true + }, + "extsprintf": { + "version": "1.3.0", + "bundled": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true + }, + "form-data": { + "version": "2.1.4", + "bundled": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "fstream": { + "version": "1.0.11", + "bundled": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.2" + } + }, + "fstream-ignore": { + "version": "1.0.5", + "bundled": true, + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" + } + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true + } + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true + }, + "har-schema": { + "version": "1.0.5", + "bundled": true + }, + "har-validator": { + "version": "4.2.1", + "bundled": true, + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true + }, + "hawk": { + "version": "3.1.3", + "bundled": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "bundled": true + }, + "http-signature": { + "version": "1.1.1", + "bundled": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.1", + "sshpk": "1.13.1" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.4", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "bundled": true, + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true + }, + "jsonify": { + "version": "0.0.0", + "bundled": true + }, + "jsprim": { + "version": "1.4.1", + "bundled": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true + } + } + }, + "mime-db": { + "version": "1.30.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.17", + "bundled": true, + "requires": { + "mime-db": "1.30.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true + }, + "nan": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", + "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=" + }, + "node-pre-gyp": { + "version": "0.6.38", + "bundled": true, + "requires": { + "hawk": "3.1.3", + "mkdirp": "0.5.1", + "nopt": "4.0.1", + "npmlog": "4.1.2", + "rc": "1.2.1", + "request": "2.81.0", + "rimraf": "2.6.2", + "semver": "5.4.1", + "tar": "2.2.1", + "tar-pack": "3.4.0" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "requires": { + "abbrev": "1.1.1", + "osenv": "0.1.4" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "oauth-sign": { + "version": "0.8.2", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true + }, + "osenv": { + "version": "0.1.4", + "bundled": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "performance-now": { + "version": "0.2.0", + "bundled": true + }, + "process-nextick-args": { + "version": "1.0.7", + "bundled": true + }, + "punycode": { + "version": "1.4.1", + "bundled": true + }, + "qs": { + "version": "6.4.0", + "bundled": true + }, + "rc": { + "version": "1.2.1", + "bundled": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true + } + } + }, + "readable-stream": { + "version": "2.3.3", + "bundled": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "request": { + "version": "2.81.0", + "bundled": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.17", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", + "tunnel-agent": "0.6.0", + "uuid": "3.1.0" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true + }, + "semver": { + "version": "5.4.1", + "bundled": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "sntp": { + "version": "1.0.9", + "bundled": true, + "requires": { + "hoek": "2.16.3" + } + }, + "sshpk": { + "version": "1.13.1", + "bundled": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true + } + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.0.3", + "bundled": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "stringstream": { + "version": "0.0.5", + "bundled": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "tar-pack": { + "version": "3.4.0", + "bundled": true, + "requires": { + "debug": "2.6.9", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.4.0", + "readable-stream": "2.3.3", + "rimraf": "2.6.2", + "tar": "2.2.1", + "uid-number": "0.0.6" + } + }, + "tough-cookie": { + "version": "2.3.3", + "bundled": true, + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "uuid": { + "version": "3.1.0", + "bundled": true + }, + "verror": { + "version": "1.10.0", + "bundled": true, + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true + } + } + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + } + } + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "supports-color": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + }, + "tar-fs": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.12.0.tgz", + "integrity": "sha1-pqgFU9ilTHPeHQrg553ncDVgXh0=", + "requires": { + "mkdirp": "0.5.1", + "pump": "1.0.3", + "tar-stream": "1.5.5" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + } + } + }, + "tar-stream": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.5.tgz", + "integrity": "sha512-mQdgLPc/Vjfr3VWqWbfxW8yQNiJCbAZ+Gf6GDu1Cy0bdb33ofyiNGBtAY96jHFhDuivCwgW1H9DgTON+INiXgg==", + "requires": { + "bl": "1.2.1", + "end-of-stream": "1.4.1", + "readable-stream": "2.3.3", + "xtend": "4.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "requires": { + "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "1.0.0", + "process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "string_decoder": "1.0.3", + "util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + } + } + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "timekeeper": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/timekeeper/-/timekeeper-0.0.4.tgz", + "integrity": "sha1-kNt58X2Ni1NiFUOJSSuXJ2LP0nY=", + "dev": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" + }, + "util-deprecate": { + "version": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "v8-profiler": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/v8-profiler/-/v8-profiler-5.7.0.tgz", + "integrity": "sha1-6DgcvrtbX9DKjSsJ9qAYGhWNs00=", + "requires": { + "nan": "2.8.0", + "node-pre-gyp": "0.6.39" + }, "dependencies": { "abbrev": { "version": "1.1.1", @@ -2076,7 +3462,7 @@ "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "requires": { "delegates": "1.0.0", - "readable-stream": "2.3.3" + "readable-stream": "2.3.4" } }, "asn1": { @@ -2118,14 +3504,6 @@ "tweetnacl": "0.14.5" } }, - "block-stream": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", - "requires": { - "inherits": "2.0.3" - } - }, "boom": { "version": "2.10.1", "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", @@ -2135,9 +3513,9 @@ } }, "brace-expansion": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -2159,9 +3537,9 @@ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "combined-stream": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "requires": { "delayed-stream": "1.0.0" } @@ -2227,6 +3605,11 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, "ecc-jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", @@ -2257,7 +3640,7 @@ "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", "requires": { "asynckit": "0.4.0", - "combined-stream": "1.0.5", + "combined-stream": "1.0.6", "mime-types": "2.1.17" } }, @@ -2395,9 +3778,9 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", - "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=" + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "is-fullwidth-code-point": { "version": "1.0.0", @@ -2487,7 +3870,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { - "brace-expansion": "1.1.8" + "brace-expansion": "1.1.11" } }, "minimist": { @@ -2509,24 +3892,26 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "nan": { - "version": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", - "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", + "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=" }, "node-pre-gyp": { - "version": "0.6.38", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.38.tgz", - "integrity": "sha1-6Sog+DQWQVu0CG9tH7eLPac9ET0=", + "version": "0.6.39", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz", + "integrity": "sha512-OsJV74qxnvz/AMGgcfZoDaeDXKD3oY3QVIbBmwszTFkRisTSXbMQyn4UWzUMOtA5SVhrBZOTp0wcoSBgfMfMmQ==", "requires": { + "detect-libc": "1.0.3", "hawk": "3.1.3", "mkdirp": "0.5.1", "nopt": "4.0.1", "npmlog": "4.1.2", - "rc": "1.2.1", + "rc": "1.2.5", "request": "2.81.0", "rimraf": "2.6.2", - "semver": "5.4.1", + "semver": "5.5.0", "tar": "2.2.1", - "tar-pack": "3.4.0" + "tar-pack": "3.4.1" } }, "nopt": { @@ -2602,9 +3987,9 @@ "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" }, "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "punycode": { "version": "1.4.1", @@ -2617,12 +4002,12 @@ "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" }, "rc": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", - "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.5.tgz", + "integrity": "sha1-J1zWh/bjs2zHVrqibf7oCnkDAf0=", "requires": { "deep-extend": "0.4.2", - "ini": "1.3.4", + "ini": "1.3.5", "minimist": "1.2.0", "strip-json-comments": "2.0.1" }, @@ -2635,14 +4020,14 @@ } }, "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "integrity": "sha512-vuYxeWYM+fde14+rajzqgeohAI7YoJcHE7kXDAc4Nk0EbuKnJfqtY9YtRkLo/tqkuF7MsBQRhPnPeyjYITp3ZQ==", "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", - "process-nextick-args": "1.0.7", + "process-nextick-args": "2.0.0", "safe-buffer": "5.1.1", "string_decoder": "1.0.3", "util-deprecate": "1.0.2" @@ -2656,7 +4041,7 @@ "aws-sign2": "0.6.0", "aws4": "1.6.0", "caseless": "0.12.0", - "combined-stream": "1.0.5", + "combined-stream": "1.0.6", "extend": "3.0.1", "forever-agent": "0.6.1", "form-data": "2.1.4", @@ -2674,7 +4059,7 @@ "stringstream": "0.0.5", "tough-cookie": "2.3.3", "tunnel-agent": "0.6.0", - "uuid": "3.1.0" + "uuid": "3.2.1" } }, "rimraf": { @@ -2691,9 +4076,9 @@ "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, "semver": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" }, "set-blocking": { "version": "2.0.0", @@ -2776,21 +4161,21 @@ "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", "requires": { - "block-stream": "0.0.9", + "block-stream": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", "fstream": "1.0.11", "inherits": "2.0.3" } }, "tar-pack": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.0.tgz", - "integrity": "sha1-I74tf2cagzk3bL2wuP4/3r8xeYQ=", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.1.tgz", + "integrity": "sha512-PPRybI9+jM5tjtCbN2cxmmRU7YmqT3Zv/UDy48tAh2XRkLa9bAORtSWLkVc13+GJF+cdTh1yEnHEk3cpTaL5Kg==", "requires": { "debug": "2.6.9", "fstream": "1.0.11", "fstream-ignore": "1.0.5", "once": "1.4.0", - "readable-stream": "2.3.3", + "readable-stream": "2.3.4", "rimraf": "2.6.2", "tar": "2.2.1", "uid-number": "0.0.6" @@ -2829,9 +4214,9 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" }, "verror": { "version": "1.10.0", @@ -2865,292 +4250,13 @@ } } }, - "sshpk": { - "version": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", - "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", - "requires": { - "asn1": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "bcrypt-pbkdf": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "dashdash": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "ecc-jsbn": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "getpass": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "jsbn": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "tweetnacl": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" - } - }, - "stack-trace": { - "version": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", - "integrity": "sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU=" - }, - "statsd-parser": { - "version": "https://registry.npmjs.org/statsd-parser/-/statsd-parser-0.0.4.tgz", - "integrity": "sha1-y9JDlTzELv/VSLXSI4jtaJ7GOb0=" - }, - "statuses": { - "version": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha1-u3PURtonlhBu/MG2AaJT1sRr0Ic=" - }, - "string-width": { - "version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "is-fullwidth-code-point": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" - } - }, - "string_decoder": { - "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, - "stringstream": { - "version": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" - }, - "strip-ansi": { - "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" - } - }, - "strip-json-comments": { - "version": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, - "tar": { - "version": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", - "requires": { - "block-stream": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "fstream": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", - "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" - } - }, - "tar-fs": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.12.0.tgz", - "integrity": "sha1-pqgFU9ilTHPeHQrg553ncDVgXh0=", - "requires": { - "mkdirp": "0.5.1", - "pump": "1.0.3", - "tar-stream": "1.5.5" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" - } - } - } - }, - "tar-pack": { - "version": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.1.tgz", - "integrity": "sha1-4dvAOpudO6B+iWrQJzF+tnmhCh8=", - "requires": { - "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "fstream": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", - "fstream-ignore": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", - "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "rimraf": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "tar": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "uid-number": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz" - }, - "dependencies": { - "isarray": { - "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "readable-stream": { - "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", - "requires": { - "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "isarray": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" - } - }, - "string_decoder": { - "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", - "requires": { - "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" - } - } - } - }, - "tar-stream": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.5.tgz", - "integrity": "sha512-mQdgLPc/Vjfr3VWqWbfxW8yQNiJCbAZ+Gf6GDu1Cy0bdb33ofyiNGBtAY96jHFhDuivCwgW1H9DgTON+INiXgg==", - "requires": { - "bl": "1.2.1", - "end-of-stream": "1.4.1", - "readable-stream": "2.3.3", - "xtend": "4.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "requires": { - "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "isarray": "1.0.0", - "process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "string_decoder": "1.0.3", - "util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" - } - } - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "timekeeper": { - "version": "https://registry.npmjs.org/timekeeper/-/timekeeper-0.0.4.tgz", - "integrity": "sha1-kNt58X2Ni1NiFUOJSSuXJ2LP0nY=", - "dev": true - }, - "toposort-class": { - "version": "https://registry.npmjs.org/toposort-class/-/toposort-class-0.3.1.tgz", - "integrity": "sha1-0TM67XNPhgjO0ShiGL0sRfYO/0g=" - }, - "tough-cookie": { - "version": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", - "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", - "requires": { - "punycode": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" - } - }, - "tunnel-agent": { - "version": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" - } - }, - "tweetnacl": { - "version": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true - }, - "type-detect": { - "version": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", - "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", - "dev": true - }, - "type-is": { - "version": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", - "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", - "requires": { - "media-typer": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "mime-types": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, - "uid-number": { - "version": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", - "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" - }, - "underscore": { - "version": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" - }, - "underscore.string": { - "version": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", - "integrity": "sha1-18D6KvXVoaZ/QlPa7pgTLnM/Dxk=", - "dev": true - }, - "unpipe": { - "version": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - }, - "util-deprecate": { - "version": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "utils-merge": { - "version": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" - }, - "uuid": { - "version": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz", - "integrity": "sha1-Zyj8BFnEUNeWqZwxg3VpvfZy1yg=" - }, - "v8-profiler": { - "version": "https://registry.npmjs.org/v8-profiler/-/v8-profiler-5.7.0.tgz", - "integrity": "sha1-6DgcvrtbX9DKjSsJ9qAYGhWNs00=", - "requires": { - "nan": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", - "node-pre-gyp": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz" - } - }, - "validator": { - "version": "https://registry.npmjs.org/validator/-/validator-3.43.0.tgz", - "integrity": "sha1-lkZLmS1BloM9l6GUv0Cxn/VLrgU=" - }, - "vary": { - "version": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - }, - "verror": { - "version": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "extsprintf": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" - } - }, - "which": { - "version": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", - "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=", - "dev": true - }, - "wide-align": { - "version": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", - "integrity": "sha1-Vx4PGwYEY268DfwhsDObvjE0FxA=", - "requires": { - "string-width": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz" - } - }, "wrappy": { "version": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "wrench": { - "version": "https://registry.npmjs.org/wrench/-/wrench-1.5.9.tgz", + "version": "1.5.9", + "resolved": "https://registry.npmjs.org/wrench/-/wrench-1.5.9.tgz", "integrity": "sha1-QRaRxjqbJTGxcAJnJ5veyiOyFCo=" }, "xtend": { diff --git a/services/clsi/package.json b/services/clsi/package.json index efc7aa0df7..e44f2e48ef 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -1,67 +1,67 @@ { - "name": "node-clsi", - "description": "A Node.js implementation of the CLSI LaTeX web-API", - "version": "0.1.4", + "name": "node-clsi", + "description": "A Node.js implementation of the CLSI LaTeX web-API", + "version": "0.1.4", "repository": { - "type": "git", + "type": "git", "url": "https://github.com/sharelatex/clsi-sharelatex.git" - }, + }, "scripts": { - "test:acceptance:wait_for_app": "echo 'Waiting for app to be accessible' && while (! curl -s -o /dev/null localhost:3013/status) do sleep 1; done", - "test:acceptance:run": "mocha --recursive --reporter spec --timeout 15000", - "test:acceptance:dir": "npm -q run test:acceptance:wait_for_app && npm -q run test:acceptance:run -- $@", - "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- $@", - "compile:test:acceptance": "coffee -o test/acceptance/js -c test/acceptance/coffee", - "compile:test:smoke": "coffee -o test/smoke/js -c test/smoke/coffee", - "compile:app": "coffee -o app/js -c app/coffee && coffee -c app.coffee", - "compile": "npm run compile:app && npm run compile:test:acceptance && npm run compile:test:smoke", - "start": "npm run compile:app && node app.js", - "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 $@ test/acceptance/js", - "test:unit:_run": "mocha --recursive --reporter spec $@ test/unit/js", - "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- $@", - "compile:unit_tests": "[ -e test/unit ] && coffee -o test/unit/js -c test/unit/coffee || echo 'No unit tests to compile'", - "compile:acceptance_tests": "[ -e test/acceptance ] && coffee -o test/acceptance/js -c test/acceptance/coffee || echo 'No acceptance tests to compile'", - "compile:all": "npm run compile:app && npm run compile:unit_tests && npm run compile:acceptance_tests && compile:test:smoke", + "test:acceptance:wait_for_app": "echo 'Waiting for app to be accessible' && while (! curl -s -o /dev/null localhost:3013/status) do sleep 1; done", + "test:acceptance:run": "mocha --recursive --reporter spec --timeout 15000", + "test:acceptance:dir": "npm -q run test:acceptance:wait_for_app && npm -q run test:acceptance:run -- $@", + "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- $@", + "compile:test:acceptance": "coffee -o test/acceptance/js -c test/acceptance/coffee", + "compile:test:smoke": "coffee -o test/smoke/js -c test/smoke/coffee", + "compile:app": "coffee -o app/js -c app/coffee && coffee -c app.coffee", + "compile": "npm run compile:app && npm run compile:test:acceptance && npm run compile:test:smoke", + "start": "npm run compile:app && node app.js", + "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", + "test:unit:_run": "mocha --recursive --reporter spec $@ test/unit/js", + "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- $@", + "compile:unit_tests": "[ -e test/unit ] && coffee -o test/unit/js -c test/unit/coffee || echo 'No unit tests to compile'", + "compile:acceptance_tests": "[ -e test/acceptance ] && coffee -o test/acceptance/js -c test/acceptance/coffee || echo 'No acceptance tests to compile'", + "compile:all": "npm run compile:app && npm run compile:unit_tests && npm run compile:acceptance_tests", "nodemon": "nodemon --config nodemon.json" - }, - "author": "James Allen <james@sharelatex.com>", + }, + "author": "James Allen <james@sharelatex.com>", "dependencies": { - "async": "0.2.9", - "body-parser": "^1.2.0", - "dockerode": "^2.5.3", - "express": "^4.2.0", - "fs-extra": "^0.16.3", - "grunt-mkdir": "^1.0.0", - "heapdump": "^0.3.5", - "lockfile": "^1.0.3", - "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", - "lynx": "0.0.11", - "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.5.0", - "mkdirp": "0.3.5", - "mysql": "2.6.2", - "request": "^2.21.0", - "sequelize": "^2.1.3", - "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", - "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", - "sqlite3": "~3.1.8", - "underscore": "^1.8.2", - "v8-profiler": "^5.2.4", + "async": "0.2.9", + "body-parser": "^1.2.0", + "dockerode": "^2.5.3", + "express": "^4.2.0", + "fs-extra": "^0.16.3", + "grunt-mkdir": "^1.0.0", + "heapdump": "^0.3.5", + "lockfile": "^1.0.3", + "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", + "lynx": "0.0.11", + "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.5.0", + "mkdirp": "0.3.5", + "mysql": "2.6.2", + "request": "^2.21.0", + "sequelize": "^2.1.3", + "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", + "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", + "sqlite3": "~3.1.8", + "underscore": "^1.8.2", + "v8-profiler": "^5.2.4", "wrench": "~1.5.4" - }, + }, "devDependencies": { - "mocha": "1.10.0", - "coffee-script": "1.6.0", - "chai": "~1.8.1", - "sinon": "~1.7.3", - "grunt": "~0.4.2", - "grunt-contrib-coffee": "~0.7.0", - "grunt-contrib-clean": "~0.5.0", - "grunt-shell": "~0.6.1", - "grunt-mocha-test": "~0.8.1", - "sandboxed-module": "~0.3.0", - "timekeeper": "0.0.4", - "grunt-execute": "^0.1.5", - "bunyan": "^0.22.1", + "mocha": "^4.0.1", + "coffee-script": "1.6.0", + "chai": "~1.8.1", + "sinon": "~1.7.3", + "grunt": "~0.4.2", + "grunt-contrib-coffee": "~0.7.0", + "grunt-contrib-clean": "~0.5.0", + "grunt-shell": "~0.6.1", + "grunt-mocha-test": "~0.8.1", + "sandboxed-module": "~0.3.0", + "timekeeper": "0.0.4", + "grunt-execute": "^0.1.5", + "bunyan": "^0.22.1", "grunt-bunyan": "^0.5.0" } } diff --git a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee index 4d431c29c2..e9a58b56cd 100644 --- a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee +++ b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee @@ -11,7 +11,8 @@ try catch e convertToPng = (pdfPath, pngPath, callback = (error) ->) -> - convert = ChildProcess.exec "convert #{fixturePath(pdfPath)} #{fixturePath(pngPath)}" + command = "convert #{fixturePath(pdfPath)} #{fixturePath(pngPath)}" + convert = ChildProcess.exec command stdout = "" convert.stdout.on "data", (chunk) -> console.log "STDOUT", chunk.toString() convert.stderr.on "data", (chunk) -> console.log "STDERR", chunk.toString() diff --git a/services/clsi/test/acceptance/coffee/helpers/Client.coffee b/services/clsi/test/acceptance/coffee/helpers/Client.coffee index 76634966af..546c2351e5 100644 --- a/services/clsi/test/acceptance/coffee/helpers/Client.coffee +++ b/services/clsi/test/acceptance/coffee/helpers/Client.coffee @@ -31,6 +31,7 @@ module.exports = Client = express = require("express") app = express() app.use express.static(directory) + console.log("starting test server on", port, host) app.listen(port, host).on "error", (error) -> console.error "error starting server:", error.message process.exit(1) From fdac655cd4731457e641cedf14b7594d002a078b Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 16 Feb 2018 11:36:32 +0000 Subject: [PATCH 328/754] acceptence tests pass inside docker container (apart from sync) --- services/clsi/Dockerfile | 15 +- services/clsi/app.coffee | 12 +- services/clsi/config/settings.defaults.coffee | 1 - services/clsi/docker-compose.yml | 35 +- services/clsi/install_deps.sh | 4 + services/clsi/package-lock.json | 531 +++++++++--------- services/clsi/package.json | 2 +- .../coffee/BrokenLatexFileTests.coffee | 5 +- .../coffee/DeleteOldFilesTest.coffee | 6 +- .../coffee/ExampleDocumentTests.coffee | 5 +- .../coffee/SimpleLatexFileTests.coffee | 4 +- .../acceptance/coffee/SynctexTests.coffee | 4 +- .../acceptance/coffee/TimeoutTests.coffee | 5 +- .../acceptance/coffee/UrlCachingTests.coffee | 4 +- .../acceptance/coffee/WordcountTests.coffee | 4 +- .../acceptance/coffee/helpers/ClsiApp.coffee | 24 + 16 files changed, 351 insertions(+), 310 deletions(-) create mode 100755 services/clsi/install_deps.sh create mode 100644 services/clsi/test/acceptance/coffee/helpers/ClsiApp.coffee diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 83e452d392..05be84642a 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -1,23 +1,16 @@ -FROM node:6.9.5 - -RUN wget -qO- https://get.docker.com/ | sh - -run apt-get install poppler-utils vim ghostscript --yes - -# run git build-essential --yes -# RUN git clone https://github.com/netblue30/firejail.git -# RUN cd firejail && ./configure && make && make install-strip -# run mkdir /data +FROM node:6.13.0 COPY ./ /app WORKDIR /app + RUN npm install +RUN [ -e ./install_deps.sh ] && ./install_deps.sh + RUN npm run compile -ENV SHARELATEX_CONFIG /app/config/settings.production.coffee ENV NODE_ENV production CMD ["node","/app/app.js"] diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index ba7d22557b..527b17e9d6 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -165,8 +165,16 @@ app.use (error, req, res, next) -> logger.error {err: error, url: req.url}, "server error" res.sendStatus(error?.statusCode || 500) -app.listen port = (Settings.internal?.clsi?.port or 3013), host = (Settings.internal?.clsi?.host or "localhost"), (error) -> - logger.info "CLSI starting up, listening on #{host}:#{port}" +port = (Settings.internal?.clsi?.port or 3013) +host = (Settings.internal?.clsi?.host or "localhost") + +if !module.parent # Called directly + app.listen port, host, (error) -> + logger.info "CLSI starting up, listening on #{host}:#{port}" + +module.exports = app + + setInterval () -> ProjectPersistenceManager.clearExpiredProjects() diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index 4835b4feb9..9b1e4be967 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -20,7 +20,6 @@ module.exports = clsi: port: 3013 host: process.env["LISTEN_ADDRESS"] or "0.0.0.0" - apis: clsi: url: "http://#{process.env['CLSI_HOST'] or 'localhost'}:3013" diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index d09ea6e0a6..a272e753b1 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -14,16 +14,17 @@ services: entrypoint: npm run test:unit test_acceptance: - image: node:6.9.5 - volumes: - - .:/app + build: . environment: - REDIS_HOST: redis - MONGO_HOST: mongo - depends_on: - - redis - - mongo - working_dir: /app + TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2017.1 + TEXLIVE_IMAGE_USER: root # Not ideal, but makes running in dev very simple + SHARELATEX_CONFIG: /app/config/settings.defaults.coffee + COMMAND_RUNNER: docker-runner-sharelatex + COMPILES_HOST_DIR: $PWD/compiles + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./docker-runner:/app/node_modules/docker-runner-sharelatex + - ./compiles:/app/compiles entrypoint: npm run test:acceptance redis: @@ -31,19 +32,3 @@ services: mongo: image: mongo:3.4 - - app: - image: gcr.io/henry-terraform-admin/clsi - build: . - environment: - TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2017.1 - TEXLIVE_IMAGE_USER: root # Not ideal, but makes running in dev very simple - COMMAND_RUNNER: docker-runner-sharelatex - SHARELATEX_CONFIG: /app/config/settings.defaults.coffee - COMPILES_HOST_DIR: $PWD/compiles - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - ./docker-runner:/app/node_modules/docker-runner-sharelatex - - /Users/henry/Projects/sharelatex-dev-environment/clsi/compiles:/app/compiles - ports: - - 3013:3013 \ No newline at end of file diff --git a/services/clsi/install_deps.sh b/services/clsi/install_deps.sh new file mode 100755 index 0000000000..49bdc5c963 --- /dev/null +++ b/services/clsi/install_deps.sh @@ -0,0 +1,4 @@ +/bin/sh +wget -qO- https://get.docker.com/ | sh +apt-get install poppler-utils vim ghostscript --yes +npm rebuild diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index 5f30ca5464..a5b7847cdb 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -18,6 +18,12 @@ "resolved": "https://registry.npmjs.org/async/-/async-0.2.9.tgz", "integrity": "sha1-32MGD789Myhqdqr21Vophtn/hhk=" }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "optional": true + }, "bl": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz", @@ -208,6 +214,16 @@ } } }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", + "optional": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, "browser-stdout": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", @@ -396,8 +412,18 @@ "coffee-script": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", - "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=", - "dev": true + "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=" + }, + "commander": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz", + "integrity": "sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "optional": true }, "concat-stream": { "version": "1.5.2", @@ -429,6 +455,11 @@ } } }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, "core-util-is": { "version": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" @@ -440,10 +471,15 @@ "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" } }, + "diff": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", + "integrity": "sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ=" + }, "docker-modem": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-1.0.4.tgz", - "integrity": "sha512-pkXB9p7KWagegOXm2NsbVDBluQQLCBJzX9uYJzVbL6CHwe4d2sSbcACJ4K8ISX1l1JUUmFSiwNkBKc1uTiU4MA==", + "integrity": "sha1-JEJUsiax1/YCJfgjlb2FGQz3Ves=", "requires": { "JSONStream": "0.10.0", "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -467,17 +503,26 @@ "dockerode": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-2.5.3.tgz", - "integrity": "sha512-LQKXR5jyI+G/+5OhZCi40m0ArY4j46g7Tl71Vtn10Ekt5TiyDzZAoqXOCS6edQpEuGbdFgSDJxleFqLxACpKJg==", + "integrity": "sha1-Wsw8Bx96JuRDpbWM83U+8vPCvAQ=", "requires": { "concat-stream": "1.5.2", "docker-modem": "1.0.4", "tar-fs": "1.12.0" } }, + "dtrace-provider": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.6.0.tgz", + "integrity": "sha1-CweNVReTfYcxAUUtkUZzdVe3XlE=", + "optional": true, + "requires": { + "nan": "2.8.0" + } + }, "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=", "requires": { "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz" } @@ -773,7 +818,7 @@ "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=" }, "statuses": { "version": "1.3.1", @@ -932,6 +977,52 @@ } } }, + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "optional": true, + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "optional": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "optional": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "optional": true + } + } + }, + "graceful-fs": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", + "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=" + }, + "growl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", + "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=" + }, "grunt": { "version": "0.4.5", "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", @@ -1478,6 +1569,32 @@ "resolved": "https://registry.npmjs.org/heapdump/-/heapdump-0.3.9.tgz", "integrity": "sha1-A8dOsN9dZ74Jgug0KbqcnSs7f3g=" }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "optional": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + }, + "dependencies": { + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "optional": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + } + }, "inherits": { "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" @@ -1486,6 +1603,32 @@ "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, + "jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "requires": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "dependencies": { + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=" + }, + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=" + } + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, "jsonparse": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz", @@ -1504,22 +1647,6 @@ "raven": "1.2.1" }, "dependencies": { - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "optional": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, "bunyan": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.5.1.tgz", @@ -1534,165 +1661,19 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.4.0.tgz", "integrity": "sha1-XjvIqsJsAajie/EHcixWVfWtfTY=" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "optional": true - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" - }, - "dtrace-provider": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.6.0.tgz", - "integrity": "sha1-CweNVReTfYcxAUUtkUZzdVe3XlE=", - "optional": true, - "requires": { - "nan": "2.8.0" - } - }, - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "optional": true, - "requires": { - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "optional": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "optional": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "lsmod": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz", - "integrity": "sha1-mgD3bco26yP6BTUK/htYXUKZ5ks=" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "optional": true, - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "optional": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "mv": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", - "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", - "optional": true, - "requires": { - "mkdirp": "0.5.1", - "ncp": "2.0.0", - "rimraf": "2.4.5" - } - }, - "nan": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", - "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=", - "optional": true - }, - "ncp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", - "optional": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "optional": true - }, - "raven": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/raven/-/raven-1.2.1.tgz", - "integrity": "sha1-lJwTTbAooZC3u/j3kKrlQbfAIL0=", - "requires": { - "cookie": "0.3.1", - "json-stringify-safe": "5.0.1", - "lsmod": "1.0.0", - "stack-trace": "0.0.9", - "uuid": "3.0.0" - } - }, - "rimraf": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", - "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", - "optional": true, - "requires": { - "glob": "6.0.4" - } - }, - "stack-trace": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", - "integrity": "sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU=" - }, - "uuid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz", - "integrity": "sha1-Zyj8BFnEUNeWqZwxg3VpvfZy1yg=" - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" } } }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" + }, + "lsmod": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz", + "integrity": "sha1-mgD3bco26yP6BTUK/htYXUKZ5ks=" + }, "lynx": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.0.11.tgz", @@ -1714,6 +1695,11 @@ } } }, + "mersenne": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz", + "integrity": "sha1-QB/ex+whzbngPNPTAhOY2iGycIU=" + }, "metrics-sharelatex": { "version": "git+https://github.com/sharelatex/metrics-sharelatex.git#05bd57604115ae5efdca2a419fbef4f25e9a780f", "requires": { @@ -1722,11 +1708,6 @@ "underscore": "1.6.0" }, "dependencies": { - "coffee-script": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", - "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=" - }, "lynx": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.1.1.tgz", @@ -1736,16 +1717,6 @@ "statsd-parser": "0.0.4" } }, - "mersenne": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz", - "integrity": "sha1-QB/ex+whzbngPNPTAhOY2iGycIU=" - }, - "statsd-parser": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/statsd-parser/-/statsd-parser-0.0.4.tgz", - "integrity": "sha1-y9JDlTzELv/VSLXSI4jtaJ7GOb0=" - }, "underscore": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", @@ -1753,6 +1724,15 @@ } } }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "optional": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, "minimist": { "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" @@ -1765,7 +1745,7 @@ "mocha": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz", - "integrity": "sha512-0RVnjg1HJsXY2YFDoTNzcc1NKhYuXKRrBAG2gDygmJJA136Cs2QlRliZG1mA0ap7cuaT30mw16luAeln+4RiNA==", + "integrity": "sha1-fYbPvPNcuCnidUwy4XNV7AUzh5Q=", "dev": true, "requires": { "browser-stdout": "1.3.0", @@ -1799,7 +1779,7 @@ "commander": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "integrity": "sha1-FXFS/R56bI2YpbcVzzdt+SgARWM=", "dev": true }, "concat-map": { @@ -1811,7 +1791,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -1820,7 +1800,7 @@ "diff": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", - "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", + "integrity": "sha1-qoVnpu7QPFMfyJ0/cRzQ5SWd7HU=", "dev": true }, "fs.realpath": { @@ -1846,7 +1826,7 @@ "growl": { "version": "1.10.3", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", - "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "integrity": "sha1-GSa6kM8+3+KttJJ/WIC8IsZseQ8=", "dev": true }, "inflight": { @@ -1922,6 +1902,34 @@ "version": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "mv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", + "optional": true, + "requires": { + "mkdirp": "0.5.1", + "ncp": "2.0.0", + "rimraf": "2.4.5" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "optional": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "optional": true, + "requires": { + "minimist": "0.0.8" + } + } + } + }, "mysql": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.6.2.tgz", @@ -1975,6 +1983,18 @@ } } }, + "nan": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", + "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=", + "optional": true + }, + "ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "optional": true + }, "node-uuid": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", @@ -1987,6 +2007,12 @@ "wrappy": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" } }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "optional": true + }, "process-nextick-args": { "version": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" @@ -1994,16 +2020,28 @@ "pump": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", - "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", + "integrity": "sha1-Xf6DEcM7v2/BgmH580cCxHwIqVQ=", "requires": { "end-of-stream": "1.4.1", "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz" } }, + "raven": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/raven/-/raven-1.2.1.tgz", + "integrity": "sha1-lJwTTbAooZC3u/j3kKrlQbfAIL0=", + "requires": { + "cookie": "0.3.1", + "json-stringify-safe": "5.0.1", + "lsmod": "1.0.0", + "stack-trace": "0.0.9", + "uuid": "3.0.0" + } + }, "request": { "version": "2.83.0", "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", - "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", + "integrity": "sha1-ygtl2gLtYpNYh4COb1EDgQNOM1Y=", "requires": { "aws-sign2": "0.7.0", "aws4": "1.6.0", @@ -2361,7 +2399,7 @@ "uuid": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + "integrity": "sha1-EsUou51Y0LkmXZovbw/ovhf/HxQ=" }, "verror": { "version": "1.10.0", @@ -2375,6 +2413,15 @@ } } }, + "rimraf": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", + "optional": true, + "requires": { + "glob": "6.0.4" + } + }, "safe-buffer": { "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" @@ -2471,15 +2518,13 @@ "version": "git+https://github.com/sharelatex/settings-sharelatex.git#cbc5e41c1dbe6789721a14b3fdae05bf22546559", "requires": { "coffee-script": "1.6.0" - }, - "dependencies": { - "coffee-script": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", - "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=" - } } }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" + }, "sinon": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.7.3.tgz", @@ -2512,16 +2557,6 @@ "mocha": "1.17.1" }, "dependencies": { - "commander": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz", - "integrity": "sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg=" - }, - "diff": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", - "integrity": "sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ=" - }, "glob": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", @@ -2532,47 +2567,11 @@ "minimatch": "0.2.14" } }, - "graceful-fs": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", - "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=" - }, - "growl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", - "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=" - }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, - "jade": { - "version": "0.26.3", - "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", - "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", - "requires": { - "commander": "0.6.1", - "mkdirp": "0.3.0" - }, - "dependencies": { - "commander": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", - "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=" - }, - "mkdirp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", - "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=" - } - } - }, - "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" - }, "minimatch": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", @@ -2595,11 +2594,6 @@ "jade": "0.26.3", "mkdirp": "0.3.5" } - }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" } } }, @@ -3325,6 +3319,16 @@ } } }, + "stack-trace": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", + "integrity": "sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU=" + }, + "statsd-parser": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/statsd-parser/-/statsd-parser-0.0.4.tgz", + "integrity": "sha1-y9JDlTzELv/VSLXSI4jtaJ7GOb0=" + }, "string_decoder": { "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" @@ -3332,7 +3336,7 @@ "supports-color": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "integrity": "sha1-iD992rwWUUKyphQn8zUt7RldGj4=", "dev": true, "requires": { "has-flag": "2.0.0" @@ -3361,7 +3365,7 @@ "tar-stream": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.5.tgz", - "integrity": "sha512-mQdgLPc/Vjfr3VWqWbfxW8yQNiJCbAZ+Gf6GDu1Cy0bdb33ofyiNGBtAY96jHFhDuivCwgW1H9DgTON+INiXgg==", + "integrity": "sha1-XK2Ed59FyDsfJQjZawnYjHIYr1U=", "requires": { "bl": "1.2.1", "end-of-stream": "1.4.1", @@ -3423,6 +3427,11 @@ "version": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "uuid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz", + "integrity": "sha1-Zyj8BFnEUNeWqZwxg3VpvfZy1yg=" + }, "v8-profiler": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/v8-profiler/-/v8-profiler-5.7.0.tgz", diff --git a/services/clsi/package.json b/services/clsi/package.json index e44f2e48ef..77e9423c0d 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -43,7 +43,7 @@ "sequelize": "^2.1.3", "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", - "sqlite3": "~3.1.8", + "sqlite3": "^3.1.13", "underscore": "^1.8.2", "v8-profiler": "^5.2.4", "wrench": "~1.5.4" diff --git a/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.coffee b/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.coffee index 5a92d5fcfb..a54df5568f 100644 --- a/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.coffee +++ b/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.coffee @@ -1,9 +1,10 @@ Client = require "./helpers/Client" request = require "request" require("chai").should() +ClsiApp = require "./helpers/ClsiApp" describe "Broken LaTeX file", -> - before -> + before (done)-> @broken_request = resources: [ path: "main.tex" @@ -24,6 +25,7 @@ describe "Broken LaTeX file", -> \\end{document} ''' ] + ClsiApp.ensureRunning done describe "on first run", -> before (done) -> @@ -31,6 +33,7 @@ describe "Broken LaTeX file", -> Client.compile @project_id, @broken_request, (@error, @res, @body) => done() it "should return a failure status", -> + console.log(@error, @res, @body) @body.compile.status.should.equal "failure" describe "on second run", -> diff --git a/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.coffee b/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.coffee index b8a1ff37f4..1cb67765ca 100644 --- a/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.coffee +++ b/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.coffee @@ -1,9 +1,10 @@ Client = require "./helpers/Client" request = require "request" require("chai").should() +ClsiApp = require "./helpers/ClsiApp" describe "Deleting Old Files", -> - before -> + before (done)-> @request = resources: [ path: "main.tex" @@ -14,7 +15,8 @@ describe "Deleting Old Files", -> \\end{document} ''' ] - + ClsiApp.ensureRunning done + describe "on first run", -> before (done) -> @project_id = Client.randomId() diff --git a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee index e9a58b56cd..5ec61111b3 100644 --- a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee +++ b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee @@ -3,6 +3,7 @@ request = require "request" require("chai").should() fs = require "fs" ChildProcess = require "child_process" +ClsiApp = require "./helpers/ClsiApp" fixturePath = (path) -> __dirname + "/../fixtures/" + path @@ -86,7 +87,9 @@ Client.runServer(4242, fixturePath("examples")) describe "Example Documents", -> before (done) -> - ChildProcess.exec("rm test/acceptance/fixtures/tmp/*").on "exit", () -> done() + ChildProcess.exec("rm test/acceptance/fixtures/tmp/*").on "exit", () -> + ClsiApp.ensureRunning done + for example_dir in fs.readdirSync fixturePath("examples") do (example_dir) -> diff --git a/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee b/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee index 2693f63eab..95b667ba20 100644 --- a/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee +++ b/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee @@ -1,6 +1,7 @@ Client = require "./helpers/Client" request = require "request" require("chai").should() +ClsiApp = require "./helpers/ClsiApp" describe "Simple LaTeX file", -> before (done) -> @@ -15,7 +16,8 @@ describe "Simple LaTeX file", -> \\end{document} ''' ] - Client.compile @project_id, @request, (@error, @res, @body) => done() + ClsiApp.ensureRunning => + Client.compile @project_id, @request, (@error, @res, @body) => done() it "should return the PDF", -> pdf = Client.getOutputFile(@body, "pdf") diff --git a/services/clsi/test/acceptance/coffee/SynctexTests.coffee b/services/clsi/test/acceptance/coffee/SynctexTests.coffee index 02b2397897..c6adcf2bf6 100644 --- a/services/clsi/test/acceptance/coffee/SynctexTests.coffee +++ b/services/clsi/test/acceptance/coffee/SynctexTests.coffee @@ -2,6 +2,7 @@ Client = require "./helpers/Client" request = require "request" require("chai").should() expect = require("chai").expect +ClsiApp = require "./helpers/ClsiApp" describe "Syncing", -> before (done) -> @@ -16,7 +17,8 @@ describe "Syncing", -> ''' ] @project_id = Client.randomId() - Client.compile @project_id, @request, (@error, @res, @body) => done() + ClsiApp.ensureRunning => + Client.compile @project_id, @request, (@error, @res, @body) => done() describe "from code to pdf", -> it "should return the correct location", (done) -> diff --git a/services/clsi/test/acceptance/coffee/TimeoutTests.coffee b/services/clsi/test/acceptance/coffee/TimeoutTests.coffee index 5e0058d3c5..386148714c 100644 --- a/services/clsi/test/acceptance/coffee/TimeoutTests.coffee +++ b/services/clsi/test/acceptance/coffee/TimeoutTests.coffee @@ -1,6 +1,8 @@ Client = require "./helpers/Client" request = require "request" require("chai").should() +ClsiApp = require "./helpers/ClsiApp" + describe "Timed out compile", -> before (done) -> @@ -18,7 +20,8 @@ describe "Timed out compile", -> ''' ] @project_id = Client.randomId() - Client.compile @project_id, @request, (@error, @res, @body) => done() + ClsiApp.ensureRunning => + Client.compile @project_id, @request, (@error, @res, @body) => done() it "should return a timeout error", -> @body.compile.error.should.equal "container timed out" diff --git a/services/clsi/test/acceptance/coffee/UrlCachingTests.coffee b/services/clsi/test/acceptance/coffee/UrlCachingTests.coffee index 9e6f3d627d..cef744672e 100644 --- a/services/clsi/test/acceptance/coffee/UrlCachingTests.coffee +++ b/services/clsi/test/acceptance/coffee/UrlCachingTests.coffee @@ -2,6 +2,7 @@ Client = require "./helpers/Client" request = require "request" require("chai").should() sinon = require "sinon" +ClsiApp = require "./helpers/ClsiApp" host = "localhost" @@ -46,7 +47,8 @@ describe "Url Caching", -> }] sinon.spy Server, "getFile" - Client.compile @project_id, @request, (@error, @res, @body) => done() + ClsiApp.ensureRunning => + Client.compile @project_id, @request, (@error, @res, @body) => done() afterEach -> Server.getFile.restore() diff --git a/services/clsi/test/acceptance/coffee/WordcountTests.coffee b/services/clsi/test/acceptance/coffee/WordcountTests.coffee index d84ecba323..abace066c9 100644 --- a/services/clsi/test/acceptance/coffee/WordcountTests.coffee +++ b/services/clsi/test/acceptance/coffee/WordcountTests.coffee @@ -4,6 +4,7 @@ require("chai").should() expect = require("chai").expect path = require("path") fs = require("fs") +ClsiApp = require "./helpers/ClsiApp" describe "Syncing", -> before (done) -> @@ -13,7 +14,8 @@ describe "Syncing", -> content: fs.readFileSync(path.join(__dirname,"../fixtures/naugty_strings.txt"),"utf-8") ] @project_id = Client.randomId() - Client.compile @project_id, @request, (@error, @res, @body) => done() + ClsiApp.ensureRunning => + Client.compile @project_id, @request, (@error, @res, @body) => done() describe "wordcount file", -> it "should return wordcount info", (done) -> diff --git a/services/clsi/test/acceptance/coffee/helpers/ClsiApp.coffee b/services/clsi/test/acceptance/coffee/helpers/ClsiApp.coffee new file mode 100644 index 0000000000..35be427b81 --- /dev/null +++ b/services/clsi/test/acceptance/coffee/helpers/ClsiApp.coffee @@ -0,0 +1,24 @@ +app = require('../../../../app') +require("logger-sharelatex").logger.level("error") +logger = require("logger-sharelatex") +Settings = require("settings-sharelatex") + +module.exports = + running: false + initing: false + callbacks: [] + ensureRunning: (callback = (error) ->) -> + if @running + return callback() + else if @initing + @callbacks.push callback + else + @initing = true + @callbacks.push callback + app.listen Settings.internal?.clsi?.port, "localhost", (error) => + throw error if error? + @running = true + logger.log("clsi running in dev mode") + + for callback in @callbacks + callback() \ No newline at end of file From ece054031883c27752e8aeec51bea6d0990f3e20 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 16 Feb 2018 12:52:12 +0000 Subject: [PATCH 329/754] updated build sripts with 1.0.3 --- services/clsi/.nvmrc | 2 +- services/clsi/Dockerfile | 2 +- services/clsi/Makefile | 9 ++- services/clsi/base.yml | 14 ++++ services/clsi/docker-compose.ci.yml | 2 +- services/clsi/docker-compose.yml | 21 +++--- services/clsi/kube.yaml | 41 ++++++++++ services/clsi/nodemon.json | 1 + services/clsi/package.json | 112 ++++++++++++++-------------- 9 files changed, 131 insertions(+), 73 deletions(-) create mode 100644 services/clsi/base.yml create mode 100644 services/clsi/kube.yaml diff --git a/services/clsi/.nvmrc b/services/clsi/.nvmrc index e1e5d1369a..5917993c09 100644 --- a/services/clsi/.nvmrc +++ b/services/clsi/.nvmrc @@ -1 +1 @@ -6.9.5 +6.13.0 diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 05be84642a..e195f969fc 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -4,13 +4,13 @@ COPY ./ /app WORKDIR /app - RUN npm install RUN [ -e ./install_deps.sh ] && ./install_deps.sh RUN npm run compile +ENV SHARELATEX_CONFIG /app/config/settings.production.coffee ENV NODE_ENV production CMD ["node","/app/app.js"] diff --git a/services/clsi/Makefile b/services/clsi/Makefile index 6e7fca5f18..828e8f8248 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -1,13 +1,16 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.0.1 +# Version: 1.0.3 BUILD_NUMBER ?= local BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) PROJECT_NAME = clsi DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml -DOCKER_COMPOSE := docker-compose ${DOCKER_COMPOSE_FLAGS} +DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ + BRANCH_NAME=$(BRANCH_NAME) \ + PROJECT_NAME=$(PROJECT_NAME) \ + docker-compose ${DOCKER_COMPOSE_FLAGS} clean: rm -f app.js @@ -24,7 +27,7 @@ test_acceptance: test_clean # clear the database before each acceptance test run @[ -d test/acceptance ] && $(DOCKER_COMPOSE) run --rm test_acceptance -- ${MOCHA_ARGS} || echo "clsi has no acceptance tests" test_clean: - $(DOCKER_COMPOSE) down + $(DOCKER_COMPOSE) down -t 0 build: docker build --pull --tag quay.io/sharelatex/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) . diff --git a/services/clsi/base.yml b/services/clsi/base.yml new file mode 100644 index 0000000000..ef5f44a1cf --- /dev/null +++ b/services/clsi/base.yml @@ -0,0 +1,14 @@ +version: "2" + +services: + base: + environment: + TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2017.1 + TEXLIVE_IMAGE_USER: root + SHARELATEX_CONFIG: /app/config/settings.defaults.coffee + COMMAND_RUNNER: docker-runner-sharelatex + COMPILES_HOST_DIR: $PWD/compiles + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./docker-runner:/app/node_modules/docker-runner-sharelatex + - ./compiles:/app/compiles diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index f34280d803..695ad3cf5d 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.0.1 +# Version: 1.0.3 version: "2" diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index a272e753b1..8c612e59ce 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -1,13 +1,13 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.0.1 +# Version: 1.0.3 version: "2" services: test_unit: - image: node:6.9.5 + image: node:6.13.0 volumes: - .:/app working_dir: /app @@ -15,16 +15,15 @@ services: test_acceptance: build: . + extends: + file: base.yml + service: base environment: - TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2017.1 - TEXLIVE_IMAGE_USER: root # Not ideal, but makes running in dev very simple - SHARELATEX_CONFIG: /app/config/settings.defaults.coffee - COMMAND_RUNNER: docker-runner-sharelatex - COMPILES_HOST_DIR: $PWD/compiles - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - ./docker-runner:/app/node_modules/docker-runner-sharelatex - - ./compiles:/app/compiles + REDIS_HOST: redis + MONGO_HOST: mongo + depends_on: + - redis + - mongo entrypoint: npm run test:acceptance redis: diff --git a/services/clsi/kube.yaml b/services/clsi/kube.yaml new file mode 100644 index 0000000000..44a562d0d5 --- /dev/null +++ b/services/clsi/kube.yaml @@ -0,0 +1,41 @@ +apiVersion: v1 +kind: Service +metadata: + name: clsi + namespace: default +spec: + type: LoadBalancer + ports: + - port: 3009 + protocol: TCP + targetPort: 3009 + selector: + run: clsi +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: clsi + namespace: default +spec: + replicas: 2 + template: + metadata: + labels: + run: clsi + spec: + containers: + - name: clsi + image: gcr.io/henry-terraform-admin/clsi:6e0c79688030117a9c27d0fc01d1271e6ac3dd3e + imagePullPolicy: Always + readinessProbe: + httpGet: + path: status + port: 3009 + periodSeconds: 5 + initialDelaySeconds: 0 + failureThreshold: 3 + successThreshold: 1 + + + diff --git a/services/clsi/nodemon.json b/services/clsi/nodemon.json index 9044f921c6..9a3be8d966 100644 --- a/services/clsi/nodemon.json +++ b/services/clsi/nodemon.json @@ -4,6 +4,7 @@ "node_modules/" ], "verbose": true, + "legacyWatch": true, "execMap": { "js": "npm run start" }, diff --git a/services/clsi/package.json b/services/clsi/package.json index 77e9423c0d..c5d52c949d 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -1,67 +1,67 @@ { - "name": "node-clsi", - "description": "A Node.js implementation of the CLSI LaTeX web-API", - "version": "0.1.4", + "name": "node-clsi", + "description": "A Node.js implementation of the CLSI LaTeX web-API", + "version": "0.1.4", "repository": { - "type": "git", + "type": "git", "url": "https://github.com/sharelatex/clsi-sharelatex.git" - }, + }, "scripts": { - "test:acceptance:wait_for_app": "echo 'Waiting for app to be accessible' && while (! curl -s -o /dev/null localhost:3013/status) do sleep 1; done", - "test:acceptance:run": "mocha --recursive --reporter spec --timeout 15000", - "test:acceptance:dir": "npm -q run test:acceptance:wait_for_app && npm -q run test:acceptance:run -- $@", - "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- $@", - "compile:test:acceptance": "coffee -o test/acceptance/js -c test/acceptance/coffee", - "compile:test:smoke": "coffee -o test/smoke/js -c test/smoke/coffee", - "compile:app": "coffee -o app/js -c app/coffee && coffee -c app.coffee", - "compile": "npm run compile:app && npm run compile:test:acceptance && npm run compile:test:smoke", - "start": "npm run compile:app && node app.js", - "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", - "test:unit:_run": "mocha --recursive --reporter spec $@ test/unit/js", - "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- $@", - "compile:unit_tests": "[ -e test/unit ] && coffee -o test/unit/js -c test/unit/coffee || echo 'No unit tests to compile'", - "compile:acceptance_tests": "[ -e test/acceptance ] && coffee -o test/acceptance/js -c test/acceptance/coffee || echo 'No acceptance tests to compile'", - "compile:all": "npm run compile:app && npm run compile:unit_tests && npm run compile:acceptance_tests", + "test:acceptance:wait_for_app": "echo 'Waiting for app to be accessible' && while (! curl -s -o /dev/null localhost:3013/status) do sleep 1; done", + "test:acceptance:run": "mocha --recursive --reporter spec --timeout 15000", + "test:acceptance:dir": "npm -q run test:acceptance:wait_for_app && npm -q run test:acceptance:run -- $@", + "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- $@", + "compile:test:acceptance": "coffee -o test/acceptance/js -c test/acceptance/coffee", + "compile:test:smoke": "coffee -o test/smoke/js -c test/smoke/coffee", + "compile:app": "coffee -o app/js -c app/coffee && coffee -c app.coffee", + "compile": "npm run compile:app && npm run compile:test:acceptance && npm run compile:test:smoke", + "start": "npm run compile:app && node app.js", + "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", + "test:unit:_run": "mocha --recursive --reporter spec $@ test/unit/js", + "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- $@", + "compile:unit_tests": "[ -e test/unit ] && coffee -o test/unit/js -c test/unit/coffee || echo 'No unit tests to compile'", + "compile:acceptance_tests": "[ -e test/acceptance ] && coffee -o test/acceptance/js -c test/acceptance/coffee || echo 'No acceptance tests to compile'", + "compile:all": "npm run compile:app && npm run compile:unit_tests && npm run compile:acceptance_tests", "nodemon": "nodemon --config nodemon.json" - }, - "author": "James Allen <james@sharelatex.com>", + }, + "author": "James Allen <james@sharelatex.com>", "dependencies": { - "async": "0.2.9", - "body-parser": "^1.2.0", - "dockerode": "^2.5.3", - "express": "^4.2.0", - "fs-extra": "^0.16.3", - "grunt-mkdir": "^1.0.0", - "heapdump": "^0.3.5", - "lockfile": "^1.0.3", - "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", - "lynx": "0.0.11", - "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.5.0", - "mkdirp": "0.3.5", - "mysql": "2.6.2", - "request": "^2.21.0", - "sequelize": "^2.1.3", - "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", - "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", - "sqlite3": "^3.1.13", - "underscore": "^1.8.2", - "v8-profiler": "^5.2.4", + "async": "0.2.9", + "body-parser": "^1.2.0", + "dockerode": "^2.5.3", + "express": "^4.2.0", + "fs-extra": "^0.16.3", + "grunt-mkdir": "^1.0.0", + "heapdump": "^0.3.5", + "lockfile": "^1.0.3", + "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", + "lynx": "0.0.11", + "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.5.0", + "mkdirp": "0.3.5", + "mysql": "2.6.2", + "request": "^2.21.0", + "sequelize": "^2.1.3", + "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", + "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", + "sqlite3": "^3.1.13", + "underscore": "^1.8.2", + "v8-profiler": "^5.2.4", "wrench": "~1.5.4" - }, + }, "devDependencies": { - "mocha": "^4.0.1", - "coffee-script": "1.6.0", - "chai": "~1.8.1", - "sinon": "~1.7.3", - "grunt": "~0.4.2", - "grunt-contrib-coffee": "~0.7.0", - "grunt-contrib-clean": "~0.5.0", - "grunt-shell": "~0.6.1", - "grunt-mocha-test": "~0.8.1", - "sandboxed-module": "~0.3.0", - "timekeeper": "0.0.4", - "grunt-execute": "^0.1.5", - "bunyan": "^0.22.1", + "mocha": "^4.0.1", + "coffee-script": "1.6.0", + "chai": "~1.8.1", + "sinon": "~1.7.3", + "grunt": "~0.4.2", + "grunt-contrib-coffee": "~0.7.0", + "grunt-contrib-clean": "~0.5.0", + "grunt-shell": "~0.6.1", + "grunt-mocha-test": "~0.8.1", + "sandboxed-module": "~0.3.0", + "timekeeper": "0.0.4", + "grunt-execute": "^0.1.5", + "bunyan": "^0.22.1", "grunt-bunyan": "^0.5.0" } } From c0058ac7204afb8aaf627f1345ecbf999af9276c Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 16 Feb 2018 15:45:47 +0000 Subject: [PATCH 330/754] mount app as volume in docker container for local tests change to overrides --- .../{base.yml => docker-compose-overrides.yml} | 14 +++++++++++++- services/clsi/docker-compose.yml | 7 +++++-- 2 files changed, 18 insertions(+), 3 deletions(-) rename services/clsi/{base.yml => docker-compose-overrides.yml} (50%) diff --git a/services/clsi/base.yml b/services/clsi/docker-compose-overrides.yml similarity index 50% rename from services/clsi/base.yml rename to services/clsi/docker-compose-overrides.yml index ef5f44a1cf..3d64975fd2 100644 --- a/services/clsi/base.yml +++ b/services/clsi/docker-compose-overrides.yml @@ -1,7 +1,7 @@ version: "2" services: - base: + dev: environment: TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2017.1 TEXLIVE_IMAGE_USER: root @@ -12,3 +12,15 @@ services: - /var/run/docker.sock:/var/run/docker.sock - ./docker-runner:/app/node_modules/docker-runner-sharelatex - ./compiles:/app/compiles + + ci: + environment: + TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2017.1 + TEXLIVE_IMAGE_USER: root + SHARELATEX_CONFIG: /app/config/settings.defaults.coffee + COMMAND_RUNNER: docker-runner-sharelatex + COMPILES_HOST_DIR: $PWD/compiles + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./docker-runner:/app/node_modules/docker-runner-sharelatex + - ./compiles:/app/compiles \ No newline at end of file diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 8c612e59ce..6126666cf3 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -15,9 +15,12 @@ services: test_acceptance: build: . + volumes: + - .:/app + working_dir: /app extends: - file: base.yml - service: base + file: docker-compose-overrides.yml + service: dev environment: REDIS_HOST: redis MONGO_HOST: mongo From b1c0abbd4d2b61c56146e9889b6588add9da1631 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Wed, 21 Feb 2018 15:42:51 +0000 Subject: [PATCH 331/754] updateded build scripts --- services/clsi/Jenkinsfile | 36 +++++++++++++++++-- services/clsi/Makefile | 7 +--- ...verrides.yml => docker-compose-config.yml} | 0 services/clsi/docker-compose.ci.yml | 12 +++++-- services/clsi/docker-compose.yml | 6 ++-- services/clsi/kube.yaml | 8 ++--- 6 files changed, 50 insertions(+), 19 deletions(-) rename services/clsi/{docker-compose-overrides.yml => docker-compose-config.yml} (100%) diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index bc9ba0142f..326dce5fb9 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -9,9 +9,34 @@ pipeline { } stages { - stage('Build') { + stage('Install') { + agent { + docker { + image 'node:6.13.0' + args "-v /var/lib/jenkins/.npm:/tmp/.npm -e HOME=/tmp" + reuseNode true + } + } steps { - sh 'make build' + // we need to disable logallrefupdates, else git clones + // during the npm install will require git to lookup the + // user id which does not exist in the container's + // /etc/passwd file, causing the clone to fail. + sh 'git config --global core.logallrefupdates false' + sh 'rm -rf node_modules' + sh 'npm install && npm rebuild' + } + } + + stage('Compile') { + agent { + docker { + image 'node:6.13.0' + reuseNode true + } + } + steps { + sh 'npm run compile:all' } } @@ -29,7 +54,12 @@ pipeline { stage('Package and publish build') { steps { - sh 'make publish' + sh 'echo ${BUILD_NUMBER} > build_number.txt' + sh 'touch build.tar.gz' // Avoid tar warning about files changing during read + sh 'tar -czf build.tar.gz --exclude=build.tar.gz --exclude-vcs .' + 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") + } } } diff --git a/services/clsi/Makefile b/services/clsi/Makefile index 828e8f8248..89066f54c5 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.0.3 +# Version: 1.1.0 BUILD_NUMBER ?= local BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) @@ -28,10 +28,5 @@ test_acceptance: test_clean # clear the database before each acceptance test run test_clean: $(DOCKER_COMPOSE) down -t 0 -build: - docker build --pull --tag quay.io/sharelatex/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) . - -publish: - docker push quay.io/sharelatex/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) .PHONY: clean test test_unit test_acceptance test_clean build publish diff --git a/services/clsi/docker-compose-overrides.yml b/services/clsi/docker-compose-config.yml similarity index 100% rename from services/clsi/docker-compose-overrides.yml rename to services/clsi/docker-compose-config.yml diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 695ad3cf5d..00740d4c0a 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -1,17 +1,23 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.0.3 +# Version: 1.1.0 version: "2" services: test_unit: - image: quay.io/sharelatex/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER + image: node:6.13.0 + volumes: + - .:/app + working_dir: /app entrypoint: npm run test:unit:_run test_acceptance: - image: quay.io/sharelatex/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER + image: node:6.13.0 + volumes: + - .:/app + working_dir: /app environment: REDIS_HOST: redis MONGO_HOST: mongo diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 6126666cf3..c259abcd69 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.0.3 +# Version: 1.1.0 version: "2" @@ -14,12 +14,12 @@ services: entrypoint: npm run test:unit test_acceptance: - build: . + image: node:6.13.0 volumes: - .:/app working_dir: /app extends: - file: docker-compose-overrides.yml + file: docker-compose-config.yml service: dev environment: REDIS_HOST: redis diff --git a/services/clsi/kube.yaml b/services/clsi/kube.yaml index 44a562d0d5..d3fb04291e 100644 --- a/services/clsi/kube.yaml +++ b/services/clsi/kube.yaml @@ -6,9 +6,9 @@ metadata: spec: type: LoadBalancer ports: - - port: 3009 + - port: 80 protocol: TCP - targetPort: 3009 + targetPort: 80 selector: run: clsi --- @@ -26,12 +26,12 @@ spec: spec: containers: - name: clsi - image: gcr.io/henry-terraform-admin/clsi:6e0c79688030117a9c27d0fc01d1271e6ac3dd3e + image: gcr.io/henry-terraform-admin/clsi imagePullPolicy: Always readinessProbe: httpGet: path: status - port: 3009 + port: 80 periodSeconds: 5 initialDelaySeconds: 0 failureThreshold: 3 From 603069ea5966923cee7fb6e88431fbce9d8e673b Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 26 Feb 2018 11:56:19 +0000 Subject: [PATCH 332/754] tests pass under app user --- services/clsi/Dockerfile | 25 ++++++++++++++------ services/clsi/Jenkinsfile | 36 +++-------------------------- services/clsi/Makefile | 5 ++++ services/clsi/docker-compose.ci.yml | 10 ++------ services/clsi/docker-compose.yml | 2 +- services/clsi/install_deps.sh | 6 ++++- 6 files changed, 34 insertions(+), 50 deletions(-) diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index e195f969fc..881852dae7 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -1,4 +1,4 @@ -FROM node:6.13.0 +FROM node:6.13.0 as app COPY ./ /app @@ -6,11 +6,22 @@ WORKDIR /app RUN npm install + +RUN npm run compile:all + +FROM node:6.13.0 + +COPY --from=app /app /app + +WORKDIR /app + + +# All app and node_modules will be owned by root. +# The app will run as the 'app' user, and so not have write permissions +# on any files it doesn't need. +RUN useradd --user-group --create-home --home-dir /app --shell /bin/bash app + RUN [ -e ./install_deps.sh ] && ./install_deps.sh -RUN npm run compile - -ENV SHARELATEX_CONFIG /app/config/settings.production.coffee -ENV NODE_ENV production - -CMD ["node","/app/app.js"] +USER app +CMD ["node","app.js"] diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index 326dce5fb9..bc9ba0142f 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -9,34 +9,9 @@ pipeline { } stages { - stage('Install') { - agent { - docker { - image 'node:6.13.0' - args "-v /var/lib/jenkins/.npm:/tmp/.npm -e HOME=/tmp" - reuseNode true - } - } + stage('Build') { steps { - // we need to disable logallrefupdates, else git clones - // during the npm install will require git to lookup the - // user id which does not exist in the container's - // /etc/passwd file, causing the clone to fail. - sh 'git config --global core.logallrefupdates false' - sh 'rm -rf node_modules' - sh 'npm install && npm rebuild' - } - } - - stage('Compile') { - agent { - docker { - image 'node:6.13.0' - reuseNode true - } - } - steps { - sh 'npm run compile:all' + sh 'make build' } } @@ -54,12 +29,7 @@ pipeline { stage('Package and publish build') { steps { - sh 'echo ${BUILD_NUMBER} > build_number.txt' - sh 'touch build.tar.gz' // Avoid tar warning about files changing during read - sh 'tar -czf build.tar.gz --exclude=build.tar.gz --exclude-vcs .' - 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") - } + sh 'make publish' } } diff --git a/services/clsi/Makefile b/services/clsi/Makefile index 89066f54c5..b20654c634 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -28,5 +28,10 @@ test_acceptance: test_clean # clear the database before each acceptance test run test_clean: $(DOCKER_COMPOSE) down -t 0 +build: + docker build --pull --tag quay.io/sharelatex/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) . + +publish: + docker push quay.io/sharelatex/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) .PHONY: clean test test_unit test_acceptance test_clean build publish diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 00740d4c0a..cbe15fe9a1 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -7,17 +7,11 @@ version: "2" services: test_unit: - image: node:6.13.0 - volumes: - - .:/app - working_dir: /app + image: quay.io/sharelatex/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER entrypoint: npm run test:unit:_run test_acceptance: - image: node:6.13.0 - volumes: - - .:/app - working_dir: /app + image: quay.io/sharelatex/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER environment: REDIS_HOST: redis MONGO_HOST: mongo diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index c259abcd69..cf90742761 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -14,7 +14,7 @@ services: entrypoint: npm run test:unit test_acceptance: - image: node:6.13.0 + build: . volumes: - .:/app working_dir: /app diff --git a/services/clsi/install_deps.sh b/services/clsi/install_deps.sh index 49bdc5c963..3caa1c7c2c 100755 --- a/services/clsi/install_deps.sh +++ b/services/clsi/install_deps.sh @@ -1,4 +1,8 @@ /bin/sh wget -qO- https://get.docker.com/ | sh -apt-get install poppler-utils vim ghostscript --yes +apt-get install poppler-utils ghostscript --yes npm rebuild +usermod -aG docker app + +touch /var/run/docker.sock +chown root:docker /var/run/docker.sock From 3399f55153fc8c62d88d3b8b8dcf87415395d166 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 26 Feb 2018 14:29:30 +0000 Subject: [PATCH 333/754] wip, docker container is correctly created --- .../clsi/app/coffee/CompileManager.coffee | 40 +- services/clsi/config/settings.defaults.coffee | 2 - services/clsi/docker-compose.yml | 11 + .../coffee/BrokenLatexFileTests.coffee | 83 ++-- .../coffee/DeleteOldFilesTest.coffee | 60 +-- .../coffee/ExampleDocumentTests.coffee | 196 ++++----- .../coffee/SimpleLatexFileTests.coffee | 70 ++-- .../acceptance/coffee/SynctexTests.coffee | 16 +- .../acceptance/coffee/TimeoutTests.coffee | 52 +-- .../acceptance/coffee/UrlCachingTests.coffee | 378 +++++++++--------- .../acceptance/coffee/WordcountTests.coffee | 72 ++-- 11 files changed, 495 insertions(+), 485 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index d3d319af94..a6df6dd594 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -41,7 +41,7 @@ module.exports = CompileManager = doCompile: (request, callback = (error, outputFiles) ->) -> compileDir = getCompileDir(request.project_id, request.user_id) - console.log("doCompile",compileDir ) + # console.log("doCompile", compileDir) timer = new Metrics.Timer("write-to-disk") logger.log project_id: request.project_id, user_id: request.user_id, "syncing resources to disk" @@ -206,9 +206,9 @@ module.exports = CompileManager = base_dir = Settings.path.synctexBaseDir(compileName) file_path = base_dir + "/" + file_name compileDir = getCompileDir(project_id, user_id) - synctex_path = Path.join(compileDir, "output.pdf") + synctex_path = "$COMPILE_DIR/output.pdf" command = ["code", synctex_path, file_path, line, column] - CompileManager._runSynctex command, (error, stdout) -> + CompileManager._runSynctex project_id, user_id, command, (error, stdout) -> return callback(error) if error? if stdout.toLowerCase().indexOf("warning") == -1 logType = "log" @@ -221,9 +221,9 @@ module.exports = CompileManager = compileName = getCompileName(project_id, user_id) base_dir = Settings.path.synctexBaseDir(compileName) compileDir = getCompileDir(project_id, user_id) - synctex_path = Path.join(compileDir, "output.pdf") - logger.log({base_dir, project_id, synctex_path}, "base diiir") - CompileManager._runSynctex ["pdf", synctex_path, page, h, v], (error, stdout) -> + synctex_path = "$COMPILE_DIR/output.pdf" + command = ["pdf", synctex_path, page, h, v] + CompileManager._runSynctex project_id, user_id, command, (error, stdout) -> return callback(error) if error? logger.log project_id: project_id, user_id:user_id, page: page, h: h, v:v, stdout: stdout, "synctex pdf output" callback null, CompileManager._parseSynctexFromPdfOutput(stdout, base_dir) @@ -242,20 +242,22 @@ module.exports = CompileManager = return callback(new Error("not a file")) if not stats?.isFile() callback() - _runSynctex: (args, callback = (error, stdout) ->) -> - bin_path = Path.resolve(__dirname + "/../../bin/synctex") + _runSynctex: (project_id, user_id, command, callback = (error, stdout) ->) -> seconds = 1000 - outputFilePath = args[1] - CompileManager._checkFileExists outputFilePath, (error) -> - return callback(error) if error? - if Settings.clsi?.synctexCommandWrapper? - [bin_path, args] = Settings.clsi?.synctexCommandWrapper bin_path, args - logger.log({bin_path, args}, "synctex being run") - child_process.execFile bin_path, args, timeout: 10 * seconds, (error, stdout, stderr) -> - if error? - logger.err err:error, args:args, "error running synctex" - return callback(error) - callback(null, stdout) + + #this is a hack, only works for docker runner + command.unshift("/opt/synctex") + directory = getCompileDir(project_id, user_id) + timeout = 10 * 1000 + compileName = getCompileName(project_id, user_id) + console.log command, "_runSynctex" + + CommandRunner.run compileName, command, directory, Settings.clsi.docker.image, timeout, {}, (error, stdout) -> + console.log("synctex run", stdout) + if error? + logger.err err:error, command:command, "error running synctex" + return callback(error) + callback(null, stdout) _parseSynctexFromCodeOutput: (output) -> results = [] diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index 9b1e4be967..d09784dee4 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -41,5 +41,3 @@ if process.env["COMMAND_RUNNER"] checkProjectsIntervalMs: 10 * 60 * 1000 module.exports.path.sandboxedCompilesHostDir = process.env["COMPILES_HOST_DIR"] - -console.log module.exports \ No newline at end of file diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index cf90742761..090c360259 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -29,6 +29,17 @@ services: - mongo entrypoint: npm run test:acceptance + synctex: + image: quay.io/sharelatex/texlive-full:2017.1 + volumes: + - ~/Projects/sharelatex-dev-environment/clsi/compiles/564c29f884179:/compile + - ./bin/synctex:/opt/synctex + + command: + /opt/synctex pdf /compile/output.pdf 1 100 200 + + + redis: image: redis diff --git a/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.coffee b/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.coffee index a54df5568f..755f28e7f1 100644 --- a/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.coffee +++ b/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.coffee @@ -1,49 +1,48 @@ -Client = require "./helpers/Client" -request = require "request" -require("chai").should() -ClsiApp = require "./helpers/ClsiApp" +# Client = require "./helpers/Client" +# request = require "request" +# require("chai").should() +# ClsiApp = require "./helpers/ClsiApp" -describe "Broken LaTeX file", -> - before (done)-> - @broken_request = - resources: [ - path: "main.tex" - content: ''' - \\documentclass{articl % :( - \\begin{documen % :( - Broken - \\end{documen % :( - ''' - ] - @correct_request = - resources: [ - path: "main.tex" - content: ''' - \\documentclass{article} - \\begin{document} - Hello world - \\end{document} - ''' - ] - ClsiApp.ensureRunning done +# describe "Broken LaTeX file", -> +# before (done)-> +# @broken_request = +# resources: [ +# path: "main.tex" +# content: ''' +# \\documentclass{articl % :( +# \\begin{documen % :( +# Broken +# \\end{documen % :( +# ''' +# ] +# @correct_request = +# resources: [ +# path: "main.tex" +# content: ''' +# \\documentclass{article} +# \\begin{document} +# Hello world +# \\end{document} +# ''' +# ] +# ClsiApp.ensureRunning done - describe "on first run", -> - before (done) -> - @project_id = Client.randomId() - Client.compile @project_id, @broken_request, (@error, @res, @body) => done() +# describe "on first run", -> +# before (done) -> +# @project_id = Client.randomId() +# Client.compile @project_id, @broken_request, (@error, @res, @body) => done() - it "should return a failure status", -> - console.log(@error, @res, @body) - @body.compile.status.should.equal "failure" +# it "should return a failure status", -> +# @body.compile.status.should.equal "failure" - describe "on second run", -> - before (done) -> - @project_id = Client.randomId() - Client.compile @project_id, @correct_request, () => - Client.compile @project_id, @broken_request, (@error, @res, @body) => - done() +# describe "on second run", -> +# before (done) -> +# @project_id = Client.randomId() +# Client.compile @project_id, @correct_request, () => +# Client.compile @project_id, @broken_request, (@error, @res, @body) => +# done() - it "should return a failure status", -> - @body.compile.status.should.equal "failure" +# it "should return a failure status", -> +# @body.compile.status.should.equal "failure" diff --git a/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.coffee b/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.coffee index 1cb67765ca..750f5f9312 100644 --- a/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.coffee +++ b/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.coffee @@ -1,36 +1,36 @@ -Client = require "./helpers/Client" -request = require "request" -require("chai").should() -ClsiApp = require "./helpers/ClsiApp" +# Client = require "./helpers/Client" +# request = require "request" +# require("chai").should() +# ClsiApp = require "./helpers/ClsiApp" -describe "Deleting Old Files", -> - before (done)-> - @request = - resources: [ - path: "main.tex" - content: ''' - \\documentclass{article} - \\begin{document} - Hello world - \\end{document} - ''' - ] - ClsiApp.ensureRunning done +# describe "Deleting Old Files", -> +# before (done)-> +# @request = +# resources: [ +# path: "main.tex" +# content: ''' +# \\documentclass{article} +# \\begin{document} +# Hello world +# \\end{document} +# ''' +# ] +# ClsiApp.ensureRunning done - describe "on first run", -> - before (done) -> - @project_id = Client.randomId() - Client.compile @project_id, @request, (@error, @res, @body) => done() +# describe "on first run", -> +# before (done) -> +# @project_id = Client.randomId() +# Client.compile @project_id, @request, (@error, @res, @body) => done() - it "should return a success status", -> - @body.compile.status.should.equal "success" +# it "should return a success status", -> +# @body.compile.status.should.equal "success" - describe "after file has been deleted", -> - before (done) -> - @request.resources = [] - Client.compile @project_id, @request, (@error, @res, @body) => - done() +# describe "after file has been deleted", -> +# before (done) -> +# @request.resources = [] +# Client.compile @project_id, @request, (@error, @res, @body) => +# done() - it "should return a failure status", -> - @body.compile.status.should.equal "failure" +# it "should return a failure status", -> +# @body.compile.status.should.equal "failure" diff --git a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee index 5ec61111b3..1a1a0a0aaf 100644 --- a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee +++ b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee @@ -1,114 +1,114 @@ -Client = require "./helpers/Client" -request = require "request" -require("chai").should() -fs = require "fs" -ChildProcess = require "child_process" -ClsiApp = require "./helpers/ClsiApp" +# Client = require "./helpers/Client" +# request = require "request" +# require("chai").should() +# fs = require "fs" +# ChildProcess = require "child_process" +# ClsiApp = require "./helpers/ClsiApp" -fixturePath = (path) -> __dirname + "/../fixtures/" + path +# fixturePath = (path) -> __dirname + "/../fixtures/" + path -try - fs.mkdirSync(fixturePath("tmp")) -catch e +# try +# fs.mkdirSync(fixturePath("tmp")) +# catch e -convertToPng = (pdfPath, pngPath, callback = (error) ->) -> - command = "convert #{fixturePath(pdfPath)} #{fixturePath(pngPath)}" - convert = ChildProcess.exec command - stdout = "" - convert.stdout.on "data", (chunk) -> console.log "STDOUT", chunk.toString() - convert.stderr.on "data", (chunk) -> console.log "STDERR", chunk.toString() - convert.on "exit", () -> - callback() +# convertToPng = (pdfPath, pngPath, callback = (error) ->) -> +# command = "convert #{fixturePath(pdfPath)} #{fixturePath(pngPath)}" +# convert = ChildProcess.exec command +# stdout = "" +# convert.stdout.on "data", (chunk) -> console.log "STDOUT", chunk.toString() +# convert.stderr.on "data", (chunk) -> console.log "STDERR", chunk.toString() +# convert.on "exit", () -> +# callback() -compare = (originalPath, generatedPath, callback = (error, same) ->) -> - diff_file = "#{fixturePath(generatedPath)}-diff.png" - proc = ChildProcess.exec "compare -metric mae #{fixturePath(originalPath)} #{fixturePath(generatedPath)} #{diff_file}" - stderr = "" - proc.stderr.on "data", (chunk) -> stderr += chunk - proc.on "exit", () -> - if stderr.trim() == "0 (0)" - fs.unlink diff_file # remove output diff if test matches expected image - callback null, true - else - console.log "compare result", stderr - callback null, false +# compare = (originalPath, generatedPath, callback = (error, same) ->) -> +# diff_file = "#{fixturePath(generatedPath)}-diff.png" +# proc = ChildProcess.exec "compare -metric mae #{fixturePath(originalPath)} #{fixturePath(generatedPath)} #{diff_file}" +# stderr = "" +# proc.stderr.on "data", (chunk) -> stderr += chunk +# proc.on "exit", () -> +# if stderr.trim() == "0 (0)" +# fs.unlink diff_file # remove output diff if test matches expected image +# callback null, true +# else +# console.log "compare result", stderr +# callback null, false -checkPdfInfo = (pdfPath, callback = (error, output) ->) -> - proc = ChildProcess.exec "pdfinfo #{fixturePath(pdfPath)}" - stdout = "" - proc.stdout.on "data", (chunk) -> stdout += chunk - proc.stderr.on "data", (chunk) -> console.log "STDERR", chunk.toString() - proc.on "exit", () -> - if stdout.match(/Optimized:\s+yes/) - callback null, true - else - console.log "pdfinfo result", stdout - callback null, false +# checkPdfInfo = (pdfPath, callback = (error, output) ->) -> +# proc = ChildProcess.exec "pdfinfo #{fixturePath(pdfPath)}" +# stdout = "" +# proc.stdout.on "data", (chunk) -> stdout += chunk +# proc.stderr.on "data", (chunk) -> console.log "STDERR", chunk.toString() +# proc.on "exit", () -> +# if stdout.match(/Optimized:\s+yes/) +# callback null, true +# else +# console.log "pdfinfo result", stdout +# callback null, false -compareMultiplePages = (project_id, callback = (error) ->) -> - compareNext = (page_no, callback) -> - path = "tmp/#{project_id}-source-#{page_no}.png" - fs.stat fixturePath(path), (error, stat) -> - if error? - callback() - else - compare "tmp/#{project_id}-source-#{page_no}.png", "tmp/#{project_id}-generated-#{page_no}.png", (error, same) => - throw error if error? - same.should.equal true - compareNext page_no + 1, callback - compareNext 0, callback +# compareMultiplePages = (project_id, callback = (error) ->) -> +# compareNext = (page_no, callback) -> +# path = "tmp/#{project_id}-source-#{page_no}.png" +# fs.stat fixturePath(path), (error, stat) -> +# if error? +# callback() +# else +# compare "tmp/#{project_id}-source-#{page_no}.png", "tmp/#{project_id}-generated-#{page_no}.png", (error, same) => +# throw error if error? +# same.should.equal true +# compareNext page_no + 1, callback +# compareNext 0, callback -comparePdf = (project_id, example_dir, callback = (error) ->) -> - convertToPng "tmp/#{project_id}.pdf", "tmp/#{project_id}-generated.png", (error) => - throw error if error? - convertToPng "examples/#{example_dir}/output.pdf", "tmp/#{project_id}-source.png", (error) => - throw error if error? - fs.stat fixturePath("tmp/#{project_id}-source-0.png"), (error, stat) => - if error? - compare "tmp/#{project_id}-source.png", "tmp/#{project_id}-generated.png", (error, same) => - throw error if error? - same.should.equal true - callback() - else - compareMultiplePages project_id, (error) -> - throw error if error? - callback() +# comparePdf = (project_id, example_dir, callback = (error) ->) -> +# convertToPng "tmp/#{project_id}.pdf", "tmp/#{project_id}-generated.png", (error) => +# throw error if error? +# convertToPng "examples/#{example_dir}/output.pdf", "tmp/#{project_id}-source.png", (error) => +# throw error if error? +# fs.stat fixturePath("tmp/#{project_id}-source-0.png"), (error, stat) => +# if error? +# compare "tmp/#{project_id}-source.png", "tmp/#{project_id}-generated.png", (error, same) => +# throw error if error? +# same.should.equal true +# callback() +# else +# compareMultiplePages project_id, (error) -> +# throw error if error? +# callback() -downloadAndComparePdf = (project_id, example_dir, url, callback = (error) ->) -> - writeStream = fs.createWriteStream(fixturePath("tmp/#{project_id}.pdf")) - request.get(url).pipe(writeStream) - writeStream.on "close", () => - checkPdfInfo "tmp/#{project_id}.pdf", (error, optimised) => - throw error if error? - optimised.should.equal true - comparePdf project_id, example_dir, callback +# downloadAndComparePdf = (project_id, example_dir, url, callback = (error) ->) -> +# writeStream = fs.createWriteStream(fixturePath("tmp/#{project_id}.pdf")) +# request.get(url).pipe(writeStream) +# writeStream.on "close", () => +# checkPdfInfo "tmp/#{project_id}.pdf", (error, optimised) => +# throw error if error? +# optimised.should.equal true +# comparePdf project_id, example_dir, callback -Client.runServer(4242, fixturePath("examples")) +# Client.runServer(4242, fixturePath("examples")) -describe "Example Documents", -> - before (done) -> - ChildProcess.exec("rm test/acceptance/fixtures/tmp/*").on "exit", () -> - ClsiApp.ensureRunning done +# describe "Example Documents", -> +# before (done) -> +# ChildProcess.exec("rm test/acceptance/fixtures/tmp/*").on "exit", () -> +# ClsiApp.ensureRunning done - for example_dir in fs.readdirSync fixturePath("examples") - do (example_dir) -> - describe example_dir, -> - before -> - @project_id = Client.randomId() + "_" + example_dir +# for example_dir in fs.readdirSync fixturePath("examples") +# do (example_dir) -> +# describe example_dir, -> +# before -> +# @project_id = Client.randomId() + "_" + example_dir - it "should generate the correct pdf", (done) -> - Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => - if error || body?.compile?.status is "failure" - console.log "DEBUG: error", error, "body", JSON.stringify(body) - pdf = Client.getOutputFile body, "pdf" - downloadAndComparePdf(@project_id, example_dir, pdf.url, done) +# it "should generate the correct pdf", (done) -> +# Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => +# if error || body?.compile?.status is "failure" +# console.log "DEBUG: error", error, "body", JSON.stringify(body) +# pdf = Client.getOutputFile body, "pdf" +# downloadAndComparePdf(@project_id, example_dir, pdf.url, done) - it "should generate the correct pdf on the second run as well", (done) -> - Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => - if error || body?.compile?.status is "failure" - console.log "DEBUG: error", error, "body", JSON.stringify(body) - pdf = Client.getOutputFile body, "pdf" - downloadAndComparePdf(@project_id, example_dir, pdf.url, done) +# it "should generate the correct pdf on the second run as well", (done) -> +# Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => +# if error || body?.compile?.status is "failure" +# console.log "DEBUG: error", error, "body", JSON.stringify(body) +# pdf = Client.getOutputFile body, "pdf" +# downloadAndComparePdf(@project_id, example_dir, pdf.url, done) diff --git a/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee b/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee index 95b667ba20..ae21c61f76 100644 --- a/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee +++ b/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee @@ -1,41 +1,41 @@ -Client = require "./helpers/Client" -request = require "request" -require("chai").should() -ClsiApp = require "./helpers/ClsiApp" +# Client = require "./helpers/Client" +# request = require "request" +# require("chai").should() +# ClsiApp = require "./helpers/ClsiApp" -describe "Simple LaTeX file", -> - before (done) -> - @project_id = Client.randomId() - @request = - resources: [ - path: "main.tex" - content: ''' - \\documentclass{article} - \\begin{document} - Hello world - \\end{document} - ''' - ] - ClsiApp.ensureRunning => - Client.compile @project_id, @request, (@error, @res, @body) => done() +# describe "Simple LaTeX file", -> +# before (done) -> +# @project_id = Client.randomId() +# @request = +# resources: [ +# path: "main.tex" +# content: ''' +# \\documentclass{article} +# \\begin{document} +# Hello world +# \\end{document} +# ''' +# ] +# ClsiApp.ensureRunning => +# Client.compile @project_id, @request, (@error, @res, @body) => done() - it "should return the PDF", -> - pdf = Client.getOutputFile(@body, "pdf") - pdf.type.should.equal "pdf" +# it "should return the PDF", -> +# pdf = Client.getOutputFile(@body, "pdf") +# pdf.type.should.equal "pdf" - it "should return the log", -> - log = Client.getOutputFile(@body, "log") - log.type.should.equal "log" +# it "should return the log", -> +# log = Client.getOutputFile(@body, "log") +# log.type.should.equal "log" - it "should provide the pdf for download", (done) -> - pdf = Client.getOutputFile(@body, "pdf") - request.get pdf.url, (error, res, body) -> - res.statusCode.should.equal 200 - done() +# it "should provide the pdf for download", (done) -> +# pdf = Client.getOutputFile(@body, "pdf") +# request.get pdf.url, (error, res, body) -> +# res.statusCode.should.equal 200 +# done() - it "should provide the log for download", (done) -> - log = Client.getOutputFile(@body, "pdf") - request.get log.url, (error, res, body) -> - res.statusCode.should.equal 200 - done() +# it "should provide the log for download", (done) -> +# log = Client.getOutputFile(@body, "pdf") +# request.get log.url, (error, res, body) -> +# res.statusCode.should.equal 200 +# done() diff --git a/services/clsi/test/acceptance/coffee/SynctexTests.coffee b/services/clsi/test/acceptance/coffee/SynctexTests.coffee index c6adcf2bf6..cbd22a791d 100644 --- a/services/clsi/test/acceptance/coffee/SynctexTests.coffee +++ b/services/clsi/test/acceptance/coffee/SynctexTests.coffee @@ -20,14 +20,14 @@ describe "Syncing", -> ClsiApp.ensureRunning => Client.compile @project_id, @request, (@error, @res, @body) => done() - describe "from code to pdf", -> - it "should return the correct location", (done) -> - Client.syncFromCode @project_id, "main.tex", 3, 5, (error, pdfPositions) -> - throw error if error? - expect(pdfPositions).to.deep.equal( - pdf: [ { page: 1, h: 133.77, v: 134.76, height: 6.92, width: 343.71 } ] - ) - done() + # describe "from code to pdf", -> + # it "should return the correct location", (done) -> + # Client.syncFromCode @project_id, "main.tex", 3, 5, (error, pdfPositions) -> + # throw error if error? + # expect(pdfPositions).to.deep.equal( + # pdf: [ { page: 1, h: 133.77, v: 134.76, height: 6.92, width: 343.71 } ] + # ) + # done() describe "from pdf to code", -> it "should return the correct location", (done) -> diff --git a/services/clsi/test/acceptance/coffee/TimeoutTests.coffee b/services/clsi/test/acceptance/coffee/TimeoutTests.coffee index 386148714c..2a0f693854 100644 --- a/services/clsi/test/acceptance/coffee/TimeoutTests.coffee +++ b/services/clsi/test/acceptance/coffee/TimeoutTests.coffee @@ -1,31 +1,31 @@ -Client = require "./helpers/Client" -request = require "request" -require("chai").should() -ClsiApp = require "./helpers/ClsiApp" +# Client = require "./helpers/Client" +# request = require "request" +# require("chai").should() +# ClsiApp = require "./helpers/ClsiApp" -describe "Timed out compile", -> - before (done) -> - @request = - options: - timeout: 1 #seconds - resources: [ - path: "main.tex" - content: ''' - \\documentclass{article} - \\begin{document} - Hello world - \\input{|"sleep 10"} - \\end{document} - ''' - ] - @project_id = Client.randomId() - ClsiApp.ensureRunning => - Client.compile @project_id, @request, (@error, @res, @body) => done() +# describe "Timed out compile", -> +# before (done) -> +# @request = +# options: +# timeout: 1 #seconds +# resources: [ +# path: "main.tex" +# content: ''' +# \\documentclass{article} +# \\begin{document} +# Hello world +# \\input{|"sleep 10"} +# \\end{document} +# ''' +# ] +# @project_id = Client.randomId() +# ClsiApp.ensureRunning => +# Client.compile @project_id, @request, (@error, @res, @body) => done() - it "should return a timeout error", -> - @body.compile.error.should.equal "container timed out" +# it "should return a timeout error", -> +# @body.compile.error.should.equal "container timed out" - it "should return a timedout status", -> - @body.compile.status.should.equal "timedout" +# it "should return a timedout status", -> +# @body.compile.status.should.equal "timedout" diff --git a/services/clsi/test/acceptance/coffee/UrlCachingTests.coffee b/services/clsi/test/acceptance/coffee/UrlCachingTests.coffee index cef744672e..89f67c2870 100644 --- a/services/clsi/test/acceptance/coffee/UrlCachingTests.coffee +++ b/services/clsi/test/acceptance/coffee/UrlCachingTests.coffee @@ -1,222 +1,222 @@ -Client = require "./helpers/Client" -request = require "request" -require("chai").should() -sinon = require "sinon" -ClsiApp = require "./helpers/ClsiApp" +# Client = require "./helpers/Client" +# request = require "request" +# require("chai").should() +# sinon = require "sinon" +# ClsiApp = require "./helpers/ClsiApp" -host = "localhost" +# host = "localhost" -Server = - run: () -> - express = require "express" - app = express() +# Server = +# run: () -> +# express = require "express" +# app = express() - staticServer = express.static __dirname + "/../fixtures/" - app.get "/:random_id/*", (req, res, next) => - @getFile(req.url) - req.url = "/" + req.params[0] - staticServer(req, res, next) +# staticServer = express.static __dirname + "/../fixtures/" +# app.get "/:random_id/*", (req, res, next) => +# @getFile(req.url) +# req.url = "/" + req.params[0] +# staticServer(req, res, next) - app.listen 31415, host +# app.listen 31415, host - getFile: () -> +# getFile: () -> - randomId: () -> - Math.random().toString(16).slice(2) +# randomId: () -> +# Math.random().toString(16).slice(2) -Server.run() +# Server.run() -describe "Url Caching", -> - describe "Downloading an image for the first time", -> - before (done) -> - @project_id = Client.randomId() - @file = "#{Server.randomId()}/lion.png" - @request = - resources: [{ - path: "main.tex" - content: ''' - \\documentclass{article} - \\usepackage{graphicx} - \\begin{document} - \\includegraphics{lion.png} - \\end{document} - ''' - }, { - path: "lion.png" - url: "http://#{host}:31415/#{@file}" - }] +# describe "Url Caching", -> +# describe "Downloading an image for the first time", -> +# before (done) -> +# @project_id = Client.randomId() +# @file = "#{Server.randomId()}/lion.png" +# @request = +# resources: [{ +# path: "main.tex" +# content: ''' +# \\documentclass{article} +# \\usepackage{graphicx} +# \\begin{document} +# \\includegraphics{lion.png} +# \\end{document} +# ''' +# }, { +# path: "lion.png" +# url: "http://#{host}:31415/#{@file}" +# }] - sinon.spy Server, "getFile" - ClsiApp.ensureRunning => - Client.compile @project_id, @request, (@error, @res, @body) => done() +# sinon.spy Server, "getFile" +# ClsiApp.ensureRunning => +# Client.compile @project_id, @request, (@error, @res, @body) => done() - afterEach -> - Server.getFile.restore() +# afterEach -> +# Server.getFile.restore() - it "should download the image", -> - Server.getFile - .calledWith("/" + @file) - .should.equal true +# it "should download the image", -> +# Server.getFile +# .calledWith("/" + @file) +# .should.equal true - describe "When an image is in the cache and the last modified date is unchanged", -> - before (done) -> - @project_id = Client.randomId() - @file = "#{Server.randomId()}/lion.png" - @request = - resources: [{ - path: "main.tex" - content: ''' - \\documentclass{article} - \\usepackage{graphicx} - \\begin{document} - \\includegraphics{lion.png} - \\end{document} - ''' - }, @image_resource = { - path: "lion.png" - url: "http://#{host}:31415/#{@file}" - modified: Date.now() - }] +# describe "When an image is in the cache and the last modified date is unchanged", -> +# before (done) -> +# @project_id = Client.randomId() +# @file = "#{Server.randomId()}/lion.png" +# @request = +# resources: [{ +# path: "main.tex" +# content: ''' +# \\documentclass{article} +# \\usepackage{graphicx} +# \\begin{document} +# \\includegraphics{lion.png} +# \\end{document} +# ''' +# }, @image_resource = { +# path: "lion.png" +# url: "http://#{host}:31415/#{@file}" +# modified: Date.now() +# }] - Client.compile @project_id, @request, (@error, @res, @body) => - sinon.spy Server, "getFile" - Client.compile @project_id, @request, (@error, @res, @body) => - done() +# Client.compile @project_id, @request, (@error, @res, @body) => +# sinon.spy Server, "getFile" +# Client.compile @project_id, @request, (@error, @res, @body) => +# done() - after -> - Server.getFile.restore() +# after -> +# Server.getFile.restore() - it "should not download the image again", -> - Server.getFile.called.should.equal false +# it "should not download the image again", -> +# Server.getFile.called.should.equal false - describe "When an image is in the cache and the last modified date is advanced", -> - before (done) -> - @project_id = Client.randomId() - @file = "#{Server.randomId()}/lion.png" - @request = - resources: [{ - path: "main.tex" - content: ''' - \\documentclass{article} - \\usepackage{graphicx} - \\begin{document} - \\includegraphics{lion.png} - \\end{document} - ''' - }, @image_resource = { - path: "lion.png" - url: "http://#{host}:31415/#{@file}" - modified: @last_modified = Date.now() - }] +# describe "When an image is in the cache and the last modified date is advanced", -> +# before (done) -> +# @project_id = Client.randomId() +# @file = "#{Server.randomId()}/lion.png" +# @request = +# resources: [{ +# path: "main.tex" +# content: ''' +# \\documentclass{article} +# \\usepackage{graphicx} +# \\begin{document} +# \\includegraphics{lion.png} +# \\end{document} +# ''' +# }, @image_resource = { +# path: "lion.png" +# url: "http://#{host}:31415/#{@file}" +# modified: @last_modified = Date.now() +# }] - Client.compile @project_id, @request, (@error, @res, @body) => - sinon.spy Server, "getFile" - @image_resource.modified = new Date(@last_modified + 3000) - Client.compile @project_id, @request, (@error, @res, @body) => - done() +# Client.compile @project_id, @request, (@error, @res, @body) => +# sinon.spy Server, "getFile" +# @image_resource.modified = new Date(@last_modified + 3000) +# Client.compile @project_id, @request, (@error, @res, @body) => +# done() - afterEach -> - Server.getFile.restore() +# afterEach -> +# Server.getFile.restore() - it "should download the image again", -> - Server.getFile.called.should.equal true +# it "should download the image again", -> +# Server.getFile.called.should.equal true - describe "When an image is in the cache and the last modified date is further in the past", -> - before (done) -> - @project_id = Client.randomId() - @file = "#{Server.randomId()}/lion.png" - @request = - resources: [{ - path: "main.tex" - content: ''' - \\documentclass{article} - \\usepackage{graphicx} - \\begin{document} - \\includegraphics{lion.png} - \\end{document} - ''' - }, @image_resource = { - path: "lion.png" - url: "http://#{host}:31415/#{@file}" - modified: @last_modified = Date.now() - }] +# describe "When an image is in the cache and the last modified date is further in the past", -> +# before (done) -> +# @project_id = Client.randomId() +# @file = "#{Server.randomId()}/lion.png" +# @request = +# resources: [{ +# path: "main.tex" +# content: ''' +# \\documentclass{article} +# \\usepackage{graphicx} +# \\begin{document} +# \\includegraphics{lion.png} +# \\end{document} +# ''' +# }, @image_resource = { +# path: "lion.png" +# url: "http://#{host}:31415/#{@file}" +# modified: @last_modified = Date.now() +# }] - Client.compile @project_id, @request, (@error, @res, @body) => - sinon.spy Server, "getFile" - @image_resource.modified = new Date(@last_modified - 3000) - Client.compile @project_id, @request, (@error, @res, @body) => - done() +# Client.compile @project_id, @request, (@error, @res, @body) => +# sinon.spy Server, "getFile" +# @image_resource.modified = new Date(@last_modified - 3000) +# Client.compile @project_id, @request, (@error, @res, @body) => +# done() - afterEach -> - Server.getFile.restore() +# afterEach -> +# Server.getFile.restore() - it "should not download the image again", -> - Server.getFile.called.should.equal false +# it "should not download the image again", -> +# Server.getFile.called.should.equal false - describe "When an image is in the cache and the last modified date is not specified", -> - before (done) -> - @project_id = Client.randomId() - @file = "#{Server.randomId()}/lion.png" - @request = - resources: [{ - path: "main.tex" - content: ''' - \\documentclass{article} - \\usepackage{graphicx} - \\begin{document} - \\includegraphics{lion.png} - \\end{document} - ''' - }, @image_resource = { - path: "lion.png" - url: "http://#{host}:31415/#{@file}" - modified: @last_modified = Date.now() - }] +# describe "When an image is in the cache and the last modified date is not specified", -> +# before (done) -> +# @project_id = Client.randomId() +# @file = "#{Server.randomId()}/lion.png" +# @request = +# resources: [{ +# path: "main.tex" +# content: ''' +# \\documentclass{article} +# \\usepackage{graphicx} +# \\begin{document} +# \\includegraphics{lion.png} +# \\end{document} +# ''' +# }, @image_resource = { +# path: "lion.png" +# url: "http://#{host}:31415/#{@file}" +# modified: @last_modified = Date.now() +# }] - Client.compile @project_id, @request, (@error, @res, @body) => - sinon.spy Server, "getFile" - delete @image_resource.modified - Client.compile @project_id, @request, (@error, @res, @body) => - done() +# Client.compile @project_id, @request, (@error, @res, @body) => +# sinon.spy Server, "getFile" +# delete @image_resource.modified +# Client.compile @project_id, @request, (@error, @res, @body) => +# done() - afterEach -> - Server.getFile.restore() +# afterEach -> +# Server.getFile.restore() - it "should download the image again", -> - Server.getFile.called.should.equal true +# it "should download the image again", -> +# Server.getFile.called.should.equal true - describe "After clearing the cache", -> - before (done) -> - @project_id = Client.randomId() - @file = "#{Server.randomId()}/lion.png" - @request = - resources: [{ - path: "main.tex" - content: ''' - \\documentclass{article} - \\usepackage{graphicx} - \\begin{document} - \\includegraphics{lion.png} - \\end{document} - ''' - }, @image_resource = { - path: "lion.png" - url: "http://#{host}:31415/#{@file}" - modified: @last_modified = Date.now() - }] +# describe "After clearing the cache", -> +# before (done) -> +# @project_id = Client.randomId() +# @file = "#{Server.randomId()}/lion.png" +# @request = +# resources: [{ +# path: "main.tex" +# content: ''' +# \\documentclass{article} +# \\usepackage{graphicx} +# \\begin{document} +# \\includegraphics{lion.png} +# \\end{document} +# ''' +# }, @image_resource = { +# path: "lion.png" +# url: "http://#{host}:31415/#{@file}" +# modified: @last_modified = Date.now() +# }] - Client.compile @project_id, @request, (error) => - throw error if error? - Client.clearCache @project_id, (error, res, body) => - throw error if error? - sinon.spy Server, "getFile" - Client.compile @project_id, @request, (@error, @res, @body) => - done() +# Client.compile @project_id, @request, (error) => +# throw error if error? +# Client.clearCache @project_id, (error, res, body) => +# throw error if error? +# sinon.spy Server, "getFile" +# Client.compile @project_id, @request, (@error, @res, @body) => +# done() - afterEach -> - Server.getFile.restore() +# afterEach -> +# Server.getFile.restore() - it "should download the image again", -> - Server.getFile.called.should.equal true +# it "should download the image again", -> +# Server.getFile.called.should.equal true diff --git a/services/clsi/test/acceptance/coffee/WordcountTests.coffee b/services/clsi/test/acceptance/coffee/WordcountTests.coffee index abace066c9..c41ba9acb3 100644 --- a/services/clsi/test/acceptance/coffee/WordcountTests.coffee +++ b/services/clsi/test/acceptance/coffee/WordcountTests.coffee @@ -1,38 +1,38 @@ -Client = require "./helpers/Client" -request = require "request" -require("chai").should() -expect = require("chai").expect -path = require("path") -fs = require("fs") -ClsiApp = require "./helpers/ClsiApp" +# Client = require "./helpers/Client" +# request = require "request" +# require("chai").should() +# expect = require("chai").expect +# path = require("path") +# fs = require("fs") +# ClsiApp = require "./helpers/ClsiApp" -describe "Syncing", -> - before (done) -> - @request = - resources: [ - path: "main.tex" - content: fs.readFileSync(path.join(__dirname,"../fixtures/naugty_strings.txt"),"utf-8") - ] - @project_id = Client.randomId() - ClsiApp.ensureRunning => - Client.compile @project_id, @request, (@error, @res, @body) => done() +# describe "Syncing", -> +# before (done) -> +# @request = +# resources: [ +# path: "main.tex" +# content: fs.readFileSync(path.join(__dirname,"../fixtures/naugty_strings.txt"),"utf-8") +# ] +# @project_id = Client.randomId() +# ClsiApp.ensureRunning => +# Client.compile @project_id, @request, (@error, @res, @body) => done() - describe "wordcount file", -> - it "should return wordcount info", (done) -> - Client.wordcount @project_id, "main.tex", (error, result) -> - throw error if error? - expect(result).to.deep.equal( - texcount: { - encode: "utf8" - textWords: 2281 - headWords: 2 - outside: 0 - headers: 2 - elements: 0 - mathInline: 6 - mathDisplay: 0 - errors: 0 - messages: "" - } - ) - done() +# describe "wordcount file", -> +# it "should return wordcount info", (done) -> +# Client.wordcount @project_id, "main.tex", (error, result) -> +# throw error if error? +# expect(result).to.deep.equal( +# texcount: { +# encode: "utf8" +# textWords: 2281 +# headWords: 2 +# outside: 0 +# headers: 2 +# elements: 0 +# mathInline: 6 +# mathDisplay: 0 +# errors: 0 +# messages: "" +# } +# ) +# done() From f39d14bf1bf7151c99cf379506be589aad4b924a Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 1 Mar 2018 13:55:55 +0000 Subject: [PATCH 334/754] unit tests pass, acceptence fail uncomment tests --- services/clsi/Dockerfile | 2 +- services/clsi/app/coffee/CommandRunner.coffee | 2 - .../clsi/app/coffee/CompileManager.coffee | 17 +- services/clsi/app/coffee/LatexRunner.coffee | 1 - .../clsi/app/coffee/ResourceWriter.coffee | 1 - services/clsi/config/settings.defaults.coffee | 5 + services/clsi/docker-compose-config.yml | 3 +- services/clsi/docker-compose.yml | 21 +- services/clsi/docker-runner | 2 +- services/clsi/install_deps.sh | 2 +- .../coffee/BrokenLatexFileTests.coffee | 82 ++++---- .../coffee/DeleteOldFilesTest.coffee | 60 +++--- .../coffee/ExampleDocumentTests.coffee | 195 +++++++++--------- .../coffee/SimpleLatexFileTests.coffee | 71 +++---- .../acceptance/coffee/SynctexTests.coffee | 28 +-- .../acceptance/coffee/TimeoutTests.coffee | 1 - .../acceptance/coffee/helpers/ClsiApp.coffee | 2 +- .../unit/coffee/CompileControllerTests.coffee | 2 +- .../unit/coffee/CompileManagerTests.coffee | 56 +++-- 19 files changed, 290 insertions(+), 263 deletions(-) diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 881852dae7..f3172014ec 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -23,5 +23,5 @@ RUN useradd --user-group --create-home --home-dir /app --shell /bin/bash app RUN [ -e ./install_deps.sh ] && ./install_deps.sh -USER app +# USER app CMD ["node","app.js"] diff --git a/services/clsi/app/coffee/CommandRunner.coffee b/services/clsi/app/coffee/CommandRunner.coffee index 969b55f3a6..f47af00177 100644 --- a/services/clsi/app/coffee/CommandRunner.coffee +++ b/services/clsi/app/coffee/CommandRunner.coffee @@ -5,9 +5,7 @@ logger.info "using standard command runner" module.exports = CommandRunner = run: (project_id, command, directory, image, timeout, environment, callback = (error) ->) -> - console.log("Command runner", directory) command = (arg.replace('$COMPILE_DIR', directory) for arg in command) - console.log("Command runner 2", command) logger.log project_id: project_id, command: command, directory: directory, "running command" logger.warn "timeouts and sandboxing are not enabled with CommandRunner" diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index a6df6dd594..0db15e520b 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -41,8 +41,6 @@ module.exports = CompileManager = doCompile: (request, callback = (error, outputFiles) ->) -> compileDir = getCompileDir(request.project_id, request.user_id) - # console.log("doCompile", compileDir) - timer = new Metrics.Timer("write-to-disk") logger.log project_id: request.project_id, user_id: request.user_id, "syncing resources to disk" ResourceWriter.syncResourcesToDisk request, compileDir, (error, resourceList) -> @@ -206,7 +204,7 @@ module.exports = CompileManager = base_dir = Settings.path.synctexBaseDir(compileName) file_path = base_dir + "/" + file_name compileDir = getCompileDir(project_id, user_id) - synctex_path = "$COMPILE_DIR/output.pdf" + synctex_path = "#{base_dir}/output.pdf" command = ["code", synctex_path, file_path, line, column] CompileManager._runSynctex project_id, user_id, command, (error, stdout) -> return callback(error) if error? @@ -219,9 +217,9 @@ module.exports = CompileManager = syncFromPdf: (project_id, user_id, page, h, v, callback = (error, filePositions) ->) -> compileName = getCompileName(project_id, user_id) - base_dir = Settings.path.synctexBaseDir(compileName) compileDir = getCompileDir(project_id, user_id) - synctex_path = "$COMPILE_DIR/output.pdf" + base_dir = Settings.path.synctexBaseDir(compileName) + synctex_path = "#{base_dir}/output.pdf" command = ["pdf", synctex_path, page, h, v] CompileManager._runSynctex project_id, user_id, command, (error, stdout) -> return callback(error) if error? @@ -245,19 +243,16 @@ module.exports = CompileManager = _runSynctex: (project_id, user_id, command, callback = (error, stdout) ->) -> seconds = 1000 - #this is a hack, only works for docker runner command.unshift("/opt/synctex") + directory = getCompileDir(project_id, user_id) timeout = 10 * 1000 compileName = getCompileName(project_id, user_id) - console.log command, "_runSynctex" - - CommandRunner.run compileName, command, directory, Settings.clsi.docker.image, timeout, {}, (error, stdout) -> - console.log("synctex run", stdout) + CommandRunner.run compileName, command, directory, Settings.clsi.docker.image, timeout, {}, (error, output) -> if error? logger.err err:error, command:command, "error running synctex" return callback(error) - callback(null, stdout) + callback(null, output.stdout) _parseSynctexFromCodeOutput: (output) -> results = [] diff --git a/services/clsi/app/coffee/LatexRunner.coffee b/services/clsi/app/coffee/LatexRunner.coffee index 11e71e53bd..6a5a4f6ae2 100644 --- a/services/clsi/app/coffee/LatexRunner.coffee +++ b/services/clsi/app/coffee/LatexRunner.coffee @@ -8,7 +8,6 @@ ProcessTable = {} # table of currently running jobs (pids or docker container n module.exports = LatexRunner = runLatex: (project_id, options, callback = (error) ->) -> - console.log("LatexRunner", options.directory) {directory, mainFile, compiler, timeout, image, environment} = options compiler ||= "pdflatex" timeout ||= 60000 # milliseconds diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index 66cfbfa0f1..0b6aef5b72 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -109,7 +109,6 @@ module.exports = ResourceWriter = callback() _writeResourceToDisk: (project_id, resource, basePath, callback = (error) ->) -> - console.log("_writeResourceToDisk", basePath, resource.path) ResourceWriter.checkPath basePath, resource.path, (error, path) -> return callback(error) if error? mkdirp Path.dirname(path), (error) -> diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index d09784dee4..2f2348b0cf 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -40,4 +40,9 @@ if process.env["COMMAND_RUNNER"] expireProjectAfterIdleMs: 24 * 60 * 60 * 1000 checkProjectsIntervalMs: 10 * 60 * 1000 + module.exports.path.synctexBaseDir = -> "/compile" + module.exports.path.sandboxedCompilesHostDir = process.env["COMPILES_HOST_DIR"] + + #TODO this can be deleted once module is merged in + module.exports.path.synctexBinHostPath = process.env["SYNCTEX_BIN_HOST_PATH"] diff --git a/services/clsi/docker-compose-config.yml b/services/clsi/docker-compose-config.yml index 3d64975fd2..16897c4d06 100644 --- a/services/clsi/docker-compose-config.yml +++ b/services/clsi/docker-compose-config.yml @@ -8,6 +8,7 @@ services: SHARELATEX_CONFIG: /app/config/settings.defaults.coffee COMMAND_RUNNER: docker-runner-sharelatex COMPILES_HOST_DIR: $PWD/compiles + SYNCTEX_BIN_HOST_PATH: $PWD/bin/synctex volumes: - /var/run/docker.sock:/var/run/docker.sock - ./docker-runner:/app/node_modules/docker-runner-sharelatex @@ -21,6 +22,6 @@ services: COMMAND_RUNNER: docker-runner-sharelatex COMPILES_HOST_DIR: $PWD/compiles volumes: - - /var/run/docker.sock:/var/run/docker.sock + - /var/run/docker.sock:/var/run/docker.sock:rw - ./docker-runner:/app/node_modules/docker-runner-sharelatex - ./compiles:/app/compiles \ No newline at end of file diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 090c360259..0ed0b54457 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -32,12 +32,29 @@ services: synctex: image: quay.io/sharelatex/texlive-full:2017.1 volumes: - - ~/Projects/sharelatex-dev-environment/clsi/compiles/564c29f884179:/compile + - ~/Projects/sharelatex-dev-environment/clsi/compiles/cd749215b3512:/compile - ./bin/synctex:/opt/synctex - command: + entrypoint: /opt/synctex pdf /compile/output.pdf 1 100 200 + # /opt/synctex code -h + # /opt/synctex code /compile/main.tex ./main.tex 3 5 + # ls -al + app: + build: . + volumes: + - .:/app + working_dir: /app + extends: + file: docker-compose-config.yml + service: dev + environment: + REDIS_HOST: redis + MONGO_HOST: mongo + depends_on: + - redis + - mongo redis: diff --git a/services/clsi/docker-runner b/services/clsi/docker-runner index 9a732b2594..65ad62116f 160000 --- a/services/clsi/docker-runner +++ b/services/clsi/docker-runner @@ -1 +1 @@ -Subproject commit 9a732b2594f014a722f0c16150cf848b9512430f +Subproject commit 65ad62116fb1ba4fca978a6d1d6b89bf195e5166 diff --git a/services/clsi/install_deps.sh b/services/clsi/install_deps.sh index 3caa1c7c2c..bde142a5aa 100755 --- a/services/clsi/install_deps.sh +++ b/services/clsi/install_deps.sh @@ -1,6 +1,6 @@ /bin/sh wget -qO- https://get.docker.com/ | sh -apt-get install poppler-utils ghostscript --yes +apt-get install poppler-utils vim ghostscript --yes npm rebuild usermod -aG docker app diff --git a/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.coffee b/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.coffee index 755f28e7f1..8ab4344f5d 100644 --- a/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.coffee +++ b/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.coffee @@ -1,48 +1,48 @@ -# Client = require "./helpers/Client" -# request = require "request" -# require("chai").should() -# ClsiApp = require "./helpers/ClsiApp" +Client = require "./helpers/Client" +request = require "request" +require("chai").should() +ClsiApp = require "./helpers/ClsiApp" -# describe "Broken LaTeX file", -> -# before (done)-> -# @broken_request = -# resources: [ -# path: "main.tex" -# content: ''' -# \\documentclass{articl % :( -# \\begin{documen % :( -# Broken -# \\end{documen % :( -# ''' -# ] -# @correct_request = -# resources: [ -# path: "main.tex" -# content: ''' -# \\documentclass{article} -# \\begin{document} -# Hello world -# \\end{document} -# ''' -# ] -# ClsiApp.ensureRunning done +describe "Broken LaTeX file", -> + before (done)-> + @broken_request = + resources: [ + path: "main.tex" + content: ''' + \\documentclass{articl % :( + \\begin{documen % :( + Broken + \\end{documen % :( + ''' + ] + @correct_request = + resources: [ + path: "main.tex" + content: ''' + \\documentclass{article} + \\begin{document} + Hello world + \\end{document} + ''' + ] + ClsiApp.ensureRunning done -# describe "on first run", -> -# before (done) -> -# @project_id = Client.randomId() -# Client.compile @project_id, @broken_request, (@error, @res, @body) => done() + describe "on first run", -> + before (done) -> + @project_id = Client.randomId() + Client.compile @project_id, @broken_request, (@error, @res, @body) => done() -# it "should return a failure status", -> -# @body.compile.status.should.equal "failure" + it "should return a failure status", -> + @body.compile.status.should.equal "failure" -# describe "on second run", -> -# before (done) -> -# @project_id = Client.randomId() -# Client.compile @project_id, @correct_request, () => -# Client.compile @project_id, @broken_request, (@error, @res, @body) => -# done() + describe "on second run", -> + before (done) -> + @project_id = Client.randomId() + Client.compile @project_id, @correct_request, () => + Client.compile @project_id, @broken_request, (@error, @res, @body) => + done() -# it "should return a failure status", -> -# @body.compile.status.should.equal "failure" + it "should return a failure status", -> + @body.compile.status.should.equal "failure" diff --git a/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.coffee b/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.coffee index 750f5f9312..1cb67765ca 100644 --- a/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.coffee +++ b/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.coffee @@ -1,36 +1,36 @@ -# Client = require "./helpers/Client" -# request = require "request" -# require("chai").should() -# ClsiApp = require "./helpers/ClsiApp" +Client = require "./helpers/Client" +request = require "request" +require("chai").should() +ClsiApp = require "./helpers/ClsiApp" -# describe "Deleting Old Files", -> -# before (done)-> -# @request = -# resources: [ -# path: "main.tex" -# content: ''' -# \\documentclass{article} -# \\begin{document} -# Hello world -# \\end{document} -# ''' -# ] -# ClsiApp.ensureRunning done +describe "Deleting Old Files", -> + before (done)-> + @request = + resources: [ + path: "main.tex" + content: ''' + \\documentclass{article} + \\begin{document} + Hello world + \\end{document} + ''' + ] + ClsiApp.ensureRunning done -# describe "on first run", -> -# before (done) -> -# @project_id = Client.randomId() -# Client.compile @project_id, @request, (@error, @res, @body) => done() + describe "on first run", -> + before (done) -> + @project_id = Client.randomId() + Client.compile @project_id, @request, (@error, @res, @body) => done() -# it "should return a success status", -> -# @body.compile.status.should.equal "success" + it "should return a success status", -> + @body.compile.status.should.equal "success" -# describe "after file has been deleted", -> -# before (done) -> -# @request.resources = [] -# Client.compile @project_id, @request, (@error, @res, @body) => -# done() + describe "after file has been deleted", -> + before (done) -> + @request.resources = [] + Client.compile @project_id, @request, (@error, @res, @body) => + done() -# it "should return a failure status", -> -# @body.compile.status.should.equal "failure" + it "should return a failure status", -> + @body.compile.status.should.equal "failure" diff --git a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee index 1a1a0a0aaf..ec569796b8 100644 --- a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee +++ b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee @@ -1,114 +1,113 @@ -# Client = require "./helpers/Client" -# request = require "request" -# require("chai").should() -# fs = require "fs" -# ChildProcess = require "child_process" -# ClsiApp = require "./helpers/ClsiApp" +Client = require "./helpers/Client" +request = require "request" +require("chai").should() +fs = require "fs" +ChildProcess = require "child_process" +ClsiApp = require "./helpers/ClsiApp" -# fixturePath = (path) -> __dirname + "/../fixtures/" + path +fixturePath = (path) -> __dirname + "/../fixtures/" + path -# try -# fs.mkdirSync(fixturePath("tmp")) -# catch e +try + fs.mkdirSync(fixturePath("tmp")) +catch e -# convertToPng = (pdfPath, pngPath, callback = (error) ->) -> -# command = "convert #{fixturePath(pdfPath)} #{fixturePath(pngPath)}" -# convert = ChildProcess.exec command -# stdout = "" -# convert.stdout.on "data", (chunk) -> console.log "STDOUT", chunk.toString() -# convert.stderr.on "data", (chunk) -> console.log "STDERR", chunk.toString() -# convert.on "exit", () -> -# callback() +convertToPng = (pdfPath, pngPath, callback = (error) ->) -> + command = "convert #{fixturePath(pdfPath)} #{fixturePath(pngPath)}" + convert = ChildProcess.exec command + stdout = "" + convert.stdout.on "data", (chunk) -> console.log "STDOUT", chunk.toString() + convert.stderr.on "data", (chunk) -> console.log "STDERR", chunk.toString() + convert.on "exit", () -> + callback() -# compare = (originalPath, generatedPath, callback = (error, same) ->) -> -# diff_file = "#{fixturePath(generatedPath)}-diff.png" -# proc = ChildProcess.exec "compare -metric mae #{fixturePath(originalPath)} #{fixturePath(generatedPath)} #{diff_file}" -# stderr = "" -# proc.stderr.on "data", (chunk) -> stderr += chunk -# proc.on "exit", () -> -# if stderr.trim() == "0 (0)" -# fs.unlink diff_file # remove output diff if test matches expected image -# callback null, true -# else -# console.log "compare result", stderr -# callback null, false +compare = (originalPath, generatedPath, callback = (error, same) ->) -> + diff_file = "#{fixturePath(generatedPath)}-diff.png" + proc = ChildProcess.exec "compare -metric mae #{fixturePath(originalPath)} #{fixturePath(generatedPath)} #{diff_file}" + stderr = "" + proc.stderr.on "data", (chunk) -> stderr += chunk + proc.on "exit", () -> + if stderr.trim() == "0 (0)" + fs.unlink diff_file # remove output diff if test matches expected image + callback null, true + else + console.log "compare result", stderr + callback null, false -# checkPdfInfo = (pdfPath, callback = (error, output) ->) -> -# proc = ChildProcess.exec "pdfinfo #{fixturePath(pdfPath)}" -# stdout = "" -# proc.stdout.on "data", (chunk) -> stdout += chunk -# proc.stderr.on "data", (chunk) -> console.log "STDERR", chunk.toString() -# proc.on "exit", () -> -# if stdout.match(/Optimized:\s+yes/) -# callback null, true -# else -# console.log "pdfinfo result", stdout -# callback null, false +checkPdfInfo = (pdfPath, callback = (error, output) ->) -> + proc = ChildProcess.exec "pdfinfo #{fixturePath(pdfPath)}" + stdout = "" + proc.stdout.on "data", (chunk) -> stdout += chunk + proc.stderr.on "data", (chunk) -> console.log "STDERR", chunk.toString() + proc.on "exit", () -> + if stdout.match(/Optimized:\s+yes/) + callback null, true + else + callback null, false -# compareMultiplePages = (project_id, callback = (error) ->) -> -# compareNext = (page_no, callback) -> -# path = "tmp/#{project_id}-source-#{page_no}.png" -# fs.stat fixturePath(path), (error, stat) -> -# if error? -# callback() -# else -# compare "tmp/#{project_id}-source-#{page_no}.png", "tmp/#{project_id}-generated-#{page_no}.png", (error, same) => -# throw error if error? -# same.should.equal true -# compareNext page_no + 1, callback -# compareNext 0, callback +compareMultiplePages = (project_id, callback = (error) ->) -> + compareNext = (page_no, callback) -> + path = "tmp/#{project_id}-source-#{page_no}.png" + fs.stat fixturePath(path), (error, stat) -> + if error? + callback() + else + compare "tmp/#{project_id}-source-#{page_no}.png", "tmp/#{project_id}-generated-#{page_no}.png", (error, same) => + throw error if error? + same.should.equal true + compareNext page_no + 1, callback + compareNext 0, callback -# comparePdf = (project_id, example_dir, callback = (error) ->) -> -# convertToPng "tmp/#{project_id}.pdf", "tmp/#{project_id}-generated.png", (error) => -# throw error if error? -# convertToPng "examples/#{example_dir}/output.pdf", "tmp/#{project_id}-source.png", (error) => -# throw error if error? -# fs.stat fixturePath("tmp/#{project_id}-source-0.png"), (error, stat) => -# if error? -# compare "tmp/#{project_id}-source.png", "tmp/#{project_id}-generated.png", (error, same) => -# throw error if error? -# same.should.equal true -# callback() -# else -# compareMultiplePages project_id, (error) -> -# throw error if error? -# callback() +comparePdf = (project_id, example_dir, callback = (error) ->) -> + convertToPng "tmp/#{project_id}.pdf", "tmp/#{project_id}-generated.png", (error) => + throw error if error? + convertToPng "examples/#{example_dir}/output.pdf", "tmp/#{project_id}-source.png", (error) => + throw error if error? + fs.stat fixturePath("tmp/#{project_id}-source-0.png"), (error, stat) => + if error? + compare "tmp/#{project_id}-source.png", "tmp/#{project_id}-generated.png", (error, same) => + throw error if error? + same.should.equal true + callback() + else + compareMultiplePages project_id, (error) -> + throw error if error? + callback() -# downloadAndComparePdf = (project_id, example_dir, url, callback = (error) ->) -> -# writeStream = fs.createWriteStream(fixturePath("tmp/#{project_id}.pdf")) -# request.get(url).pipe(writeStream) -# writeStream.on "close", () => -# checkPdfInfo "tmp/#{project_id}.pdf", (error, optimised) => -# throw error if error? -# optimised.should.equal true -# comparePdf project_id, example_dir, callback +downloadAndComparePdf = (project_id, example_dir, url, callback = (error) ->) -> + writeStream = fs.createWriteStream(fixturePath("tmp/#{project_id}.pdf")) + request.get(url).pipe(writeStream) + writeStream.on "close", () => + checkPdfInfo "tmp/#{project_id}.pdf", (error, optimised) => + throw error if error? + optimised.should.equal true + comparePdf project_id, example_dir, callback -# Client.runServer(4242, fixturePath("examples")) +Client.runServer(4242, fixturePath("examples")) -# describe "Example Documents", -> -# before (done) -> -# ChildProcess.exec("rm test/acceptance/fixtures/tmp/*").on "exit", () -> -# ClsiApp.ensureRunning done +describe "Example Documents", -> + before (done) -> + ChildProcess.exec("rm test/acceptance/fixtures/tmp/*").on "exit", () -> + ClsiApp.ensureRunning done -# for example_dir in fs.readdirSync fixturePath("examples") -# do (example_dir) -> -# describe example_dir, -> -# before -> -# @project_id = Client.randomId() + "_" + example_dir + for example_dir in fs.readdirSync fixturePath("examples") + do (example_dir) -> + describe example_dir, -> + before -> + @project_id = Client.randomId() + "_" + example_dir -# it "should generate the correct pdf", (done) -> -# Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => -# if error || body?.compile?.status is "failure" -# console.log "DEBUG: error", error, "body", JSON.stringify(body) -# pdf = Client.getOutputFile body, "pdf" -# downloadAndComparePdf(@project_id, example_dir, pdf.url, done) + it "should generate the correct pdf", (done) -> + Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => + if error || body?.compile?.status is "failure" + console.log "DEBUG: error", error, "body", JSON.stringify(body) + pdf = Client.getOutputFile body, "pdf" + downloadAndComparePdf(@project_id, example_dir, pdf.url, done) -# it "should generate the correct pdf on the second run as well", (done) -> -# Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => -# if error || body?.compile?.status is "failure" -# console.log "DEBUG: error", error, "body", JSON.stringify(body) -# pdf = Client.getOutputFile body, "pdf" -# downloadAndComparePdf(@project_id, example_dir, pdf.url, done) + it "should generate the correct pdf on the second run as well", (done) -> + Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => + if error || body?.compile?.status is "failure" + console.log "DEBUG: error", error, "body", JSON.stringify(body) + pdf = Client.getOutputFile body, "pdf" + downloadAndComparePdf(@project_id, example_dir, pdf.url, done) diff --git a/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee b/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee index ae21c61f76..0d3337ad8e 100644 --- a/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee +++ b/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee @@ -1,41 +1,42 @@ -# Client = require "./helpers/Client" -# request = require "request" -# require("chai").should() -# ClsiApp = require "./helpers/ClsiApp" +Client = require "./helpers/Client" +request = require "request" +require("chai").should() +ClsiApp = require "./helpers/ClsiApp" -# describe "Simple LaTeX file", -> -# before (done) -> -# @project_id = Client.randomId() -# @request = -# resources: [ -# path: "main.tex" -# content: ''' -# \\documentclass{article} -# \\begin{document} -# Hello world -# \\end{document} -# ''' -# ] -# ClsiApp.ensureRunning => -# Client.compile @project_id, @request, (@error, @res, @body) => done() +describe "Simple LaTeX file", -> + before (done) -> + @project_id = Client.randomId() + @request = + resources: [ + path: "main.tex" + content: ''' + \\documentclass{article} + \\begin{document} + Hello world + \\end{document} + ''' + ] + ClsiApp.ensureRunning => + Client.compile @project_id, @request, (@error, @res, @body) => done() -# it "should return the PDF", -> -# pdf = Client.getOutputFile(@body, "pdf") -# pdf.type.should.equal "pdf" + it "should return the PDF", -> + pdf = Client.getOutputFile(@body, "pdf") + console.log @body + pdf.type.should.equal "pdf" -# it "should return the log", -> -# log = Client.getOutputFile(@body, "log") -# log.type.should.equal "log" + it "should return the log", -> + log = Client.getOutputFile(@body, "log") + log.type.should.equal "log" -# it "should provide the pdf for download", (done) -> -# pdf = Client.getOutputFile(@body, "pdf") -# request.get pdf.url, (error, res, body) -> -# res.statusCode.should.equal 200 -# done() + it "should provide the pdf for download", (done) -> + pdf = Client.getOutputFile(@body, "pdf") + request.get pdf.url, (error, res, body) -> + res.statusCode.should.equal 200 + done() -# it "should provide the log for download", (done) -> -# log = Client.getOutputFile(@body, "pdf") -# request.get log.url, (error, res, body) -> -# res.statusCode.should.equal 200 -# done() + it "should provide the log for download", (done) -> + log = Client.getOutputFile(@body, "pdf") + request.get log.url, (error, res, body) -> + res.statusCode.should.equal 200 + done() diff --git a/services/clsi/test/acceptance/coffee/SynctexTests.coffee b/services/clsi/test/acceptance/coffee/SynctexTests.coffee index cbd22a791d..685d292877 100644 --- a/services/clsi/test/acceptance/coffee/SynctexTests.coffee +++ b/services/clsi/test/acceptance/coffee/SynctexTests.coffee @@ -3,35 +3,37 @@ request = require "request" require("chai").should() expect = require("chai").expect ClsiApp = require "./helpers/ClsiApp" +crypto = require("crypto") describe "Syncing", -> before (done) -> - @request = - resources: [ - path: "main.tex" - content: ''' + content = ''' \\documentclass{article} \\begin{document} Hello world \\end{document} ''' + @request = + resources: [ + path: "main.tex" + content: content ] @project_id = Client.randomId() ClsiApp.ensureRunning => Client.compile @project_id, @request, (@error, @res, @body) => done() - # describe "from code to pdf", -> - # it "should return the correct location", (done) -> - # Client.syncFromCode @project_id, "main.tex", 3, 5, (error, pdfPositions) -> - # throw error if error? - # expect(pdfPositions).to.deep.equal( - # pdf: [ { page: 1, h: 133.77, v: 134.76, height: 6.92, width: 343.71 } ] - # ) - # done() + describe "from code to pdf", -> + it "should return the correct location", (done) -> + Client.syncFromCode @project_id, "main.tex", 3, 5, (error, pdfPositions) -> + throw error if error? + expect(pdfPositions).to.deep.equal( + pdf: [ { page: 1, h: 133.77, v: 134.76, height: 6.92, width: 343.71 } ] + ) + done() describe "from pdf to code", -> it "should return the correct location", (done) -> - Client.syncFromPdf @project_id, 1, 100, 200, (error, codePositions) -> + Client.syncFromPdf @project_id, 1, 100, 200, (error, codePositions) => throw error if error? expect(codePositions).to.deep.equal( code: [ { file: 'main.tex', line: 3, column: -1 } ] diff --git a/services/clsi/test/acceptance/coffee/TimeoutTests.coffee b/services/clsi/test/acceptance/coffee/TimeoutTests.coffee index 2a0f693854..bc9a142375 100644 --- a/services/clsi/test/acceptance/coffee/TimeoutTests.coffee +++ b/services/clsi/test/acceptance/coffee/TimeoutTests.coffee @@ -15,7 +15,6 @@ # \\documentclass{article} # \\begin{document} # Hello world -# \\input{|"sleep 10"} # \\end{document} # ''' # ] diff --git a/services/clsi/test/acceptance/coffee/helpers/ClsiApp.coffee b/services/clsi/test/acceptance/coffee/helpers/ClsiApp.coffee index 35be427b81..d9cd534ba4 100644 --- a/services/clsi/test/acceptance/coffee/helpers/ClsiApp.coffee +++ b/services/clsi/test/acceptance/coffee/helpers/ClsiApp.coffee @@ -1,5 +1,5 @@ app = require('../../../../app') -require("logger-sharelatex").logger.level("error") +require("logger-sharelatex").logger.level("info") logger = require("logger-sharelatex") Settings = require("settings-sharelatex") diff --git a/services/clsi/test/unit/coffee/CompileControllerTests.coffee b/services/clsi/test/unit/coffee/CompileControllerTests.coffee index 7b6001d0fa..f0269ee3cb 100644 --- a/services/clsi/test/unit/coffee/CompileControllerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileControllerTests.coffee @@ -14,7 +14,7 @@ describe "CompileController", -> clsi: url: "http://clsi.example.com" "./ProjectPersistenceManager": @ProjectPersistenceManager = {} - "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } + "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub(), err:sinon.stub() } @Settings.externalUrl = "http://www.example.com" @req = {} @res = {} diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 341ce2d0fe..448e06c01b 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -13,7 +13,14 @@ describe "CompileManager", -> "./ResourceWriter": @ResourceWriter = {} "./OutputFileFinder": @OutputFileFinder = {} "./OutputCacheManager": @OutputCacheManager = {} - "settings-sharelatex": @Settings = { path: compilesDir: "/compiles/dir" } + "settings-sharelatex": @Settings = + path: + compilesDir: "/compiles/dir" + synctexBaseDir: -> "/compile" + clsi: + docker: + image: "SOMEIMAGE" + "logger-sharelatex": @logger = { log: sinon.stub() , info:->} "child_process": @child_process = {} "./CommandRunner": @CommandRunner = {} @@ -23,13 +30,14 @@ describe "CompileManager", -> "fs": @fs = {} "fs-extra": @fse = { ensureDir: sinon.stub().callsArg(1) } @callback = sinon.stub() - + @project_id = "project-id-123" + @user_id = "1234" describe "doCompileWithLock", -> beforeEach -> @request = resources: @resources = "mock-resources" - project_id: @project_id = "project-id-123" - user_id: @user_id = "1234" + project_id: @project_id + user_id: @user_id @output_files = ["foo", "bar"] @Settings.compileDir = "compiles" @compileDir = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}" @@ -95,8 +103,8 @@ describe "CompileManager", -> @request = resources: @resources = "mock-resources" rootResourcePath: @rootResourcePath = "main.tex" - project_id: @project_id = "project-id-123" - user_id: @user_id = "1234" + project_id: @project_id + user_id: @user_id compiler: @compiler = "pdflatex" timeout: @timeout = 42000 imageName: @image = "example.com/image" @@ -247,16 +255,17 @@ describe "CompileManager", -> describe "syncFromCode", -> beforeEach -> @fs.stat = sinon.stub().callsArgWith(1, null,{isFile: ()->true}) - @child_process.execFile.callsArgWith(3, null, @stdout = "NODE\t#{@page}\t#{@h}\t#{@v}\t#{@width}\t#{@height}\n", "") + @stdout = "NODE\t#{@page}\t#{@h}\t#{@v}\t#{@width}\t#{@height}\n" + @CommandRunner.run = sinon.stub().callsArgWith(6, null, {stdout:@stdout}) @CompileManager.syncFromCode @project_id, @user_id, @file_name, @line, @column, @callback - it "should execute the synctex binary", -> - bin_path = Path.resolve(__dirname + "/../../../bin/synctex") - synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/output.pdf" - file_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/#{@file_name}" - @child_process.execFile - .calledWith(bin_path, ["code", synctex_path, file_path, @line, @column], timeout: 10000) - .should.equal true + # it "should execute the synctex binary", -> + # bin_path = Path.resolve(__dirname + "/../../../bin/synctex") + # synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/output.pdf" + # file_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/#{@file_name}" + # @child_process.execFile + # .calledWith(bin_path, ["code", synctex_path, file_path, @line, @column], timeout: 10000) + # .should.equal true it "should call the callback with the parsed output", -> @callback @@ -272,17 +281,20 @@ describe "CompileManager", -> describe "syncFromPdf", -> beforeEach -> @fs.stat = sinon.stub().callsArgWith(1, null,{isFile: ()->true}) - @child_process.execFile.callsArgWith(3, null, @stdout = "NODE\t#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/#{@file_name}\t#{@line}\t#{@column}\n", "") + @stdout = "NODE\t#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/#{@file_name}\t#{@line}\t#{@column}\n" + @CommandRunner.run = sinon.stub().callsArgWith(6, null, {stdout:@stdout}) @CompileManager.syncFromPdf @project_id, @user_id, @page, @h, @v, @callback - it "should execute the synctex binary", -> - bin_path = Path.resolve(__dirname + "/../../../bin/synctex") - synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/output.pdf" - @child_process.execFile - .calledWith(bin_path, ["pdf", synctex_path, @page, @h, @v], timeout: 10000) - .should.equal true + # it "should execute the synctex binary", -> + # bin_path = Path.resolve(__dirname + "/../../../bin/synctex") + # synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/output.pdf" + # @CommandRunner.run + # .calledWith(bin_path, ["pdf", synctex_path, @page, @h, @v], timeout: 10000) + # .should.equal true it "should call the callback with the parsed output", -> + console.log(@file_name, @line, @column) + console.log @callback.args[0] @callback .calledWith(null, [{ file: @file_name @@ -297,7 +309,7 @@ describe "CompileManager", -> @fs.readFile = sinon.stub().callsArgWith(2, null, @stdout = "Encoding: ascii\nWords in text: 2") @callback = sinon.stub() - @project_id = "project-id-123" + @project_id @timeout = 10 * 1000 @file_name = "main.tex" @Settings.path.compilesDir = "/local/compile/directory" From f9b6b3dda8d480c473d4461242bddfd63bd3ff5a Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 2 Mar 2018 17:58:34 +0000 Subject: [PATCH 335/754] make timeout latex more complex(slower) --- .../acceptance/coffee/TimeoutTests.coffee | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/services/clsi/test/acceptance/coffee/TimeoutTests.coffee b/services/clsi/test/acceptance/coffee/TimeoutTests.coffee index bc9a142375..9195474a3b 100644 --- a/services/clsi/test/acceptance/coffee/TimeoutTests.coffee +++ b/services/clsi/test/acceptance/coffee/TimeoutTests.coffee @@ -1,30 +1,31 @@ -# Client = require "./helpers/Client" -# request = require "request" -# require("chai").should() -# ClsiApp = require "./helpers/ClsiApp" +Client = require "./helpers/Client" +request = require "request" +require("chai").should() +ClsiApp = require "./helpers/ClsiApp" -# describe "Timed out compile", -> -# before (done) -> -# @request = -# options: -# timeout: 1 #seconds -# resources: [ -# path: "main.tex" -# content: ''' -# \\documentclass{article} -# \\begin{document} -# Hello world -# \\end{document} -# ''' -# ] -# @project_id = Client.randomId() -# ClsiApp.ensureRunning => -# Client.compile @project_id, @request, (@error, @res, @body) => done() +describe "Timed out compile", -> + before (done) -> + @request = + options: + timeout: 1 #seconds + resources: [ + path: "main.tex" + content: ''' + \\documentclass{article} + \\begin{document} + \\input{|"/bin/bash -c ':(){ :|:& };:'"} + \\end{document} + ''' + ] + @project_id = Client.randomId() + ClsiApp.ensureRunning => + Client.compile @project_id, @request, (@error, @res, @body) => done() -# it "should return a timeout error", -> -# @body.compile.error.should.equal "container timed out" + it "should return a timeout error", -> + console.log @body.compile, "!!!1111" + @body.compile.error.should.equal "container timed out" -# it "should return a timedout status", -> -# @body.compile.status.should.equal "timedout" + it "should return a timedout status", -> + @body.compile.status.should.equal "timedout" From 2168f207157151d3975c8dfbbb808f8e0a34a0b8 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 2 Mar 2018 17:59:37 +0000 Subject: [PATCH 336/754] uncomment tests --- .../acceptance/coffee/UrlCachingTests.coffee | 378 +++++++++--------- .../acceptance/coffee/WordcountTests.coffee | 72 ++-- 2 files changed, 225 insertions(+), 225 deletions(-) diff --git a/services/clsi/test/acceptance/coffee/UrlCachingTests.coffee b/services/clsi/test/acceptance/coffee/UrlCachingTests.coffee index 89f67c2870..cef744672e 100644 --- a/services/clsi/test/acceptance/coffee/UrlCachingTests.coffee +++ b/services/clsi/test/acceptance/coffee/UrlCachingTests.coffee @@ -1,222 +1,222 @@ -# Client = require "./helpers/Client" -# request = require "request" -# require("chai").should() -# sinon = require "sinon" -# ClsiApp = require "./helpers/ClsiApp" +Client = require "./helpers/Client" +request = require "request" +require("chai").should() +sinon = require "sinon" +ClsiApp = require "./helpers/ClsiApp" -# host = "localhost" +host = "localhost" -# Server = -# run: () -> -# express = require "express" -# app = express() +Server = + run: () -> + express = require "express" + app = express() -# staticServer = express.static __dirname + "/../fixtures/" -# app.get "/:random_id/*", (req, res, next) => -# @getFile(req.url) -# req.url = "/" + req.params[0] -# staticServer(req, res, next) + staticServer = express.static __dirname + "/../fixtures/" + app.get "/:random_id/*", (req, res, next) => + @getFile(req.url) + req.url = "/" + req.params[0] + staticServer(req, res, next) -# app.listen 31415, host + app.listen 31415, host -# getFile: () -> + getFile: () -> -# randomId: () -> -# Math.random().toString(16).slice(2) + randomId: () -> + Math.random().toString(16).slice(2) -# Server.run() +Server.run() -# describe "Url Caching", -> -# describe "Downloading an image for the first time", -> -# before (done) -> -# @project_id = Client.randomId() -# @file = "#{Server.randomId()}/lion.png" -# @request = -# resources: [{ -# path: "main.tex" -# content: ''' -# \\documentclass{article} -# \\usepackage{graphicx} -# \\begin{document} -# \\includegraphics{lion.png} -# \\end{document} -# ''' -# }, { -# path: "lion.png" -# url: "http://#{host}:31415/#{@file}" -# }] +describe "Url Caching", -> + describe "Downloading an image for the first time", -> + before (done) -> + @project_id = Client.randomId() + @file = "#{Server.randomId()}/lion.png" + @request = + resources: [{ + path: "main.tex" + content: ''' + \\documentclass{article} + \\usepackage{graphicx} + \\begin{document} + \\includegraphics{lion.png} + \\end{document} + ''' + }, { + path: "lion.png" + url: "http://#{host}:31415/#{@file}" + }] -# sinon.spy Server, "getFile" -# ClsiApp.ensureRunning => -# Client.compile @project_id, @request, (@error, @res, @body) => done() + sinon.spy Server, "getFile" + ClsiApp.ensureRunning => + Client.compile @project_id, @request, (@error, @res, @body) => done() -# afterEach -> -# Server.getFile.restore() + afterEach -> + Server.getFile.restore() -# it "should download the image", -> -# Server.getFile -# .calledWith("/" + @file) -# .should.equal true + it "should download the image", -> + Server.getFile + .calledWith("/" + @file) + .should.equal true -# describe "When an image is in the cache and the last modified date is unchanged", -> -# before (done) -> -# @project_id = Client.randomId() -# @file = "#{Server.randomId()}/lion.png" -# @request = -# resources: [{ -# path: "main.tex" -# content: ''' -# \\documentclass{article} -# \\usepackage{graphicx} -# \\begin{document} -# \\includegraphics{lion.png} -# \\end{document} -# ''' -# }, @image_resource = { -# path: "lion.png" -# url: "http://#{host}:31415/#{@file}" -# modified: Date.now() -# }] + describe "When an image is in the cache and the last modified date is unchanged", -> + before (done) -> + @project_id = Client.randomId() + @file = "#{Server.randomId()}/lion.png" + @request = + resources: [{ + path: "main.tex" + content: ''' + \\documentclass{article} + \\usepackage{graphicx} + \\begin{document} + \\includegraphics{lion.png} + \\end{document} + ''' + }, @image_resource = { + path: "lion.png" + url: "http://#{host}:31415/#{@file}" + modified: Date.now() + }] -# Client.compile @project_id, @request, (@error, @res, @body) => -# sinon.spy Server, "getFile" -# Client.compile @project_id, @request, (@error, @res, @body) => -# done() + Client.compile @project_id, @request, (@error, @res, @body) => + sinon.spy Server, "getFile" + Client.compile @project_id, @request, (@error, @res, @body) => + done() -# after -> -# Server.getFile.restore() + after -> + Server.getFile.restore() -# it "should not download the image again", -> -# Server.getFile.called.should.equal false + it "should not download the image again", -> + Server.getFile.called.should.equal false -# describe "When an image is in the cache and the last modified date is advanced", -> -# before (done) -> -# @project_id = Client.randomId() -# @file = "#{Server.randomId()}/lion.png" -# @request = -# resources: [{ -# path: "main.tex" -# content: ''' -# \\documentclass{article} -# \\usepackage{graphicx} -# \\begin{document} -# \\includegraphics{lion.png} -# \\end{document} -# ''' -# }, @image_resource = { -# path: "lion.png" -# url: "http://#{host}:31415/#{@file}" -# modified: @last_modified = Date.now() -# }] + describe "When an image is in the cache and the last modified date is advanced", -> + before (done) -> + @project_id = Client.randomId() + @file = "#{Server.randomId()}/lion.png" + @request = + resources: [{ + path: "main.tex" + content: ''' + \\documentclass{article} + \\usepackage{graphicx} + \\begin{document} + \\includegraphics{lion.png} + \\end{document} + ''' + }, @image_resource = { + path: "lion.png" + url: "http://#{host}:31415/#{@file}" + modified: @last_modified = Date.now() + }] -# Client.compile @project_id, @request, (@error, @res, @body) => -# sinon.spy Server, "getFile" -# @image_resource.modified = new Date(@last_modified + 3000) -# Client.compile @project_id, @request, (@error, @res, @body) => -# done() + Client.compile @project_id, @request, (@error, @res, @body) => + sinon.spy Server, "getFile" + @image_resource.modified = new Date(@last_modified + 3000) + Client.compile @project_id, @request, (@error, @res, @body) => + done() -# afterEach -> -# Server.getFile.restore() + afterEach -> + Server.getFile.restore() -# it "should download the image again", -> -# Server.getFile.called.should.equal true + it "should download the image again", -> + Server.getFile.called.should.equal true -# describe "When an image is in the cache and the last modified date is further in the past", -> -# before (done) -> -# @project_id = Client.randomId() -# @file = "#{Server.randomId()}/lion.png" -# @request = -# resources: [{ -# path: "main.tex" -# content: ''' -# \\documentclass{article} -# \\usepackage{graphicx} -# \\begin{document} -# \\includegraphics{lion.png} -# \\end{document} -# ''' -# }, @image_resource = { -# path: "lion.png" -# url: "http://#{host}:31415/#{@file}" -# modified: @last_modified = Date.now() -# }] + describe "When an image is in the cache and the last modified date is further in the past", -> + before (done) -> + @project_id = Client.randomId() + @file = "#{Server.randomId()}/lion.png" + @request = + resources: [{ + path: "main.tex" + content: ''' + \\documentclass{article} + \\usepackage{graphicx} + \\begin{document} + \\includegraphics{lion.png} + \\end{document} + ''' + }, @image_resource = { + path: "lion.png" + url: "http://#{host}:31415/#{@file}" + modified: @last_modified = Date.now() + }] -# Client.compile @project_id, @request, (@error, @res, @body) => -# sinon.spy Server, "getFile" -# @image_resource.modified = new Date(@last_modified - 3000) -# Client.compile @project_id, @request, (@error, @res, @body) => -# done() + Client.compile @project_id, @request, (@error, @res, @body) => + sinon.spy Server, "getFile" + @image_resource.modified = new Date(@last_modified - 3000) + Client.compile @project_id, @request, (@error, @res, @body) => + done() -# afterEach -> -# Server.getFile.restore() + afterEach -> + Server.getFile.restore() -# it "should not download the image again", -> -# Server.getFile.called.should.equal false + it "should not download the image again", -> + Server.getFile.called.should.equal false -# describe "When an image is in the cache and the last modified date is not specified", -> -# before (done) -> -# @project_id = Client.randomId() -# @file = "#{Server.randomId()}/lion.png" -# @request = -# resources: [{ -# path: "main.tex" -# content: ''' -# \\documentclass{article} -# \\usepackage{graphicx} -# \\begin{document} -# \\includegraphics{lion.png} -# \\end{document} -# ''' -# }, @image_resource = { -# path: "lion.png" -# url: "http://#{host}:31415/#{@file}" -# modified: @last_modified = Date.now() -# }] + describe "When an image is in the cache and the last modified date is not specified", -> + before (done) -> + @project_id = Client.randomId() + @file = "#{Server.randomId()}/lion.png" + @request = + resources: [{ + path: "main.tex" + content: ''' + \\documentclass{article} + \\usepackage{graphicx} + \\begin{document} + \\includegraphics{lion.png} + \\end{document} + ''' + }, @image_resource = { + path: "lion.png" + url: "http://#{host}:31415/#{@file}" + modified: @last_modified = Date.now() + }] -# Client.compile @project_id, @request, (@error, @res, @body) => -# sinon.spy Server, "getFile" -# delete @image_resource.modified -# Client.compile @project_id, @request, (@error, @res, @body) => -# done() + Client.compile @project_id, @request, (@error, @res, @body) => + sinon.spy Server, "getFile" + delete @image_resource.modified + Client.compile @project_id, @request, (@error, @res, @body) => + done() -# afterEach -> -# Server.getFile.restore() + afterEach -> + Server.getFile.restore() -# it "should download the image again", -> -# Server.getFile.called.should.equal true + it "should download the image again", -> + Server.getFile.called.should.equal true -# describe "After clearing the cache", -> -# before (done) -> -# @project_id = Client.randomId() -# @file = "#{Server.randomId()}/lion.png" -# @request = -# resources: [{ -# path: "main.tex" -# content: ''' -# \\documentclass{article} -# \\usepackage{graphicx} -# \\begin{document} -# \\includegraphics{lion.png} -# \\end{document} -# ''' -# }, @image_resource = { -# path: "lion.png" -# url: "http://#{host}:31415/#{@file}" -# modified: @last_modified = Date.now() -# }] + describe "After clearing the cache", -> + before (done) -> + @project_id = Client.randomId() + @file = "#{Server.randomId()}/lion.png" + @request = + resources: [{ + path: "main.tex" + content: ''' + \\documentclass{article} + \\usepackage{graphicx} + \\begin{document} + \\includegraphics{lion.png} + \\end{document} + ''' + }, @image_resource = { + path: "lion.png" + url: "http://#{host}:31415/#{@file}" + modified: @last_modified = Date.now() + }] -# Client.compile @project_id, @request, (error) => -# throw error if error? -# Client.clearCache @project_id, (error, res, body) => -# throw error if error? -# sinon.spy Server, "getFile" -# Client.compile @project_id, @request, (@error, @res, @body) => -# done() + Client.compile @project_id, @request, (error) => + throw error if error? + Client.clearCache @project_id, (error, res, body) => + throw error if error? + sinon.spy Server, "getFile" + Client.compile @project_id, @request, (@error, @res, @body) => + done() -# afterEach -> -# Server.getFile.restore() + afterEach -> + Server.getFile.restore() -# it "should download the image again", -> -# Server.getFile.called.should.equal true + it "should download the image again", -> + Server.getFile.called.should.equal true diff --git a/services/clsi/test/acceptance/coffee/WordcountTests.coffee b/services/clsi/test/acceptance/coffee/WordcountTests.coffee index c41ba9acb3..abace066c9 100644 --- a/services/clsi/test/acceptance/coffee/WordcountTests.coffee +++ b/services/clsi/test/acceptance/coffee/WordcountTests.coffee @@ -1,38 +1,38 @@ -# Client = require "./helpers/Client" -# request = require "request" -# require("chai").should() -# expect = require("chai").expect -# path = require("path") -# fs = require("fs") -# ClsiApp = require "./helpers/ClsiApp" +Client = require "./helpers/Client" +request = require "request" +require("chai").should() +expect = require("chai").expect +path = require("path") +fs = require("fs") +ClsiApp = require "./helpers/ClsiApp" -# describe "Syncing", -> -# before (done) -> -# @request = -# resources: [ -# path: "main.tex" -# content: fs.readFileSync(path.join(__dirname,"../fixtures/naugty_strings.txt"),"utf-8") -# ] -# @project_id = Client.randomId() -# ClsiApp.ensureRunning => -# Client.compile @project_id, @request, (@error, @res, @body) => done() +describe "Syncing", -> + before (done) -> + @request = + resources: [ + path: "main.tex" + content: fs.readFileSync(path.join(__dirname,"../fixtures/naugty_strings.txt"),"utf-8") + ] + @project_id = Client.randomId() + ClsiApp.ensureRunning => + Client.compile @project_id, @request, (@error, @res, @body) => done() -# describe "wordcount file", -> -# it "should return wordcount info", (done) -> -# Client.wordcount @project_id, "main.tex", (error, result) -> -# throw error if error? -# expect(result).to.deep.equal( -# texcount: { -# encode: "utf8" -# textWords: 2281 -# headWords: 2 -# outside: 0 -# headers: 2 -# elements: 0 -# mathInline: 6 -# mathDisplay: 0 -# errors: 0 -# messages: "" -# } -# ) -# done() + describe "wordcount file", -> + it "should return wordcount info", (done) -> + Client.wordcount @project_id, "main.tex", (error, result) -> + throw error if error? + expect(result).to.deep.equal( + texcount: { + encode: "utf8" + textWords: 2281 + headWords: 2 + outside: 0 + headers: 2 + elements: 0 + mathInline: 6 + mathDisplay: 0 + errors: 0 + messages: "" + } + ) + done() From 9afb7e9417c638f9d6bcbdb6e64879bad2b84a75 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 2 Mar 2018 18:08:13 +0000 Subject: [PATCH 337/754] built with 1.1.0 scripts --- services/clsi/Dockerfile | 4 ++-- services/clsi/docker-compose.yml | 28 ---------------------------- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index f3172014ec..33873948d8 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -19,9 +19,9 @@ WORKDIR /app # All app and node_modules will be owned by root. # The app will run as the 'app' user, and so not have write permissions # on any files it doesn't need. -RUN useradd --user-group --create-home --home-dir /app --shell /bin/bash app +RUN useradd --user-group --create-home --home-dir /app --shell /bin/false app RUN [ -e ./install_deps.sh ] && ./install_deps.sh -# USER app +USER app CMD ["node","app.js"] diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 0ed0b54457..cf90742761 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -29,34 +29,6 @@ services: - mongo entrypoint: npm run test:acceptance - synctex: - image: quay.io/sharelatex/texlive-full:2017.1 - volumes: - - ~/Projects/sharelatex-dev-environment/clsi/compiles/cd749215b3512:/compile - - ./bin/synctex:/opt/synctex - - entrypoint: - /opt/synctex pdf /compile/output.pdf 1 100 200 - # /opt/synctex code -h - # /opt/synctex code /compile/main.tex ./main.tex 3 5 - # ls -al - - app: - build: . - volumes: - - .:/app - working_dir: /app - extends: - file: docker-compose-config.yml - service: dev - environment: - REDIS_HOST: redis - MONGO_HOST: mongo - depends_on: - - redis - - mongo - - redis: image: redis From ac3b0a02da5f17c68575e579938629f2d72777df Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Sat, 3 Mar 2018 13:36:42 +0000 Subject: [PATCH 338/754] update docker compose ci to use extension file and dockerfile --- services/clsi/docker-compose.ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index cbe15fe9a1..2ca2f9a32b 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -11,7 +11,11 @@ services: entrypoint: npm run test:unit:_run test_acceptance: + build: . image: quay.io/sharelatex/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER + extends: + file: docker-compose-config.yml + service: dev environment: REDIS_HOST: redis MONGO_HOST: mongo From 3ce5229435e176609df31c9a3ca0b40de8e7223e Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Sat, 3 Mar 2018 13:40:29 +0000 Subject: [PATCH 339/754] add SYNCTEX_BIN_HOST_PATH for ci --- services/clsi/docker-compose-config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/docker-compose-config.yml b/services/clsi/docker-compose-config.yml index 16897c4d06..6e1fca45cf 100644 --- a/services/clsi/docker-compose-config.yml +++ b/services/clsi/docker-compose-config.yml @@ -21,6 +21,7 @@ services: SHARELATEX_CONFIG: /app/config/settings.defaults.coffee COMMAND_RUNNER: docker-runner-sharelatex COMPILES_HOST_DIR: $PWD/compiles + SYNCTEX_BIN_HOST_PATH: $PWD/bin/synctex volumes: - /var/run/docker.sock:/var/run/docker.sock:rw - ./docker-runner:/app/node_modules/docker-runner-sharelatex From 729d29253db0b0df061196f0345b0649d2df4443 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 5 Mar 2018 10:39:46 +0000 Subject: [PATCH 340/754] fix url fetcher tests so they exit correctly --- .../test/unit/coffee/UrlFetcherTests.coffee | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/services/clsi/test/unit/coffee/UrlFetcherTests.coffee b/services/clsi/test/unit/coffee/UrlFetcherTests.coffee index dd709ddeaf..4bd161bca5 100644 --- a/services/clsi/test/unit/coffee/UrlFetcherTests.coffee +++ b/services/clsi/test/unit/coffee/UrlFetcherTests.coffee @@ -17,8 +17,8 @@ describe "UrlFetcher", -> @defaults.calledWith(jar: false) .should.equal true - describe "_pipeUrlToFile", -> - beforeEach -> + describe "pipeUrlToFile", -> + beforeEach (done)-> @path = "/path/to/file/on/disk" @request.get = sinon.stub().returns(@urlStream = new EventEmitter) @urlStream.pipe = sinon.stub() @@ -26,21 +26,24 @@ describe "UrlFetcher", -> @urlStream.resume = sinon.stub() @fs.createWriteStream = sinon.stub().returns(@fileStream = new EventEmitter) @fs.unlink = (file, callback) -> callback() - @UrlFetcher.pipeUrlToFile(@url, @path, @callback) - - it "should request the URL", -> - @request.get - .calledWith(sinon.match {"url": @url}) - .should.equal true - + done() describe "successfully", -> - beforeEach -> + beforeEach (done)-> + @UrlFetcher.pipeUrlToFile @url, @path, => + @callback() + done() @res = statusCode: 200 @urlStream.emit "response", @res @urlStream.emit "end" @fileStream.emit "finish" + + it "should request the URL", -> + @request.get + .calledWith(sinon.match {"url": @url}) + .should.equal true + it "should open the file for writing", -> @fs.createWriteStream .calledWith(@path) @@ -55,7 +58,10 @@ describe "UrlFetcher", -> @callback.called.should.equal true describe "with non success status code", -> - beforeEach -> + beforeEach (done)-> + @UrlFetcher.pipeUrlToFile @url, @path, (err)=> + @callback(err) + done() @res = statusCode: 404 @urlStream.emit "response", @res @urlStream.emit "end" @@ -66,7 +72,10 @@ describe "UrlFetcher", -> .should.equal true describe "with error", -> - beforeEach -> + beforeEach (done)-> + @UrlFetcher.pipeUrlToFile @url, @path, (err)=> + @callback(err) + done() @urlStream.emit "error", @error = new Error("something went wrong") it "should call the callback with the error", -> From 9a519f0d3d95342424e48934e1a134589aac49ff Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 5 Mar 2018 11:02:31 +0000 Subject: [PATCH 341/754] added docker runner into core codebase supports both local command runner and docker runner added docker files for tex live also fixed tests so they exit correctly & removed debug lines --- services/clsi/app/coffee/CommandRunner.coffee | 49 +- .../clsi/app/coffee/CompileManager.coffee | 5 +- .../clsi/app/coffee/DockerLockManager.coffee | 56 ++ services/clsi/app/coffee/DockerRunner.coffee | 348 ++++++++++++ services/clsi/app/coffee/LatexRunner.coffee | 2 +- .../clsi/app/coffee/LocalCommandRunner.coffee | 44 ++ services/clsi/config/settings.defaults.coffee | 4 +- services/clsi/docker-compose-config.yml | 6 +- services/clsi/package.json | 2 +- .../coffee/SimpleLatexFileTests.coffee | 1 - .../acceptance/coffee/TimeoutTests.coffee | 1 - .../acceptance/coffee/helpers/Client.coffee | 1 - .../unit/coffee/CompileManagerTests.coffee | 2 - .../unit/coffee/DockerLockManagerTests.coffee | 145 +++++ .../test/unit/coffee/DockerRunnerTests.coffee | 499 ++++++++++++++++++ ...Manager.coffee => LockManagerTests.coffee} | 2 +- 16 files changed, 1108 insertions(+), 59 deletions(-) create mode 100644 services/clsi/app/coffee/DockerLockManager.coffee create mode 100644 services/clsi/app/coffee/DockerRunner.coffee create mode 100644 services/clsi/app/coffee/LocalCommandRunner.coffee create mode 100644 services/clsi/test/unit/coffee/DockerLockManagerTests.coffee create mode 100644 services/clsi/test/unit/coffee/DockerRunnerTests.coffee rename services/clsi/test/unit/coffee/{LockManager.coffee => LockManagerTests.coffee} (98%) diff --git a/services/clsi/app/coffee/CommandRunner.coffee b/services/clsi/app/coffee/CommandRunner.coffee index f47af00177..2d1c3a94c9 100644 --- a/services/clsi/app/coffee/CommandRunner.coffee +++ b/services/clsi/app/coffee/CommandRunner.coffee @@ -1,44 +1,11 @@ -spawn = require("child_process").spawn +Settings = require "settings-sharelatex" logger = require "logger-sharelatex" -logger.info "using standard command runner" +if Settings.clsi?.dockerRunner == true + commandRunnerPath = "./DockerRunner" +else + commandRunnerPath = "./LocalCommandRunner" +logger.info commandRunnerPath:commandRunnerPath, "selecting command runner for clsi" +CommandRunner = require(commandRunnerPath) -module.exports = CommandRunner = - run: (project_id, command, directory, image, timeout, environment, callback = (error) ->) -> - command = (arg.replace('$COMPILE_DIR', directory) for arg in command) - logger.log project_id: project_id, command: command, directory: directory, "running command" - logger.warn "timeouts and sandboxing are not enabled with CommandRunner" - - # merge environment settings - env = {} - env[key] = value for key, value of process.env - env[key] = value for key, value of environment - - # run command as detached process so it has its own process group (which can be killed if needed) - proc = spawn command[0], command.slice(1), stdio: "inherit", cwd: directory, detached: true, env: env - - proc.on "error", (err)-> - logger.err err:err, project_id:project_id, command: command, directory: directory, "error running command" - callback(err) - - proc.on "close", (code, signal) -> - logger.info code:code, signal:signal, project_id:project_id, "command exited" - if signal is 'SIGTERM' # signal from kill method below - err = new Error("terminated") - err.terminated = true - return callback(err) - else if code is 1 # exit status from chktex - err = new Error("exited") - err.code = code - return callback(err) - else - callback() - - return proc.pid # return process id to allow job to be killed if necessary - - kill: (pid, callback = (error) ->) -> - try - process.kill -pid # kill all processes in group - catch err - return callback(err) - callback() +module.exports = CommandRunner diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 0db15e520b..807b8c442f 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -15,10 +15,7 @@ fse = require "fs-extra" os = require("os") async = require "async" Errors = require './Errors' - -commandRunner = Settings.clsi?.commandRunner or "./CommandRunner" -logger.info commandRunner:commandRunner, "selecting command runner for clsi" -CommandRunner = require(commandRunner) +CommandRunner = require "./CommandRunner" getCompileName = (project_id, user_id) -> if user_id? then "#{project_id}-#{user_id}" else project_id diff --git a/services/clsi/app/coffee/DockerLockManager.coffee b/services/clsi/app/coffee/DockerLockManager.coffee new file mode 100644 index 0000000000..739f2cd19f --- /dev/null +++ b/services/clsi/app/coffee/DockerLockManager.coffee @@ -0,0 +1,56 @@ +logger = require "logger-sharelatex" + +LockState = {} # locks for docker container operations, by container name + +module.exports = LockManager = + + MAX_LOCK_HOLD_TIME: 15000 # how long we can keep a lock + MAX_LOCK_WAIT_TIME: 10000 # how long we wait for a lock + LOCK_TEST_INTERVAL: 1000 # retry time + + tryLock: (key, callback = (err, gotLock) ->) -> + existingLock = LockState[key] + if existingLock? # the lock is already taken, check how old it is + lockAge = Date.now() - existingLock.created + if lockAge < LockManager.MAX_LOCK_HOLD_TIME + return callback(null, false) # we didn't get the lock, bail out + else + logger.error {key: key, lock: existingLock, age:lockAge}, "taking old lock by force" + # take the lock + LockState[key] = lockValue = {created: Date.now()} + callback(null, true, lockValue) + + getLock: (key, callback = (error, lockValue) ->) -> + startTime = Date.now() + do attempt = () -> + LockManager.tryLock key, (error, gotLock, lockValue) -> + return callback(error) if error? + if gotLock + callback(null, lockValue) + else if Date.now() - startTime > LockManager.MAX_LOCK_WAIT_TIME + e = new Error("Lock timeout") + e.key = key + return callback(e) + else + setTimeout attempt, LockManager.LOCK_TEST_INTERVAL + + releaseLock: (key, lockValue, callback = (error) ->) -> + existingLock = LockState[key] + if existingLock is lockValue # lockValue is an object, so we can test by reference + delete LockState[key] # our lock, so we can free it + callback() + else if existingLock? # lock exists but doesn't match ours + logger.error {key:key, lock: existingLock}, "tried to release lock taken by force" + callback() + else + logger.error {key:key, lock: existingLock}, "tried to release lock that has gone" + callback() + + runWithLock: (key, runner = ( (releaseLock = (error) ->) -> ), callback = ( (error) -> )) -> + LockManager.getLock key, (error, lockValue) -> + return callback(error) if error? + runner (error1, args...) -> + LockManager.releaseLock key, lockValue, (error2) -> + error = error1 or error2 + return callback(error) if error? + callback(null, args...) diff --git a/services/clsi/app/coffee/DockerRunner.coffee b/services/clsi/app/coffee/DockerRunner.coffee new file mode 100644 index 0000000000..f14b5c6497 --- /dev/null +++ b/services/clsi/app/coffee/DockerRunner.coffee @@ -0,0 +1,348 @@ +Settings = require "settings-sharelatex" +logger = require "logger-sharelatex" +Docker = require("dockerode") +dockerode = new Docker() +crypto = require "crypto" +async = require "async" +LockManager = require "./DockerLockManager" +fs = require "fs" +Path = require 'path' +_ = require "underscore" + +logger.info "using docker runner" + +usingSiblingContainers = () -> + Settings?.path?.sandboxedCompilesHostDir? + +module.exports = DockerRunner = + ERR_NOT_DIRECTORY: new Error("not a directory") + ERR_TERMINATED: new Error("terminated") + ERR_EXITED: new Error("exited") + ERR_TIMED_OUT: new Error("container timed out") + + run: (project_id, command, directory, image, timeout, environment, callback = (error, output) ->) -> + + if usingSiblingContainers() + _newPath = Settings.path.sandboxedCompilesHostDir + logger.log {path: _newPath}, "altering bind path for sibling containers" + # Server Pro, example: + # '/var/lib/sharelatex/data/compiles/<project-id>' + # ... becomes ... + # '/opt/sharelatex_data/data/compiles/<project-id>' + directory = Path.join(Settings.path.sandboxedCompilesHostDir, Path.basename(directory)) + + volumes = {} + volumes[directory] = "/compile" + + command = (arg.toString().replace?('$COMPILE_DIR', "/compile") for arg in command) + if !image? + image = Settings.clsi.docker.image + + options = DockerRunner._getContainerOptions(command, image, volumes, timeout, environment) + fingerprint = DockerRunner._fingerprintContainer(options) + options.name = name = "project-#{project_id}-#{fingerprint}" + + logger.log project_id: project_id, options: options, "running docker container" + DockerRunner._runAndWaitForContainer options, volumes, timeout, (error, output) -> + if error?.message?.match("HTTP code is 500") + logger.log err: error, project_id: project_id, "error running container so destroying and retrying" + DockerRunner.destroyContainer name, null, true, (error) -> + return callback(error) if error? + DockerRunner._runAndWaitForContainer options, volumes, timeout, callback + else + callback(error, output) + + return name # pass back the container name to allow it to be killed + + kill: (container_id, callback = (error) ->) -> + logger.log container_id: container_id, "sending kill signal to container" + container = dockerode.getContainer(container_id) + container.kill (error) -> + if error? and error?.message?.match?(/Cannot kill container .* is not running/) + logger.warn err: error, container_id: container_id, "container not running, continuing" + error = null + if error? + logger.error err: error, container_id: container_id, "error killing container" + return callback(error) + else + callback() + + _runAndWaitForContainer: (options, volumes, timeout, _callback = (error, output) ->) -> + callback = (args...) -> + _callback(args...) + # Only call the callback once + _callback = () -> + + name = options.name + + streamEnded = false + containerReturned = false + output = {} + + callbackIfFinished = () -> + if streamEnded and containerReturned + callback(null, output) + + attachStreamHandler = (error, _output) -> + return callback(error) if error? + output = _output + streamEnded = true + callbackIfFinished() + + DockerRunner.startContainer options, volumes, attachStreamHandler, (error, containerId) -> + return callback(error) if error? + + DockerRunner.waitForContainer name, timeout, (error, exitCode) -> + return callback(error) if error? + if exitCode is 137 # exit status from kill -9 + err = DockerRunner.ERR_TERMINATED + err.terminated = true + return callback(err) + if exitCode is 1 # exit status from chktex + err = DockerRunner.ERR_EXITED + err.code = exitCode + return callback(err) + containerReturned = true + callbackIfFinished() + + _getContainerOptions: (command, image, volumes, timeout, environment) -> + timeoutInSeconds = timeout / 1000 + + if Settings.path?.synctexBinHostPath? + volumes[Settings.path.synctexBinHostPath] = "/opt/synctex:ro" + + dockerVolumes = {} + for hostVol, dockerVol of volumes + dockerVolumes[dockerVol] = {} + + if volumes[hostVol].slice(-3).indexOf(":r") == -1 + volumes[hostVol] = "#{dockerVol}:rw" + + + # merge settings and environment parameter + env = {} + for src in [Settings.clsi.docker.env, environment or {}] + env[key] = value for key, value of src + # set the path based on the image year + if m = image.match /:([0-9]+)\.[0-9]+/ + year = m[1] + else + year = "2014" + env['PATH'] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/#{year}/bin/x86_64-linux/" + options = + "Cmd" : command, + "Image" : image + "Volumes" : dockerVolumes + "WorkingDir" : "/compile" + "NetworkDisabled" : true + "Memory" : 1024 * 1024 * 1024 * 1024 # 1 Gb + "User" : Settings.clsi.docker.user + "Env" : ("#{key}=#{value}" for key, value of env) # convert the environment hash to an array + "HostConfig" : + "Binds": ("#{hostVol}:#{dockerVol}" for hostVol, dockerVol of volumes) + "LogConfig": {"Type": "none", "Config": {}} + "Ulimits": [{'Name': 'cpu', 'Soft': timeoutInSeconds+5, 'Hard': timeoutInSeconds+10}] + "CapDrop": "ALL" + "SecurityOpt": ["no-new-privileges"] + if Settings.clsi.docker.seccomp_profile? + options.HostConfig.SecurityOpt.push "seccomp=#{Settings.clsi.docker.seccomp_profile}" + return options + + _fingerprintContainer: (containerOptions) -> + # Yay, Hashing! + json = JSON.stringify(containerOptions) + return crypto.createHash("md5").update(json).digest("hex") + + startContainer: (options, volumes, attachStreamHandler, callback) -> + LockManager.runWithLock options.name, (releaseLock) -> + # Check that volumes exist before starting the container. + # When a container is started with volume pointing to a + # non-existent directory then docker creates the directory but + # with root ownership. + DockerRunner._checkVolumes options, volumes, (err) -> + return releaseLock(err) if err? + DockerRunner._startContainer options, volumes, attachStreamHandler, releaseLock + , callback + + # Check that volumes exist and are directories + _checkVolumes: (options, volumes, callback = (error, containerName) ->) -> + if usingSiblingContainers() + # Server Pro, with sibling-containers active, skip checks + return callback(null) + + checkVolume = (path, cb) -> + fs.stat path, (err, stats) -> + return cb(err) if err? + return cb(DockerRunner.ERR_NOT_DIRECTORY) if not stats?.isDirectory() + cb() + jobs = [] + for vol of volumes + do (vol) -> + jobs.push (cb) -> checkVolume(vol, cb) + async.series jobs, callback + + _startContainer: (options, volumes, attachStreamHandler, callback = ((error, output) ->)) -> + callback = _.once(callback) + name = options.name + + logger.log {container_name: name}, "starting container" + container = dockerode.getContainer(name) + + createAndStartContainer = -> + dockerode.createContainer options, (error, container) -> + return callback(error) if error? + startExistingContainer() + + startExistingContainer = -> + DockerRunner.attachToContainer options.name, attachStreamHandler, (error)-> + return callback(error) if error? + container.start (error) -> + if error? and error?.statusCode != 304 #already running + return callback(error) + else + callback() + + container.inspect (error, stats)-> + if error?.statusCode == 404 + createAndStartContainer() + else if error? + logger.err {container_name: name}, "unable to inspect container to start" + return callback(error) + else + startExistingContainer() + + + attachToContainer: (containerId, attachStreamHandler, attachStartCallback) -> + container = dockerode.getContainer(containerId) + container.attach {stdout: 1, stderr: 1, stream: 1}, (error, stream) -> + if error? + logger.error err: error, container_id: containerId, "error attaching to container" + return attachStartCallback(error) + else + attachStartCallback() + + + logger.log container_id: containerId, "attached to container" + + MAX_OUTPUT = 1024 * 1024 # limit output to 1MB + createStringOutputStream = (name) -> + return { + data: "" + overflowed: false + write: (data) -> + return if @overflowed + if @data.length < MAX_OUTPUT + @data += data + else + logger.error container_id: containerId, length: @data.length, maxLen: MAX_OUTPUT, "#{name} exceeds max size" + @data += "(...truncated at #{MAX_OUTPUT} chars...)" + @overflowed = true + # kill container if too much output + # docker.containers.kill(containerId, () ->) + } + + stdout = createStringOutputStream "stdout" + stderr = createStringOutputStream "stderr" + + container.modem.demuxStream(stream, stdout, stderr) + + stream.on "error", (err) -> + logger.error err: err, container_id: containerId, "error reading from container stream" + + stream.on "end", () -> + attachStreamHandler null, {stdout: stdout.data, stderr: stderr.data} + + waitForContainer: (containerId, timeout, _callback = (error, exitCode) ->) -> + callback = (args...) -> + _callback(args...) + # Only call the callback once + _callback = () -> + + container = dockerode.getContainer(containerId) + + timedOut = false + timeoutId = setTimeout () -> + timedOut = true + logger.log container_id: containerId, "timeout reached, killing container" + container.kill(() ->) + , timeout + + logger.log container_id: containerId, "waiting for docker container" + container.wait (error, res) -> + if error? + clearTimeout timeoutId + logger.error err: error, container_id: containerId, "error waiting for container" + return callback(error) + if timedOut + logger.log containerId: containerId, "docker container timed out" + error = DockerRunner.ERR_TIMED_OUT + error.timedout = true + callback error + else + clearTimeout timeoutId + logger.log container_id: containerId, exitCode: res.StatusCode, "docker container returned" + callback null, res.StatusCode + + destroyContainer: (containerName, containerId, shouldForce, callback = (error) ->) -> + # We want the containerName for the lock and, ideally, the + # containerId to delete. There is a bug in the docker.io module + # where if you delete by name and there is an error, it throws an + # async exception, but if you delete by id it just does a normal + # error callback. We fall back to deleting by name if no id is + # supplied. + LockManager.runWithLock containerName, (releaseLock) -> + DockerRunner._destroyContainer containerId or containerName, shouldForce, releaseLock + , callback + + _destroyContainer: (containerId, shouldForce, callback = (error) ->) -> + logger.log container_id: containerId, "destroying docker container" + container = dockerode.getContainer(containerId) + container.remove {force: shouldForce == true}, (error) -> + if error? and error?.statusCode == 404 + logger.warn err: error, container_id: containerId, "container not found, continuing" + error = null + if error? + logger.error err: error, container_id: containerId, "error destroying container" + else + logger.log container_id: containerId, "destroyed container" + callback(error) + + # handle expiry of docker containers + + MAX_CONTAINER_AGE: Settings.clsi.docker.maxContainerAge or oneHour = 60 * 60 * 1000 + + examineOldContainer: (container, callback = (error, name, id, ttl)->) -> + name = container.Name or container.Names?[0] + created = container.Created * 1000 # creation time is returned in seconds + now = Date.now() + age = now - created + maxAge = DockerRunner.MAX_CONTAINER_AGE + ttl = maxAge - age + logger.log {containerName: name, created: created, now: now, age: age, maxAge: maxAge, ttl: ttl}, "checking whether to destroy container" + callback(null, name, container.Id, ttl) + + destroyOldContainers: (callback = (error) ->) -> + dockerode.listContainers all: true, (error, containers) -> + return callback(error) if error? + jobs = [] + for container in containers or [] + do (container) -> + DockerRunner.examineOldContainer container, (err, name, id, ttl) -> + if name.slice(0, 9) == '/project-' && ttl <= 0 + jobs.push (cb) -> + DockerRunner.destroyContainer name, id, false, () -> cb() + # Ignore errors because some containers get stuck but + # will be destroyed next time + async.series jobs, callback + + startContainerMonitor: () -> + logger.log {maxAge: DockerRunner.MAX_CONTAINER_AGE}, "starting container expiry" + # randomise the start time + randomDelay = Math.floor(Math.random() * 5 * 60 * 1000) + setTimeout () -> + setInterval () -> + DockerRunner.destroyOldContainers() + , oneHour = 60 * 60 * 1000 + , randomDelay + +DockerRunner.startContainerMonitor() \ No newline at end of file diff --git a/services/clsi/app/coffee/LatexRunner.coffee b/services/clsi/app/coffee/LatexRunner.coffee index 6a5a4f6ae2..3571af20bf 100644 --- a/services/clsi/app/coffee/LatexRunner.coffee +++ b/services/clsi/app/coffee/LatexRunner.coffee @@ -2,7 +2,7 @@ Path = require "path" Settings = require "settings-sharelatex" logger = require "logger-sharelatex" Metrics = require "./Metrics" -CommandRunner = require(Settings.clsi?.commandRunner or "./CommandRunner") +CommandRunner = require "./CommandRunner" ProcessTable = {} # table of currently running jobs (pids or docker container names) diff --git a/services/clsi/app/coffee/LocalCommandRunner.coffee b/services/clsi/app/coffee/LocalCommandRunner.coffee new file mode 100644 index 0000000000..f47af00177 --- /dev/null +++ b/services/clsi/app/coffee/LocalCommandRunner.coffee @@ -0,0 +1,44 @@ +spawn = require("child_process").spawn +logger = require "logger-sharelatex" + +logger.info "using standard command runner" + +module.exports = CommandRunner = + run: (project_id, command, directory, image, timeout, environment, callback = (error) ->) -> + command = (arg.replace('$COMPILE_DIR', directory) for arg in command) + logger.log project_id: project_id, command: command, directory: directory, "running command" + logger.warn "timeouts and sandboxing are not enabled with CommandRunner" + + # merge environment settings + env = {} + env[key] = value for key, value of process.env + env[key] = value for key, value of environment + + # run command as detached process so it has its own process group (which can be killed if needed) + proc = spawn command[0], command.slice(1), stdio: "inherit", cwd: directory, detached: true, env: env + + proc.on "error", (err)-> + logger.err err:err, project_id:project_id, command: command, directory: directory, "error running command" + callback(err) + + proc.on "close", (code, signal) -> + logger.info code:code, signal:signal, project_id:project_id, "command exited" + if signal is 'SIGTERM' # signal from kill method below + err = new Error("terminated") + err.terminated = true + return callback(err) + else if code is 1 # exit status from chktex + err = new Error("exited") + err.code = code + return callback(err) + else + callback() + + return proc.pid # return process id to allow job to be killed if necessary + + kill: (pid, callback = (error) ->) -> + try + process.kill -pid # kill all processes in group + catch err + return callback(err) + callback() diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index c29829eed7..c76b251752 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -29,9 +29,9 @@ module.exports = project_cache_length_ms: 1000 * 60 * 60 * 24 parallelFileDownloads:1 -if process.env["COMMAND_RUNNER"] +if process.env["DOCKER_RUNNER"] module.exports.clsi = - commandRunner: process.env["COMMAND_RUNNER"] + dockerRunner: process.env["DOCKER_RUNNER"] == "true" docker: image: process.env["TEXLIVE_IMAGE"] or "quay.io/sharelatex/texlive-full:2017.1" env: diff --git a/services/clsi/docker-compose-config.yml b/services/clsi/docker-compose-config.yml index 6e1fca45cf..55a3f012bd 100644 --- a/services/clsi/docker-compose-config.yml +++ b/services/clsi/docker-compose-config.yml @@ -6,12 +6,11 @@ services: TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2017.1 TEXLIVE_IMAGE_USER: root SHARELATEX_CONFIG: /app/config/settings.defaults.coffee - COMMAND_RUNNER: docker-runner-sharelatex + DOCKER_RUNNER: "true" COMPILES_HOST_DIR: $PWD/compiles SYNCTEX_BIN_HOST_PATH: $PWD/bin/synctex volumes: - /var/run/docker.sock:/var/run/docker.sock - - ./docker-runner:/app/node_modules/docker-runner-sharelatex - ./compiles:/app/compiles ci: @@ -19,10 +18,9 @@ services: TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2017.1 TEXLIVE_IMAGE_USER: root SHARELATEX_CONFIG: /app/config/settings.defaults.coffee - COMMAND_RUNNER: docker-runner-sharelatex + DOCKER_RUNNER: "true" COMPILES_HOST_DIR: $PWD/compiles SYNCTEX_BIN_HOST_PATH: $PWD/bin/synctex volumes: - /var/run/docker.sock:/var/run/docker.sock:rw - - ./docker-runner:/app/node_modules/docker-runner-sharelatex - ./compiles:/app/compiles \ No newline at end of file diff --git a/services/clsi/package.json b/services/clsi/package.json index ae379558e6..6c5361cb3e 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -17,7 +17,7 @@ "compile": "npm run compile:app && npm run compile:test:acceptance && npm run compile:test:smoke", "start": "npm run compile:app && node app.js", "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", - "test:unit:_run": "mocha --recursive --reporter spec $@ test/unit/js", + "test:unit:_run": "mocha --recursive --exit --reporter spec $@ test/unit/js", "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- $@", "compile:unit_tests": "[ -e test/unit ] && coffee -o test/unit/js -c test/unit/coffee || echo 'No unit tests to compile'", "compile:acceptance_tests": "[ -e test/acceptance ] && coffee -o test/acceptance/js -c test/acceptance/coffee || echo 'No acceptance tests to compile'", diff --git a/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee b/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee index 0d3337ad8e..95b667ba20 100644 --- a/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee +++ b/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee @@ -21,7 +21,6 @@ describe "Simple LaTeX file", -> it "should return the PDF", -> pdf = Client.getOutputFile(@body, "pdf") - console.log @body pdf.type.should.equal "pdf" it "should return the log", -> diff --git a/services/clsi/test/acceptance/coffee/TimeoutTests.coffee b/services/clsi/test/acceptance/coffee/TimeoutTests.coffee index 9195474a3b..877223a961 100644 --- a/services/clsi/test/acceptance/coffee/TimeoutTests.coffee +++ b/services/clsi/test/acceptance/coffee/TimeoutTests.coffee @@ -23,7 +23,6 @@ describe "Timed out compile", -> Client.compile @project_id, @request, (@error, @res, @body) => done() it "should return a timeout error", -> - console.log @body.compile, "!!!1111" @body.compile.error.should.equal "container timed out" it "should return a timedout status", -> diff --git a/services/clsi/test/acceptance/coffee/helpers/Client.coffee b/services/clsi/test/acceptance/coffee/helpers/Client.coffee index 546c2351e5..391317032f 100644 --- a/services/clsi/test/acceptance/coffee/helpers/Client.coffee +++ b/services/clsi/test/acceptance/coffee/helpers/Client.coffee @@ -11,7 +11,6 @@ module.exports = Client = Math.random().toString(16).slice(2) compile: (project_id, data, callback = (error, res, body) ->) -> - console.log("#{@host}/project/#{project_id}/compile") request.post { url: "#{@host}/project/#{project_id}/compile" json: diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 448e06c01b..1836c05cf9 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -293,8 +293,6 @@ describe "CompileManager", -> # .should.equal true it "should call the callback with the parsed output", -> - console.log(@file_name, @line, @column) - console.log @callback.args[0] @callback .calledWith(null, [{ file: @file_name diff --git a/services/clsi/test/unit/coffee/DockerLockManagerTests.coffee b/services/clsi/test/unit/coffee/DockerLockManagerTests.coffee new file mode 100644 index 0000000000..6161bec36d --- /dev/null +++ b/services/clsi/test/unit/coffee/DockerLockManagerTests.coffee @@ -0,0 +1,145 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +require('chai').should() +require "coffee-script" +modulePath = require('path').join __dirname, '../../../app/coffee/DockerLockManager' + +describe "LockManager", -> + beforeEach -> + @LockManager = SandboxedModule.require modulePath, requires: + "settings-sharelatex": @Settings = + clsi: docker: {} + "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } + + describe "runWithLock", -> + describe "with a single lock", -> + beforeEach (done) -> + @callback = sinon.stub() + @LockManager.runWithLock "lock-one", (releaseLock) -> + setTimeout () -> + releaseLock(null, "hello", "world") + , 100 + , (err, args...) => + @callback(err,args...) + done() + + it "should call the callback", -> + @callback.calledWith(null,"hello","world").should.equal true + + describe "with two locks", -> + beforeEach (done) -> + @callback1 = sinon.stub() + @callback2 = sinon.stub() + @LockManager.runWithLock "lock-one", (releaseLock) -> + setTimeout () -> + releaseLock(null, "hello", "world","one") + , 100 + , (err, args...) => + @callback1(err,args...) + @LockManager.runWithLock "lock-two", (releaseLock) -> + setTimeout () -> + releaseLock(null, "hello", "world","two") + , 200 + , (err, args...) => + @callback2(err,args...) + done() + + it "should call the first callback", -> + @callback1.calledWith(null,"hello","world","one").should.equal true + + it "should call the second callback", -> + @callback2.calledWith(null,"hello","world","two").should.equal true + + describe "with lock contention", -> + describe "where the first lock is released quickly", -> + beforeEach (done) -> + @LockManager.MAX_LOCK_WAIT_TIME = 1000 + @LockManager.LOCK_TEST_INTERVAL = 100 + @callback1 = sinon.stub() + @callback2 = sinon.stub() + @LockManager.runWithLock "lock", (releaseLock) -> + setTimeout () -> + releaseLock(null, "hello", "world","one") + , 100 + , (err, args...) => + @callback1(err,args...) + @LockManager.runWithLock "lock", (releaseLock) -> + setTimeout () -> + releaseLock(null, "hello", "world","two") + , 200 + , (err, args...) => + @callback2(err,args...) + done() + + it "should call the first callback", -> + @callback1.calledWith(null,"hello","world","one").should.equal true + + it "should call the second callback", -> + @callback2.calledWith(null,"hello","world","two").should.equal true + + describe "where the first lock is held longer than the waiting time", -> + beforeEach (done) -> + @LockManager.MAX_LOCK_HOLD_TIME = 10000 + @LockManager.MAX_LOCK_WAIT_TIME = 1000 + @LockManager.LOCK_TEST_INTERVAL = 100 + @callback1 = sinon.stub() + @callback2 = sinon.stub() + doneOne = doneTwo = false + finish = (key) -> + doneOne = true if key is 1 + doneTwo = true if key is 2 + done() if doneOne and doneTwo + @LockManager.runWithLock "lock", (releaseLock) -> + setTimeout () -> + releaseLock(null, "hello", "world","one") + , 1100 + , (err, args...) => + @callback1(err,args...) + finish(1) + @LockManager.runWithLock "lock", (releaseLock) -> + setTimeout () -> + releaseLock(null, "hello", "world","two") + , 100 + , (err, args...) => + @callback2(err,args...) + finish(2) + + it "should call the first callback", -> + @callback1.calledWith(null,"hello","world","one").should.equal true + + it "should call the second callback with an error", -> + error = sinon.match.instanceOf Error + @callback2.calledWith(error).should.equal true + + describe "where the first lock is held longer than the max holding time", -> + beforeEach (done) -> + @LockManager.MAX_LOCK_HOLD_TIME = 1000 + @LockManager.MAX_LOCK_WAIT_TIME = 2000 + @LockManager.LOCK_TEST_INTERVAL = 100 + @callback1 = sinon.stub() + @callback2 = sinon.stub() + doneOne = doneTwo = false + finish = (key) -> + doneOne = true if key is 1 + doneTwo = true if key is 2 + done() if doneOne and doneTwo + @LockManager.runWithLock "lock", (releaseLock) -> + setTimeout () -> + releaseLock(null, "hello", "world","one") + , 1500 + , (err, args...) => + @callback1(err,args...) + finish(1) + @LockManager.runWithLock "lock", (releaseLock) -> + setTimeout () -> + releaseLock(null, "hello", "world","two") + , 100 + , (err, args...) => + @callback2(err,args...) + finish(2) + + it "should call the first callback", -> + @callback1.calledWith(null,"hello","world","one").should.equal true + + it "should call the second callback", -> + @callback2.calledWith(null,"hello","world","two").should.equal true diff --git a/services/clsi/test/unit/coffee/DockerRunnerTests.coffee b/services/clsi/test/unit/coffee/DockerRunnerTests.coffee new file mode 100644 index 0000000000..5a697e2b8c --- /dev/null +++ b/services/clsi/test/unit/coffee/DockerRunnerTests.coffee @@ -0,0 +1,499 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +require('chai').should() +expect = require('chai').expect +require "coffee-script" +modulePath = require('path').join __dirname, '../../../app/coffee/DockerRunner' +Path = require "path" + +describe "DockerRunner", -> + beforeEach -> + @container = container = {} + @DockerRunner = SandboxedModule.require modulePath, requires: + "settings-sharelatex": @Settings = + clsi: docker: {} + path: {} + "logger-sharelatex": @logger = { + log: sinon.stub(), + error: sinon.stub(), + info: sinon.stub(), + warn: sinon.stub() + } + "dockerode": class Docker + getContainer: sinon.stub().returns(container) + createContainer: sinon.stub().yields(null, container) + listContainers: sinon.stub() + "fs": @fs = { stat: sinon.stub().yields(null,{isDirectory:()->true}) } + "./Metrics": + Timer: class Timer + done: () -> + "./LockManager": + runWithLock: (key, runner, callback) -> runner(callback) + @Docker = Docker + @getContainer = Docker::getContainer + @createContainer = Docker::createContainer + @listContainers = Docker::listContainers + + @directory = "/local/compile/directory" + @mainFile = "main-file.tex" + @compiler = "pdflatex" + @image = "example.com/sharelatex/image:2016.2" + @env = {} + @callback = sinon.stub() + @project_id = "project-id-123" + @volumes = + "/local/compile/directory": "/compile" + @Settings.clsi.docker.image = @defaultImage = "default-image" + @Settings.clsi.docker.env = PATH: "mock-path" + + describe "run", -> + beforeEach (done)-> + @DockerRunner._getContainerOptions = sinon.stub().returns(@options = {mockoptions: "foo"}) + @DockerRunner._fingerprintContainer = sinon.stub().returns(@fingerprint = "fingerprint") + + @name = "project-#{@project_id}-#{@fingerprint}" + + @command = ["mock", "command", "--outdir=$COMPILE_DIR"] + @command_with_dir = ["mock", "command", "--outdir=/compile"] + @timeout = 42000 + done() + + describe "successfully", -> + beforeEach (done)-> + @DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, @output = "mock-output") + @DockerRunner.run @project_id, @command, @directory, @image, @timeout, @env, (err, output)=> + @callback(err, output) + done() + + it "should generate the options for the container", -> + @DockerRunner._getContainerOptions + .calledWith(@command_with_dir, @image, @volumes, @timeout) + .should.equal true + + it "should generate the fingerprint from the returned options", -> + @DockerRunner._fingerprintContainer + .calledWith(@options) + .should.equal true + + it "should do the run", -> + @DockerRunner._runAndWaitForContainer + .calledWith(@options, @volumes, @timeout) + .should.equal true + + it "should call the callback", -> + @callback.calledWith(null, @output).should.equal true + + describe 'when path.sandboxedCompilesHostDir is set', -> + + beforeEach -> + @Settings.path.sandboxedCompilesHostDir = '/some/host/dir/compiles' + @directory = '/var/lib/sharelatex/data/compiles/xyz' + @DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, @output = "mock-output") + @DockerRunner.run @project_id, @command, @directory, @image, @timeout, @env, @callback + + it 'should re-write the bind directory', -> + volumes = @DockerRunner._runAndWaitForContainer.lastCall.args[1] + expect(volumes).to.deep.equal { + '/some/host/dir/compiles/xyz': '/compile' + } + + it "should call the callback", -> + @callback.calledWith(null, @output).should.equal true + + describe "when the run throws an error", -> + beforeEach -> + firstTime = true + @output = "mock-output" + @DockerRunner._runAndWaitForContainer = (options, volumes, timeout, callback = (error, output)->) => + if firstTime + firstTime = false + callback new Error("HTTP code is 500 which indicates error: server error") + else + callback(null, @output) + sinon.spy @DockerRunner, "_runAndWaitForContainer" + @DockerRunner.destroyContainer = sinon.stub().callsArg(3) + @DockerRunner.run @project_id, @command, @directory, @image, @timeout, @env, @callback + + it "should do the run twice", -> + @DockerRunner._runAndWaitForContainer + .calledTwice.should.equal true + + it "should destroy the container in between", -> + @DockerRunner.destroyContainer + .calledWith(@name, null) + .should.equal true + + it "should call the callback", -> + @callback.calledWith(null, @output).should.equal true + + describe "with no image", -> + beforeEach -> + @DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, @output = "mock-output") + @DockerRunner.run @project_id, @command, @directory, null, @timeout, @env, @callback + + it "should use the default image", -> + @DockerRunner._getContainerOptions + .calledWith(@command_with_dir, @defaultImage, @volumes, @timeout) + .should.equal true + + describe "_runAndWaitForContainer", -> + beforeEach -> + @options = {mockoptions: "foo", name: @name = "mock-name"} + @DockerRunner.startContainer = (options, volumes, attachStreamHandler, callback) => + attachStreamHandler(null, @output = "mock-output") + callback(null, @containerId = "container-id") + sinon.spy @DockerRunner, "startContainer" + @DockerRunner.waitForContainer = sinon.stub().callsArgWith(2, null, @exitCode = 42) + @DockerRunner._runAndWaitForContainer @options, @volumes, @timeout, @callback + + it "should create/start the container", -> + @DockerRunner.startContainer + .calledWith(@options, @volumes) + .should.equal true + + it "should wait for the container to finish", -> + @DockerRunner.waitForContainer + .calledWith(@name, @timeout) + .should.equal true + + it "should call the callback with the output", -> + @callback.calledWith(null, @output).should.equal true + + describe "startContainer", -> + beforeEach -> + @attachStreamHandler = sinon.stub() + @attachStreamHandler.cock = true + @options = {mockoptions: "foo", name: "mock-name"} + @container.inspect = sinon.stub().callsArgWith(0) + @DockerRunner.attachToContainer = (containerId, attachStreamHandler, cb)=> + attachStreamHandler() + cb() + sinon.spy @DockerRunner, "attachToContainer" + + + + describe "when the container exists", -> + beforeEach -> + @container.inspect = sinon.stub().callsArgWith(0) + @container.start = sinon.stub().yields() + + @DockerRunner.startContainer @options, @volumes, @callback, -> + + it "should start the container with the given name", -> + @getContainer + .calledWith(@options.name) + .should.equal true + @container.start + .called + .should.equal true + + it "should not try to create the container", -> + @createContainer.called.should.equal false + + it "should attach to the container", -> + @DockerRunner.attachToContainer.called.should.equal true + + it "should call the callback", -> + @callback.called.should.equal true + + it "should attach before the container starts", -> + sinon.assert.callOrder(@DockerRunner.attachToContainer, @container.start) + + describe "when the container does not exist", -> + beforeEach ()-> + exists = false + @container.start = sinon.stub().yields() + @container.inspect = sinon.stub().callsArgWith(0, {statusCode:404}) + @DockerRunner.startContainer @options, @volumes, @attachStreamHandler, @callback + + it "should create the container", -> + @createContainer + .calledWith(@options) + .should.equal true + + it "should call the callback and stream handler", -> + @attachStreamHandler.called.should.equal true + @callback.called.should.equal true + + it "should attach to the container", -> + @DockerRunner.attachToContainer.called.should.equal true + + it "should attach before the container starts", -> + sinon.assert.callOrder(@DockerRunner.attachToContainer, @container.start) + + + describe "when the container is already running", -> + beforeEach -> + error = new Error("HTTP code is 304 which indicates error: server error - start: Cannot start container #{@name}: The container MOCKID is already running.") + error.statusCode = 304 + @container.start = sinon.stub().yields(error) + @container.inspect = sinon.stub().callsArgWith(0) + @DockerRunner.startContainer @options, @volumes, @attachStreamHandler, @callback + + it "should not try to create the container", -> + @createContainer.called.should.equal false + + it "should call the callback and stream handler without an error", -> + @attachStreamHandler.called.should.equal true + @callback.called.should.equal true + + describe "when a volume does not exist", -> + beforeEach ()-> + @fs.stat = sinon.stub().yields(new Error("no such path")) + @DockerRunner.startContainer @options, @volumes, @attachStreamHandler, @callback + + it "should not try to create the container", -> + @createContainer.called.should.equal false + + it "should call the callback with an error", -> + @callback.calledWith(new Error()).should.equal true + + describe "when a volume exists but is not a directory", -> + beforeEach -> + @fs.stat = sinon.stub().yields(null, {isDirectory: () -> return false}) + @DockerRunner.startContainer @options, @volumes, @attachStreamHandler, @callback + + it "should not try to create the container", -> + @createContainer.called.should.equal false + + it "should call the callback with an error", -> + @callback.calledWith(new Error()).should.equal true + + describe "when a volume does not exist, but sibling-containers are used", -> + beforeEach -> + @fs.stat = sinon.stub().yields(new Error("no such path")) + @Settings.path.sandboxedCompilesHostDir = '/some/path' + @container.start = sinon.stub().yields() + @DockerRunner.startContainer @options, @volumes, @callback + + afterEach -> + delete @Settings.path.sandboxedCompilesHostDir + + it "should start the container with the given name", -> + @getContainer + .calledWith(@options.name) + .should.equal true + @container.start + .called + .should.equal true + + it "should not try to create the container", -> + @createContainer.called.should.equal false + + it "should call the callback", -> + @callback.called.should.equal true + @callback.calledWith(new Error()).should.equal false + + describe "when the container tries to be created, but already has been (race condition)", -> + + describe "waitForContainer", -> + beforeEach -> + @containerId = "container-id" + @timeout = 5000 + @container.wait = sinon.stub().yields(null, StatusCode: @statusCode = 42) + @container.kill = sinon.stub().yields() + + describe "when the container returns in time", -> + beforeEach -> + @DockerRunner.waitForContainer @containerId, @timeout, @callback + + it "should wait for the container", -> + @getContainer + .calledWith(@containerId) + .should.equal true + @container.wait + .called + .should.equal true + + it "should call the callback with the exit", -> + @callback + .calledWith(null, @statusCode) + .should.equal true + + describe "when the container does not return before the timeout", -> + beforeEach (done) -> + @container.wait = (callback = (error, exitCode) ->) -> + setTimeout () -> + callback(null, StatusCode: 42) + , 100 + @timeout = 5 + @DockerRunner.waitForContainer @containerId, @timeout, (args...) => + @callback(args...) + done() + + it "should call kill on the container", -> + @getContainer + .calledWith(@containerId) + .should.equal true + @container.kill + .called + .should.equal true + + it "should call the callback with an error", -> + error = new Error("container timed out") + error.timedout = true + @callback + .calledWith(error) + .should.equal true + + describe "destroyOldContainers", -> + beforeEach (done) -> + oneHourInSeconds = 60 * 60 + oneHourInMilliseconds = oneHourInSeconds * 1000 + nowInSeconds = Date.now()/1000 + @containers = [{ + Name: "/project-old-container-name" + Id: "old-container-id" + Created: nowInSeconds - oneHourInSeconds - 100 + }, { + Name: "/project-new-container-name" + Id: "new-container-id" + Created: nowInSeconds - oneHourInSeconds + 100 + }, { + Name: "/totally-not-a-project-container" + Id: "some-random-id" + Created: nowInSeconds - (2 * oneHourInSeconds ) + }] + @DockerRunner.MAX_CONTAINER_AGE = oneHourInMilliseconds + @listContainers.callsArgWith(1, null, @containers) + @DockerRunner.destroyContainer = sinon.stub().callsArg(3) + @DockerRunner.destroyOldContainers (error) => + @callback(error) + done() + + it "should list all containers", -> + @listContainers + .calledWith(all: true) + .should.equal true + + it "should destroy old containers", -> + @DockerRunner.destroyContainer + .callCount + .should.equal 1 + @DockerRunner.destroyContainer + .calledWith("/project-old-container-name", "old-container-id") + .should.equal true + + it "should not destroy new containers", -> + @DockerRunner.destroyContainer + .calledWith("/project-new-container-name", "new-container-id") + .should.equal false + + it "should not destroy non-project containers", -> + @DockerRunner.destroyContainer + .calledWith("/totally-not-a-project-container", "some-random-id") + .should.equal false + + it "should callback the callback", -> + @callback.called.should.equal true + + + describe '_destroyContainer', -> + beforeEach -> + @containerId = 'some_id' + @fakeContainer = + remove: sinon.stub().callsArgWith(1, null) + @Docker::getContainer = sinon.stub().returns(@fakeContainer) + + it 'should get the container', (done) -> + @DockerRunner._destroyContainer @containerId, false, (err) => + @Docker::getContainer.callCount.should.equal 1 + @Docker::getContainer.calledWith(@containerId).should.equal true + done() + + it 'should try to force-destroy the container when shouldForce=true', (done) -> + @DockerRunner._destroyContainer @containerId, true, (err) => + @fakeContainer.remove.callCount.should.equal 1 + @fakeContainer.remove.calledWith({force: true}).should.equal true + done() + + it 'should not try to force-destroy the container when shouldForce=false', (done) -> + @DockerRunner._destroyContainer @containerId, false, (err) => + @fakeContainer.remove.callCount.should.equal 1 + @fakeContainer.remove.calledWith({force: false}).should.equal true + done() + + it 'should not produce an error', (done) -> + @DockerRunner._destroyContainer @containerId, false, (err) => + expect(err).to.equal null + done() + + describe 'when the container is already gone', -> + beforeEach -> + @fakeError = new Error('woops') + @fakeError.statusCode = 404 + @fakeContainer = + remove: sinon.stub().callsArgWith(1, @fakeError) + @Docker::getContainer = sinon.stub().returns(@fakeContainer) + + it 'should not produce an error', (done) -> + @DockerRunner._destroyContainer @containerId, false, (err) => + expect(err).to.equal null + done() + + describe 'when container.destroy produces an error', (done) -> + beforeEach -> + @fakeError = new Error('woops') + @fakeError.statusCode = 500 + @fakeContainer = + remove: sinon.stub().callsArgWith(1, @fakeError) + @Docker::getContainer = sinon.stub().returns(@fakeContainer) + + it 'should produce an error', (done) -> + @DockerRunner._destroyContainer @containerId, false, (err) => + expect(err).to.not.equal null + expect(err).to.equal @fakeError + done() + + + describe 'kill', -> + beforeEach -> + @containerId = 'some_id' + @fakeContainer = + kill: sinon.stub().callsArgWith(0, null) + @Docker::getContainer = sinon.stub().returns(@fakeContainer) + + it 'should get the container', (done) -> + @DockerRunner.kill @containerId, (err) => + @Docker::getContainer.callCount.should.equal 1 + @Docker::getContainer.calledWith(@containerId).should.equal true + done() + + it 'should try to force-destroy the container', (done) -> + @DockerRunner.kill @containerId, (err) => + @fakeContainer.kill.callCount.should.equal 1 + done() + + it 'should not produce an error', (done) -> + @DockerRunner.kill @containerId, (err) => + expect(err).to.equal undefined + done() + + describe 'when the container is not actually running', -> + beforeEach -> + @fakeError = new Error('woops') + @fakeError.statusCode = 500 + @fakeError.message = 'Cannot kill container <whatever> is not running' + @fakeContainer = + kill: sinon.stub().callsArgWith(0, @fakeError) + @Docker::getContainer = sinon.stub().returns(@fakeContainer) + + it 'should not produce an error', (done) -> + @DockerRunner.kill @containerId, (err) => + expect(err).to.equal undefined + done() + + describe 'when container.kill produces a legitimate error', (done) -> + beforeEach -> + @fakeError = new Error('woops') + @fakeError.statusCode = 500 + @fakeError.message = 'Totally legitimate reason to throw an error' + @fakeContainer = + kill: sinon.stub().callsArgWith(0, @fakeError) + @Docker::getContainer = sinon.stub().returns(@fakeContainer) + + it 'should produce an error', (done) -> + @DockerRunner.kill @containerId, (err) => + expect(err).to.not.equal undefined + expect(err).to.equal @fakeError + done() diff --git a/services/clsi/test/unit/coffee/LockManager.coffee b/services/clsi/test/unit/coffee/LockManagerTests.coffee similarity index 98% rename from services/clsi/test/unit/coffee/LockManager.coffee rename to services/clsi/test/unit/coffee/LockManagerTests.coffee index c1071a5841..2d0c95afa1 100644 --- a/services/clsi/test/unit/coffee/LockManager.coffee +++ b/services/clsi/test/unit/coffee/LockManagerTests.coffee @@ -5,7 +5,7 @@ modulePath = require('path').join __dirname, '../../../app/js/LockManager' Path = require "path" Errors = require "../../../app/js/Errors" -describe "LockManager", -> +describe "DockerLockManager", -> beforeEach -> @LockManager = SandboxedModule.require modulePath, requires: "settings-sharelatex": {} From 8dce1e2958b48f84c55b76f171b5395acd4b0791 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 5 Mar 2018 15:29:12 +0000 Subject: [PATCH 342/754] wip for ci --- services/clsi/Jenkinsfile | 2 +- services/clsi/config/settings.defaults.coffee | 2 +- services/clsi/docker-compose-config.yml | 1 + services/clsi/docker-compose.ci.yml | 2 +- services/clsi/install_deps.sh | 2 ++ 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index bc9ba0142f..8607d39312 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -23,7 +23,7 @@ pipeline { stage('Acceptance Tests') { steps { - sh 'DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" make test_acceptance' + sh 'DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" test_acceptance' } } diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index c76b251752..3100a5d712 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -9,7 +9,7 @@ module.exports = username: "clsi" password: null dialect: "sqlite" - storage: Path.resolve(__dirname + "/../db.sqlite") + storage: process.env["SQLITE_PATH"] or Path.resolve(__dirname + "/../db.sqlite") path: compilesDir: Path.resolve(__dirname + "/../compiles") diff --git a/services/clsi/docker-compose-config.yml b/services/clsi/docker-compose-config.yml index 55a3f012bd..f2bb8631c3 100644 --- a/services/clsi/docker-compose-config.yml +++ b/services/clsi/docker-compose-config.yml @@ -21,6 +21,7 @@ services: DOCKER_RUNNER: "true" COMPILES_HOST_DIR: $PWD/compiles SYNCTEX_BIN_HOST_PATH: $PWD/bin/synctex + SQLITE_PATH: /app/compiles/db.sqlite volumes: - /var/run/docker.sock:/var/run/docker.sock:rw - ./compiles:/app/compiles \ No newline at end of file diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 2ca2f9a32b..98715c2644 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -15,7 +15,7 @@ services: image: quay.io/sharelatex/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER extends: file: docker-compose-config.yml - service: dev + service: ci environment: REDIS_HOST: redis MONGO_HOST: mongo diff --git a/services/clsi/install_deps.sh b/services/clsi/install_deps.sh index bde142a5aa..dce17a4c91 100755 --- a/services/clsi/install_deps.sh +++ b/services/clsi/install_deps.sh @@ -6,3 +6,5 @@ usermod -aG docker app touch /var/run/docker.sock chown root:docker /var/run/docker.sock + +chown -R app:app /app/cache From 34c52e6c987517d780c542b231fd827d82c37c31 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 5 Mar 2018 15:30:15 +0000 Subject: [PATCH 343/754] removed unused scripts --- services/clsi/package.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index 6c5361cb3e..771ee96deb 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -7,16 +7,13 @@ "url": "https://github.com/sharelatex/clsi-sharelatex.git" }, "scripts": { - "test:acceptance:wait_for_app": "echo 'Waiting for app to be accessible' && while (! curl -s -o /dev/null localhost:3013/status) do sleep 1; done", - "test:acceptance:run": "mocha --recursive --reporter spec --timeout 15000", - "test:acceptance:dir": "npm -q run test:acceptance:wait_for_app && npm -q run test:acceptance:run -- $@", - "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- $@", "compile:test:acceptance": "coffee -o test/acceptance/js -c test/acceptance/coffee", "compile:test:smoke": "coffee -o test/smoke/js -c test/smoke/coffee", "compile:app": "coffee -o app/js -c app/coffee && coffee -c app.coffee", "compile": "npm run compile:app && npm run compile:test:acceptance && npm run compile:test:smoke", "start": "npm run compile:app && node app.js", "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", + "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- $@", "test:unit:_run": "mocha --recursive --exit --reporter spec $@ test/unit/js", "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- $@", "compile:unit_tests": "[ -e test/unit ] && coffee -o test/unit/js -c test/unit/coffee || echo 'No unit tests to compile'", From cf8d5cdd4189c1f42efabcf945d9cffb8bedc61a Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 5 Mar 2018 15:35:08 +0000 Subject: [PATCH 344/754] add cache dir --- services/clsi/install_deps.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/install_deps.sh b/services/clsi/install_deps.sh index dce17a4c91..078c311705 100755 --- a/services/clsi/install_deps.sh +++ b/services/clsi/install_deps.sh @@ -7,4 +7,5 @@ usermod -aG docker app touch /var/run/docker.sock chown root:docker /var/run/docker.sock +mkdir -p /app/cache chown -R app:app /app/cache From dbb5cb5e44858c051ac0c9e7783642389a3e2c44 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 5 Mar 2018 15:40:33 +0000 Subject: [PATCH 345/754] update jenkins task --- services/clsi/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index 8607d39312..993d22f06c 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -23,7 +23,7 @@ pipeline { stage('Acceptance Tests') { steps { - sh 'DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" test_acceptance' + sh 'DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" up --build test_acceptance' } } From bd0a7c35576221fa83c3869fe406ae60f04eb6db Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 5 Mar 2018 16:02:16 +0000 Subject: [PATCH 346/754] add logging in db.coffee --- services/clsi/app/coffee/db.coffee | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/db.coffee b/services/clsi/app/coffee/db.coffee index a72f61e8d0..cb633592ed 100644 --- a/services/clsi/app/coffee/db.coffee +++ b/services/clsi/app/coffee/db.coffee @@ -1,6 +1,7 @@ Sequelize = require("sequelize") Settings = require("settings-sharelatex") _ = require("underscore") +logger = require "logger-sharelatex" options = _.extend {logging:false}, Settings.mysql.clsi @@ -32,5 +33,7 @@ module.exports = ] }) - sync: () -> sequelize.sync() + sync: () -> + logger.log dbPath:Settings.mysql.clsi.storage, "syncing db schema" + sequelize.sync() From b86dc2623fdd5f9b4f309703842a08a415072767 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 6 Mar 2018 10:30:05 +0000 Subject: [PATCH 347/754] removed user temporarly, created make ci task --- services/clsi/Dockerfile | 2 +- services/clsi/Jenkinsfile | 22 ++-------------------- services/clsi/Makefile | 12 ++++++++++++ services/clsi/docker-compose.ci.yml | 9 +-------- services/clsi/docker-compose.yml | 6 ------ 5 files changed, 16 insertions(+), 35 deletions(-) diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 33873948d8..58266614e4 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -23,5 +23,5 @@ RUN useradd --user-group --create-home --home-dir /app --shell /bin/false app RUN [ -e ./install_deps.sh ] && ./install_deps.sh -USER app +# USER app CMD ["node","app.js"] diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index 993d22f06c..e92cfbe565 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -9,27 +9,9 @@ pipeline { } stages { - stage('Build') { + stage('CI') { steps { - sh 'make build' - } - } - - 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" up --build test_acceptance' - } - } - - stage('Package and publish build') { - steps { - sh 'make publish' + sh 'make ci' } } diff --git a/services/clsi/Makefile b/services/clsi/Makefile index b20654c634..450efc80e2 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -28,10 +28,22 @@ test_acceptance: test_clean # clear the database before each acceptance test run test_clean: $(DOCKER_COMPOSE) down -t 0 + build: docker build --pull --tag quay.io/sharelatex/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) . publish: docker push quay.io/sharelatex/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) +ci: + # On the CI server, we want to run our tests in the image that we + # have built for deployment, which is what the docker-compose.ci.yml + # override does. + PROJECT_NAME=$(PROJECT_NAME) \ + BRANCH_NAME=$(BRANCH_NAME) \ + BUILD_NUMBER=$(BUILD_NUMBER) \ + DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" \ + $(MAKE) build test + + .PHONY: clean test test_unit test_acceptance test_clean build publish diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 98715c2644..16a5da3669 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -12,17 +12,10 @@ services: test_acceptance: build: . - image: quay.io/sharelatex/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER extends: file: docker-compose-config.yml service: ci - environment: - REDIS_HOST: redis - MONGO_HOST: mongo - depends_on: - - redis - - mongo - entrypoint: npm run test:acceptance:_run + entrypoint: npm run test:acceptance redis: image: redis diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index cf90742761..2469482bfb 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -21,12 +21,6 @@ services: extends: file: docker-compose-config.yml service: dev - environment: - REDIS_HOST: redis - MONGO_HOST: mongo - depends_on: - - redis - - mongo entrypoint: npm run test:acceptance redis: From 5285d393a046d971039bc67cf05157dad2a04ee2 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 6 Mar 2018 10:59:40 +0000 Subject: [PATCH 348/754] set entry point for dockerfile --- services/clsi/Dockerfile | 3 +-- services/clsi/docker-compose.yml | 2 +- services/clsi/entrypoint.sh | 5 +++++ 3 files changed, 7 insertions(+), 3 deletions(-) create mode 100755 services/clsi/entrypoint.sh diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 58266614e4..db863cda42 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -23,5 +23,4 @@ RUN useradd --user-group --create-home --home-dir /app --shell /bin/false app RUN [ -e ./install_deps.sh ] && ./install_deps.sh -# USER app -CMD ["node","app.js"] +ENTRYPOINT ["/bin/sh", "entrypoint.sh"] diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 2469482bfb..e6af23b152 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -21,7 +21,7 @@ services: extends: file: docker-compose-config.yml service: dev - entrypoint: npm run test:acceptance + command: npm run test:acceptance redis: image: redis diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh new file mode 100755 index 0000000000..588854ed1e --- /dev/null +++ b/services/clsi/entrypoint.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +echo "Changing permissions of /var/run/docker.sock for sibling containers" +chown root:docker /var/run/docker.sock +exec runuser -u app "$@" \ No newline at end of file From f06fa2e146d9f53f94e67510d8e6840617200ecc Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 6 Mar 2018 11:26:43 +0000 Subject: [PATCH 349/754] add cmd back in --- services/clsi/Dockerfile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index db863cda42..de5f218276 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -6,7 +6,6 @@ WORKDIR /app RUN npm install - RUN npm run compile:all FROM node:6.13.0 @@ -15,12 +14,9 @@ COPY --from=app /app /app WORKDIR /app - -# All app and node_modules will be owned by root. -# The app will run as the 'app' user, and so not have write permissions -# on any files it doesn't need. RUN useradd --user-group --create-home --home-dir /app --shell /bin/false app RUN [ -e ./install_deps.sh ] && ./install_deps.sh ENTRYPOINT ["/bin/sh", "entrypoint.sh"] +CMD ["node","app.js"] From 17e86fbfc5f6d0804e742c6faa8160cdb5046339 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 6 Mar 2018 11:27:51 +0000 Subject: [PATCH 350/754] =?UTF-8?q?remove=20touch=20/var/run/docker.sock?= =?UTF-8?q?=20which=20doesn=E2=80=99t=20work=20robustly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- services/clsi/install_deps.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/services/clsi/install_deps.sh b/services/clsi/install_deps.sh index 078c311705..188394995a 100755 --- a/services/clsi/install_deps.sh +++ b/services/clsi/install_deps.sh @@ -4,8 +4,5 @@ apt-get install poppler-utils vim ghostscript --yes npm rebuild usermod -aG docker app -touch /var/run/docker.sock -chown root:docker /var/run/docker.sock - mkdir -p /app/cache chown -R app:app /app/cache From 572f1ee23009233c9b8cd2e94e1a166f938a2fb9 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 6 Mar 2018 12:07:54 +0000 Subject: [PATCH 351/754] update scripts from latest build scripts 1.1.0 --- services/clsi/Dockerfile | 7 ++++++- services/clsi/Jenkinsfile | 1 - services/clsi/Makefile | 5 ++--- services/clsi/docker-compose.ci.yml | 11 +++++++++-- services/clsi/docker-compose.yml | 8 +++++++- services/clsi/package.json | 2 +- 6 files changed, 25 insertions(+), 9 deletions(-) diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index de5f218276..62ef342538 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -6,6 +6,7 @@ WORKDIR /app RUN npm install + RUN npm run compile:all FROM node:6.13.0 @@ -14,9 +15,13 @@ COPY --from=app /app /app WORKDIR /app + +# All app and node_modules will be owned by root. +# The app will run as the 'app' user, and so not have write permissions +# on any files it doesn't need. RUN useradd --user-group --create-home --home-dir /app --shell /bin/false app RUN [ -e ./install_deps.sh ] && ./install_deps.sh - ENTRYPOINT ["/bin/sh", "entrypoint.sh"] + CMD ["node","app.js"] diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index e92cfbe565..7a34ee75bd 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -14,7 +14,6 @@ pipeline { sh 'make ci' } } - stage('Publish build number') { steps { sh 'echo ${BRANCH_NAME}-${BUILD_NUMBER} > build_number.txt' diff --git a/services/clsi/Makefile b/services/clsi/Makefile index 450efc80e2..063d96e602 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -28,13 +28,12 @@ test_acceptance: test_clean # clear the database before each acceptance test run test_clean: $(DOCKER_COMPOSE) down -t 0 - build: docker build --pull --tag quay.io/sharelatex/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) . publish: docker push quay.io/sharelatex/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) - + ci: # On the CI server, we want to run our tests in the image that we # have built for deployment, which is what the docker-compose.ci.yml @@ -43,7 +42,7 @@ ci: BRANCH_NAME=$(BRANCH_NAME) \ BUILD_NUMBER=$(BUILD_NUMBER) \ DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" \ - $(MAKE) build test + $(MAKE) build test publish .PHONY: clean test test_unit test_acceptance test_clean build publish diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 16a5da3669..2ca2f9a32b 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -12,10 +12,17 @@ services: test_acceptance: build: . + image: quay.io/sharelatex/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER extends: file: docker-compose-config.yml - service: ci - entrypoint: npm run test:acceptance + service: dev + environment: + REDIS_HOST: redis + MONGO_HOST: mongo + depends_on: + - redis + - mongo + entrypoint: npm run test:acceptance:_run redis: image: redis diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index e6af23b152..cf90742761 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -21,7 +21,13 @@ services: extends: file: docker-compose-config.yml service: dev - command: npm run test:acceptance + environment: + REDIS_HOST: redis + MONGO_HOST: mongo + depends_on: + - redis + - mongo + entrypoint: npm run test:acceptance redis: image: redis diff --git a/services/clsi/package.json b/services/clsi/package.json index 771ee96deb..fed32605ba 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -21,7 +21,7 @@ "compile:all": "npm run compile:app && npm run compile:unit_tests && npm run compile:acceptance_tests", "nodemon": "nodemon --config nodemon.json" }, - "author": "James Allen <james@sharelatex.com>", + "author": "James Allen <james@sharelatex.com>", "dependencies": { "async": "0.2.9", "body-parser": "^1.2.0", From e4e30cf2e2e42fad122313f4980c195b42dfec5d Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 6 Mar 2018 13:06:10 +0000 Subject: [PATCH 352/754] fixed commended tests --- .../unit/coffee/CompileManagerTests.coffee | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 1836c05cf9..93b7f11b59 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -259,13 +259,19 @@ describe "CompileManager", -> @CommandRunner.run = sinon.stub().callsArgWith(6, null, {stdout:@stdout}) @CompileManager.syncFromCode @project_id, @user_id, @file_name, @line, @column, @callback - # it "should execute the synctex binary", -> - # bin_path = Path.resolve(__dirname + "/../../../bin/synctex") - # synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/output.pdf" - # file_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/#{@file_name}" - # @child_process.execFile - # .calledWith(bin_path, ["code", synctex_path, file_path, @line, @column], timeout: 10000) - # .should.equal true + it "should execute the synctex binary", -> + bin_path = Path.resolve(__dirname + "/../../../bin/synctex") + synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/output.pdf" + file_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/#{@file_name}" + @CommandRunner.run + .calledWith( + "#{@project_id}-#{@user_id}", + ['/opt/synctex', 'code', synctex_path, file_path, @line, @column], + "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}", + @Settings.clsi.docker.image, + 10000, + {} + ).should.equal true it "should call the callback with the parsed output", -> @callback @@ -285,12 +291,17 @@ describe "CompileManager", -> @CommandRunner.run = sinon.stub().callsArgWith(6, null, {stdout:@stdout}) @CompileManager.syncFromPdf @project_id, @user_id, @page, @h, @v, @callback - # it "should execute the synctex binary", -> - # bin_path = Path.resolve(__dirname + "/../../../bin/synctex") - # synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/output.pdf" - # @CommandRunner.run - # .calledWith(bin_path, ["pdf", synctex_path, @page, @h, @v], timeout: 10000) - # .should.equal true + it "should execute the synctex binary", -> + bin_path = Path.resolve(__dirname + "/../../../bin/synctex") + synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/output.pdf" + @CommandRunner.run + .calledWith( + "#{@project_id}-#{@user_id}", + ['/opt/synctex', "pdf", synctex_path, @page, @h, @v], + "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}", + @Settings.clsi.docker.image, + 10000, + {}).should.equal true it "should call the callback with the parsed output", -> @callback From 43b1fe4b6821cdeaed26c168265e821f2863a934 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 6 Mar 2018 16:03:26 +0000 Subject: [PATCH 353/754] test new scripts on ci --- services/clsi/Jenkinsfile | 23 +++++++++++++++++++++-- services/clsi/Makefile | 10 ---------- services/clsi/docker-compose.ci.yml | 2 +- services/clsi/package.json | 4 ++-- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index 7a34ee75bd..bc9ba0142f 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -9,11 +9,30 @@ pipeline { } stages { - stage('CI') { + stage('Build') { steps { - sh 'make ci' + sh 'make build' } } + + 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 publish build') { + steps { + sh 'make publish' + } + } + stage('Publish build number') { steps { sh 'echo ${BRANCH_NAME}-${BUILD_NUMBER} > build_number.txt' diff --git a/services/clsi/Makefile b/services/clsi/Makefile index 063d96e602..74b78a3d42 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -33,16 +33,6 @@ build: publish: docker push quay.io/sharelatex/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) - -ci: - # On the CI server, we want to run our tests in the image that we - # have built for deployment, which is what the docker-compose.ci.yml - # override does. - PROJECT_NAME=$(PROJECT_NAME) \ - BRANCH_NAME=$(BRANCH_NAME) \ - BUILD_NUMBER=$(BUILD_NUMBER) \ - DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" \ - $(MAKE) build test publish .PHONY: clean test test_unit test_acceptance test_clean build publish diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 2ca2f9a32b..98715c2644 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -15,7 +15,7 @@ services: image: quay.io/sharelatex/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER extends: file: docker-compose-config.yml - service: dev + service: ci environment: REDIS_HOST: redis MONGO_HOST: mongo diff --git a/services/clsi/package.json b/services/clsi/package.json index fed32605ba..54181da596 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -9,9 +9,9 @@ "scripts": { "compile:test:acceptance": "coffee -o test/acceptance/js -c test/acceptance/coffee", "compile:test:smoke": "coffee -o test/smoke/js -c test/smoke/coffee", - "compile:app": "coffee -o app/js -c app/coffee && coffee -c app.coffee", + "compile:app": "coffee $COFFEE_OPTIONS -o app/js -c app/coffee && coffee $COFFEE_OPTIONS -c app.coffee", "compile": "npm run compile:app && npm run compile:test:acceptance && npm run compile:test:smoke", - "start": "npm run compile:app && node app.js", + "start": "npm run compile:app && node $NODE_APP_OPTIONS app.js", "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- $@", "test:unit:_run": "mocha --recursive --exit --reporter spec $@ test/unit/js", From c80c38b7a933503f60d744999bda94b7fe440e1f Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Wed, 7 Mar 2018 11:23:52 +0000 Subject: [PATCH 354/754] update build scripts --- services/clsi/Dockerfile | 5 +++-- services/clsi/docker-compose.ci.yml | 4 ++-- services/clsi/docker-compose.yml | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 62ef342538..ba3b3e1b21 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -4,6 +4,8 @@ COPY ./ /app WORKDIR /app +RUN rm -rf node_modules/* && make clean + RUN npm install @@ -20,8 +22,7 @@ WORKDIR /app # The app will run as the 'app' user, and so not have write permissions # on any files it doesn't need. RUN useradd --user-group --create-home --home-dir /app --shell /bin/false app - -RUN [ -e ./install_deps.sh ] && ./install_deps.sh +RUN ./install_deps.sh ENTRYPOINT ["/bin/sh", "entrypoint.sh"] CMD ["node","app.js"] diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 98715c2644..85512cf7f1 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -8,7 +8,7 @@ version: "2" services: test_unit: image: quay.io/sharelatex/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER - entrypoint: npm run test:unit:_run + command: npm run test:unit:_run test_acceptance: build: . @@ -22,7 +22,7 @@ services: depends_on: - redis - mongo - entrypoint: npm run test:acceptance:_run + command: npm run test:acceptance:_run redis: image: redis diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index cf90742761..1850687648 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -11,7 +11,7 @@ services: volumes: - .:/app working_dir: /app - entrypoint: npm run test:unit + command: npm run test:unit test_acceptance: build: . @@ -27,7 +27,7 @@ services: depends_on: - redis - mongo - entrypoint: npm run test:acceptance + command: npm run test:acceptance redis: image: redis From c08093848019748270806f292aed3884272fb411 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Wed, 7 Mar 2018 11:41:38 +0000 Subject: [PATCH 355/754] add docker ignore rather than make clean --- services/clsi/.dockerignore | 3 +++ services/clsi/Dockerfile | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 services/clsi/.dockerignore diff --git a/services/clsi/.dockerignore b/services/clsi/.dockerignore new file mode 100644 index 0000000000..a99835353f --- /dev/null +++ b/services/clsi/.dockerignore @@ -0,0 +1,3 @@ +node_modules/* +app.js +**/js/* diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index ba3b3e1b21..94247abfd4 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -4,8 +4,6 @@ COPY ./ /app WORKDIR /app -RUN rm -rf node_modules/* && make clean - RUN npm install From 3c2172434bd9abf009b916fe43dead41efc74e97 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 8 Mar 2018 15:38:17 +0000 Subject: [PATCH 356/754] have entrypoint kickoff download off texlive images install script exits without error if auth fails. --- services/clsi/Makefile | 2 +- services/clsi/bin/install_texlive_gce.sh | 13 +++++++++++++ services/clsi/entrypoint.sh | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100755 services/clsi/bin/install_texlive_gce.sh diff --git a/services/clsi/Makefile b/services/clsi/Makefile index 74b78a3d42..5c46c268ba 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -28,11 +28,11 @@ test_acceptance: test_clean # clear the database before each acceptance test run test_clean: $(DOCKER_COMPOSE) down -t 0 + build: docker build --pull --tag quay.io/sharelatex/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) . publish: docker push quay.io/sharelatex/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) - .PHONY: clean test test_unit test_acceptance test_clean build publish diff --git a/services/clsi/bin/install_texlive_gce.sh b/services/clsi/bin/install_texlive_gce.sh new file mode 100755 index 0000000000..20d2ca07f7 --- /dev/null +++ b/services/clsi/bin/install_texlive_gce.sh @@ -0,0 +1,13 @@ +#!/bin/sh +METADATA=http://metadata.google.internal./computeMetadata/v1 +SVC_ACCT=$METADATA/instance/service-accounts/default +ACCESS_TOKEN=$(curl -s -H 'Metadata-Flavor: Google' $SVC_ACCT/token | cut -d'"' -f 4) +if [ -z "$ACCESS_TOKEN" ]; then + echo "No acccess token to download texlive-full images from google container, continuing without downloading. This is likely not a google cloud enviroment." + exit 0 +fi +docker login -u '_token' -p $ACCESS_TOKEN https://gcr.io +docker pull --all-tags gcr.io/henry-terraform-admin/texlive-full #TODO NEED TO MAKE THIS AN ENV VAR +echo "Finished downloading texlive-full images" + + diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh index 588854ed1e..f4861a44a9 100755 --- a/services/clsi/entrypoint.sh +++ b/services/clsi/entrypoint.sh @@ -2,4 +2,5 @@ echo "Changing permissions of /var/run/docker.sock for sibling containers" chown root:docker /var/run/docker.sock +./bin/install_texlive_gce.sh exec runuser -u app "$@" \ No newline at end of file From b714ea193a0b715b49ea1c33236863a6b105b68e Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Wed, 14 Mar 2018 15:20:27 +0000 Subject: [PATCH 357/754] remove texlive docker images --- services/clsi/docker-runner | 1 - services/clsi/package.json | 11 +++++------ 2 files changed, 5 insertions(+), 7 deletions(-) delete mode 160000 services/clsi/docker-runner diff --git a/services/clsi/docker-runner b/services/clsi/docker-runner deleted file mode 160000 index 65ad62116f..0000000000 --- a/services/clsi/docker-runner +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 65ad62116fb1ba4fca978a6d1d6b89bf195e5166 diff --git a/services/clsi/package.json b/services/clsi/package.json index 54181da596..fdb5030723 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -7,8 +7,6 @@ "url": "https://github.com/sharelatex/clsi-sharelatex.git" }, "scripts": { - "compile:test:acceptance": "coffee -o test/acceptance/js -c test/acceptance/coffee", - "compile:test:smoke": "coffee -o test/smoke/js -c test/smoke/coffee", "compile:app": "coffee $COFFEE_OPTIONS -o app/js -c app/coffee && coffee $COFFEE_OPTIONS -c app.coffee", "compile": "npm run compile:app && npm run compile:test:acceptance && npm run compile:test:smoke", "start": "npm run compile:app && node $NODE_APP_OPTIONS app.js", @@ -16,10 +14,11 @@ "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- $@", "test:unit:_run": "mocha --recursive --exit --reporter spec $@ test/unit/js", "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- $@", - "compile:unit_tests": "[ -e test/unit ] && coffee -o test/unit/js -c test/unit/coffee || echo 'No unit tests to compile'", - "compile:acceptance_tests": "[ -e test/acceptance ] && coffee -o test/acceptance/js -c test/acceptance/coffee || echo 'No acceptance tests to compile'", - "compile:all": "npm run compile:app && npm run compile:unit_tests && npm run compile:acceptance_tests", - "nodemon": "nodemon --config nodemon.json" + "compile:unit_tests": "[ -e test/unit ] && coffee -o test/unit/js -c test/unit/coffee || echo 'No unit tests to compile, skipping'", + "compile:acceptance_tests": "[ -e test/acceptance ] && coffee -o test/acceptance/js -c test/acceptance/coffee || echo 'No acceptance tests to compile, skipping'", + "compile:all": "npm run compile:app && npm run compile:unit_tests && npm run compile:acceptance_tests && npm run compile:smoke_tests", + "nodemon": "nodemon --config nodemon.json", + "compile:smoke_tests": "[ -e test/smoke ] && coffee -o test/smoke/js -c test/smoke/coffee || echo 'No smoke tests to compile, skipping'" }, "author": "James Allen <james@sharelatex.com>", "dependencies": { From 6fe6924e6a1926c285bbc109d3eb7282195479a1 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 16 Mar 2018 15:25:36 +0000 Subject: [PATCH 358/754] make compiles dir --- services/clsi/install_deps.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/clsi/install_deps.sh b/services/clsi/install_deps.sh index 188394995a..511f291ccc 100755 --- a/services/clsi/install_deps.sh +++ b/services/clsi/install_deps.sh @@ -6,3 +6,6 @@ usermod -aG docker app mkdir -p /app/cache chown -R app:app /app/cache + +mkdir -p /app/compiles +chown -R app:app /app/compiles From faedf167044202b8f0552d3b771ec0be694a2672 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 16 Mar 2018 15:29:35 +0000 Subject: [PATCH 359/754] add log line for connecting to a db --- services/clsi/app/coffee/db.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/clsi/app/coffee/db.coffee b/services/clsi/app/coffee/db.coffee index cb633592ed..f32cdf7aef 100644 --- a/services/clsi/app/coffee/db.coffee +++ b/services/clsi/app/coffee/db.coffee @@ -5,6 +5,8 @@ logger = require "logger-sharelatex" options = _.extend {logging:false}, Settings.mysql.clsi +logger.log dbPath:Settings.mysql.clsi.storage, "connecting to db" + sequelize = new Sequelize( Settings.mysql.clsi.database, Settings.mysql.clsi.username, From 29dcef3b9e9ba27672b5b890e3abc09ac02a962b Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 16 Mar 2018 16:04:26 +0000 Subject: [PATCH 360/754] comment out synctex for moment --- services/clsi/app/coffee/DockerRunner.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/DockerRunner.coffee b/services/clsi/app/coffee/DockerRunner.coffee index f14b5c6497..3ee7f293de 100644 --- a/services/clsi/app/coffee/DockerRunner.coffee +++ b/services/clsi/app/coffee/DockerRunner.coffee @@ -108,8 +108,8 @@ module.exports = DockerRunner = _getContainerOptions: (command, image, volumes, timeout, environment) -> timeoutInSeconds = timeout / 1000 - if Settings.path?.synctexBinHostPath? - volumes[Settings.path.synctexBinHostPath] = "/opt/synctex:ro" + # if Settings.path?.synctexBinHostPath? + # volumes[Settings.path.synctexBinHostPath] = "/opt/synctex:ro" dockerVolumes = {} for hostVol, dockerVol of volumes From 48eb7129a76ab689e85b1c0a6dc20a650724ef77 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 16 Mar 2018 16:22:39 +0000 Subject: [PATCH 361/754] add synctex back in --- services/clsi/app/coffee/DockerRunner.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/DockerRunner.coffee b/services/clsi/app/coffee/DockerRunner.coffee index 3ee7f293de..f14b5c6497 100644 --- a/services/clsi/app/coffee/DockerRunner.coffee +++ b/services/clsi/app/coffee/DockerRunner.coffee @@ -108,8 +108,8 @@ module.exports = DockerRunner = _getContainerOptions: (command, image, volumes, timeout, environment) -> timeoutInSeconds = timeout / 1000 - # if Settings.path?.synctexBinHostPath? - # volumes[Settings.path.synctexBinHostPath] = "/opt/synctex:ro" + if Settings.path?.synctexBinHostPath? + volumes[Settings.path.synctexBinHostPath] = "/opt/synctex:ro" dockerVolumes = {} for hostVol, dockerVol of volumes From 4179d19200244ef214daff3196a2ce57a81bcde5 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 16 Mar 2018 16:50:30 +0000 Subject: [PATCH 362/754] move synctex into a directory for simple mounting --- services/clsi/.gitignore | 1 - services/clsi/bin/{ => synctex}/synctex | Bin 2 files changed, 1 deletion(-) rename services/clsi/bin/{ => synctex}/synctex (100%) diff --git a/services/clsi/.gitignore b/services/clsi/.gitignore index 476f2d1feb..21c1ecae1f 100644 --- a/services/clsi/.gitignore +++ b/services/clsi/.gitignore @@ -13,5 +13,4 @@ cache .vagrant db.sqlite config/* -bin/synctex npm-debug.log diff --git a/services/clsi/bin/synctex b/services/clsi/bin/synctex/synctex similarity index 100% rename from services/clsi/bin/synctex rename to services/clsi/bin/synctex/synctex From 6d21ab734a67790a12ab70a3553ccaacc9d37467 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 16 Mar 2018 17:10:56 +0000 Subject: [PATCH 363/754] try copying synctex betwen directories --- services/clsi/bin/{synctex => }/synctex | Bin services/clsi/entrypoint.sh | 4 ++++ 2 files changed, 4 insertions(+) rename services/clsi/bin/{synctex => }/synctex (100%) diff --git a/services/clsi/bin/synctex/synctex b/services/clsi/bin/synctex similarity index 100% rename from services/clsi/bin/synctex/synctex rename to services/clsi/bin/synctex diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh index f4861a44a9..d50a70a386 100755 --- a/services/clsi/entrypoint.sh +++ b/services/clsi/entrypoint.sh @@ -1,6 +1,10 @@ #!/bin/sh echo "Changing permissions of /var/run/docker.sock for sibling containers" +cp /var/clsi/bin/synctex /var/clsi/bin/synctex-mount/synctex + chown root:docker /var/run/docker.sock +chown app:app /app/compiles + ./bin/install_texlive_gce.sh exec runuser -u app "$@" \ No newline at end of file From 2d03cb3eb11aa2ce6193c1316c3de9f7951a086f Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 16 Mar 2018 17:30:11 +0000 Subject: [PATCH 364/754] added debugging and new moving commands --- services/clsi/app.coffee | 4 ++++ services/clsi/entrypoint.sh | 8 ++++++-- services/clsi/install_deps.sh | 5 +---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 527b17e9d6..7bd056460e 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -144,6 +144,10 @@ app.get "/smoke_test_force", (req, res)-> smokeTest.run(require.resolve(__dirname + "/test/smoke/js/SmokeTests.js"))(req, res) +#TODO delete this +app.get "/settings", (req, res)-> + res.json(Settings) + profiler = require "v8-profiler" app.get "/profile", (req, res) -> time = parseInt(req.query.time || "1000") diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh index d50a70a386..ecb28d1180 100755 --- a/services/clsi/entrypoint.sh +++ b/services/clsi/entrypoint.sh @@ -1,10 +1,14 @@ #!/bin/sh echo "Changing permissions of /var/run/docker.sock for sibling containers" -cp /var/clsi/bin/synctex /var/clsi/bin/synctex-mount/synctex chown root:docker /var/run/docker.sock -chown app:app /app/compiles + +mkdir -p /app/cache +chown -R app:app /app/cache + +mkdir -p /app/compiles +chown -R app:app /app/compiles ./bin/install_texlive_gce.sh exec runuser -u app "$@" \ No newline at end of file diff --git a/services/clsi/install_deps.sh b/services/clsi/install_deps.sh index 511f291ccc..20ab946523 100755 --- a/services/clsi/install_deps.sh +++ b/services/clsi/install_deps.sh @@ -4,8 +4,5 @@ apt-get install poppler-utils vim ghostscript --yes npm rebuild usermod -aG docker app -mkdir -p /app/cache -chown -R app:app /app/cache +cp /app/bin/synctex /app/bin/synctex-mount/synctex -mkdir -p /app/compiles -chown -R app:app /app/compiles From fc594f7d8ea5ce3da4dafb9a0b21a7884d56ac8d Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 16 Mar 2018 17:37:36 +0000 Subject: [PATCH 365/754] add logging of docker options --- services/clsi/app/coffee/DockerRunner.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/app/coffee/DockerRunner.coffee b/services/clsi/app/coffee/DockerRunner.coffee index f14b5c6497..8323d6c454 100644 --- a/services/clsi/app/coffee/DockerRunner.coffee +++ b/services/clsi/app/coffee/DockerRunner.coffee @@ -146,6 +146,7 @@ module.exports = DockerRunner = "SecurityOpt": ["no-new-privileges"] if Settings.clsi.docker.seccomp_profile? options.HostConfig.SecurityOpt.push "seccomp=#{Settings.clsi.docker.seccomp_profile}" + logger.log options:options, "options for running docker container" return options _fingerprintContainer: (containerOptions) -> From 3b2deaecf79de12ab8c456256cfb3dbfe11ba388 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 16 Mar 2018 17:40:10 +0000 Subject: [PATCH 366/754] mkdir the /app/bin/synctex-mount --- services/clsi/install_deps.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/install_deps.sh b/services/clsi/install_deps.sh index 20ab946523..76aaf090be 100755 --- a/services/clsi/install_deps.sh +++ b/services/clsi/install_deps.sh @@ -4,5 +4,6 @@ apt-get install poppler-utils vim ghostscript --yes npm rebuild usermod -aG docker app +mkdir /app/bin/synctex-mount cp /app/bin/synctex /app/bin/synctex-mount/synctex From bde294b8c2bc1b6aba0785216ffb1f40f7646c2e Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 16 Mar 2018 17:48:55 +0000 Subject: [PATCH 367/754] chown synctex and add the creation of directories in --- services/clsi/bin/install_texlive_gce.sh | 1 + services/clsi/install_deps.sh | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/services/clsi/bin/install_texlive_gce.sh b/services/clsi/bin/install_texlive_gce.sh index 20d2ca07f7..2ea5f99b3a 100755 --- a/services/clsi/bin/install_texlive_gce.sh +++ b/services/clsi/bin/install_texlive_gce.sh @@ -8,6 +8,7 @@ if [ -z "$ACCESS_TOKEN" ]; then fi docker login -u '_token' -p $ACCESS_TOKEN https://gcr.io docker pull --all-tags gcr.io/henry-terraform-admin/texlive-full #TODO NEED TO MAKE THIS AN ENV VAR +cp /app/bin/synctex /app/bin/synctex-mount/synctex echo "Finished downloading texlive-full images" diff --git a/services/clsi/install_deps.sh b/services/clsi/install_deps.sh index 76aaf090be..e1f3ce4671 100755 --- a/services/clsi/install_deps.sh +++ b/services/clsi/install_deps.sh @@ -4,6 +4,10 @@ apt-get install poppler-utils vim ghostscript --yes npm rebuild usermod -aG docker app -mkdir /app/bin/synctex-mount -cp /app/bin/synctex /app/bin/synctex-mount/synctex +mkdir -p /app/cache +chown -R app:app /app/cache +mkdir -p /app/compiles +chown -R app:app /app/compiles + +chown -R app:app /app/bin/synctex From f496de6d132c5da6024ece19e4a8dea2765a9b7c Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 16 Mar 2018 18:11:46 +0000 Subject: [PATCH 368/754] don't put synctex in as a volume --- services/clsi/app/coffee/DockerRunner.coffee | 9 +++++---- services/clsi/bin/install_texlive_gce.sh | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/services/clsi/app/coffee/DockerRunner.coffee b/services/clsi/app/coffee/DockerRunner.coffee index 8323d6c454..635243fd02 100644 --- a/services/clsi/app/coffee/DockerRunner.coffee +++ b/services/clsi/app/coffee/DockerRunner.coffee @@ -108,9 +108,6 @@ module.exports = DockerRunner = _getContainerOptions: (command, image, volumes, timeout, environment) -> timeoutInSeconds = timeout / 1000 - if Settings.path?.synctexBinHostPath? - volumes[Settings.path.synctexBinHostPath] = "/opt/synctex:ro" - dockerVolumes = {} for hostVol, dockerVol of volumes dockerVolumes[dockerVol] = {} @@ -118,7 +115,6 @@ module.exports = DockerRunner = if volumes[hostVol].slice(-3).indexOf(":r") == -1 volumes[hostVol] = "#{dockerVol}:rw" - # merge settings and environment parameter env = {} for src in [Settings.clsi.docker.env, environment or {}] @@ -144,8 +140,13 @@ module.exports = DockerRunner = "Ulimits": [{'Name': 'cpu', 'Soft': timeoutInSeconds+5, 'Hard': timeoutInSeconds+10}] "CapDrop": "ALL" "SecurityOpt": ["no-new-privileges"] + if Settings.clsi.docker.seccomp_profile? options.HostConfig.SecurityOpt.push "seccomp=#{Settings.clsi.docker.seccomp_profile}" + + if Settings.path?.synctexBinHostPath? + options["HostConfig"]["Binds"].push("#{Settings.path.synctexBinHostPath}:/opt/synctex:ro") + logger.log options:options, "options for running docker container" return options diff --git a/services/clsi/bin/install_texlive_gce.sh b/services/clsi/bin/install_texlive_gce.sh index 2ea5f99b3a..85ab709694 100755 --- a/services/clsi/bin/install_texlive_gce.sh +++ b/services/clsi/bin/install_texlive_gce.sh @@ -9,6 +9,7 @@ fi docker login -u '_token' -p $ACCESS_TOKEN https://gcr.io docker pull --all-tags gcr.io/henry-terraform-admin/texlive-full #TODO NEED TO MAKE THIS AN ENV VAR cp /app/bin/synctex /app/bin/synctex-mount/synctex + echo "Finished downloading texlive-full images" From f6663f2f5fd328a2b8c1202ac406faacad048fa2 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 19 Mar 2018 09:51:26 +0000 Subject: [PATCH 369/754] try running as root --- services/clsi/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh index ecb28d1180..6722a41843 100755 --- a/services/clsi/entrypoint.sh +++ b/services/clsi/entrypoint.sh @@ -11,4 +11,4 @@ mkdir -p /app/compiles chown -R app:app /app/compiles ./bin/install_texlive_gce.sh -exec runuser -u app "$@" \ No newline at end of file +exec "$@" \ No newline at end of file From edf0125c2f4a20724c4ef9f474838805d7a1a476 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 19 Mar 2018 12:56:53 +0000 Subject: [PATCH 370/754] run as app user and chmod 777 compiles dir --- services/clsi/entrypoint.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh index 6722a41843..ee1df04b90 100755 --- a/services/clsi/entrypoint.sh +++ b/services/clsi/entrypoint.sh @@ -9,6 +9,7 @@ chown -R app:app /app/cache mkdir -p /app/compiles chown -R app:app /app/compiles +chmod -R 777 /app/compiles #TODO why do I need this? ./bin/install_texlive_gce.sh -exec "$@" \ No newline at end of file +exec runuser -u app "$@" \ No newline at end of file From 9731267ae5873f7695c6c08d9179a4d6a6d03b1e Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 19 Mar 2018 14:10:45 +0000 Subject: [PATCH 371/754] try a build with node user --- services/clsi/Dockerfile | 1 - services/clsi/entrypoint.sh | 7 +++---- services/clsi/install_deps.sh | 8 ++++---- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 94247abfd4..c30764d78b 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -19,7 +19,6 @@ WORKDIR /app # All app and node_modules will be owned by root. # The app will run as the 'app' user, and so not have write permissions # on any files it doesn't need. -RUN useradd --user-group --create-home --home-dir /app --shell /bin/false app RUN ./install_deps.sh ENTRYPOINT ["/bin/sh", "entrypoint.sh"] diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh index ee1df04b90..423a5d284c 100755 --- a/services/clsi/entrypoint.sh +++ b/services/clsi/entrypoint.sh @@ -5,11 +5,10 @@ echo "Changing permissions of /var/run/docker.sock for sibling containers" chown root:docker /var/run/docker.sock mkdir -p /app/cache -chown -R app:app /app/cache +chown -R node:node /app/cache mkdir -p /app/compiles -chown -R app:app /app/compiles -chmod -R 777 /app/compiles #TODO why do I need this? +chown -R node:node /app/compiles ./bin/install_texlive_gce.sh -exec runuser -u app "$@" \ No newline at end of file +exec runuser -u node "$@" \ No newline at end of file diff --git a/services/clsi/install_deps.sh b/services/clsi/install_deps.sh index e1f3ce4671..4b06f22d88 100755 --- a/services/clsi/install_deps.sh +++ b/services/clsi/install_deps.sh @@ -2,12 +2,12 @@ wget -qO- https://get.docker.com/ | sh apt-get install poppler-utils vim ghostscript --yes npm rebuild -usermod -aG docker app +usermod -aG docker node mkdir -p /app/cache -chown -R app:app /app/cache +chown -R node:node /app/cache mkdir -p /app/compiles -chown -R app:app /app/compiles +chown -R node:node /app/compiles -chown -R app:app /app/bin/synctex +chown -R node:node /app/bin/synctex From 4a5dc9d946b3f4e3fe0820a1f6a2e2e96823e865 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 19 Mar 2018 14:22:18 +0000 Subject: [PATCH 372/754] ammend comment --- services/clsi/Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index c30764d78b..68e1a4a920 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -15,9 +15,8 @@ COPY --from=app /app /app WORKDIR /app - # All app and node_modules will be owned by root. -# The app will run as the 'app' user, and so not have write permissions +# The app will run as the 'node' user, and so not have write permissions # on any files it doesn't need. RUN ./install_deps.sh ENTRYPOINT ["/bin/sh", "entrypoint.sh"] From 41e39458d18b82dbca61f97d3bb4fddb9a4937d2 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 20 Mar 2018 13:48:12 +0000 Subject: [PATCH 373/754] add smoke test env var --- services/clsi/config/settings.defaults.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index 3100a5d712..de853f5924 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -25,7 +25,7 @@ module.exports = clsi: url: "http://#{process.env['CLSI_HOST'] or 'localhost'}:3013" - smokeTest: false + smokeTest: process.env["SMOKE_TEST"] or false project_cache_length_ms: 1000 * 60 * 60 * 24 parallelFileDownloads:1 From 551e8d36b4d827b35d32dccab1b00b38cb70198b Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 29 Mar 2018 12:12:29 +0100 Subject: [PATCH 374/754] update build script and add load balancer agent --- services/clsi/.viminfo | 35 + services/clsi/Dockerfile | 4 - services/clsi/Makefile | 6 +- services/clsi/app.coffee | 41 + services/clsi/debug | 5 + services/clsi/docker-compose.ci.yml | 6 +- services/clsi/docker-compose.yml | 6 +- services/clsi/entrypoint.sh | 2 + services/clsi/nodemon.json | 3 +- services/clsi/package-lock.json | 3661 +----------------------- services/clsi/package.json | 6 +- services/clsi/patch-texlive-dockerfile | 3 + services/clsi/synctex.profile | 34 + 13 files changed, 184 insertions(+), 3628 deletions(-) create mode 100644 services/clsi/.viminfo create mode 100755 services/clsi/debug create mode 100644 services/clsi/patch-texlive-dockerfile create mode 100644 services/clsi/synctex.profile diff --git a/services/clsi/.viminfo b/services/clsi/.viminfo new file mode 100644 index 0000000000..78c0129851 --- /dev/null +++ b/services/clsi/.viminfo @@ -0,0 +1,35 @@ +# This viminfo file was generated by Vim 7.4. +# You may edit it if you're careful! + +# Value of 'encoding' when this file was written +*encoding=latin1 + + +# hlsearch on (H) or off (h): +~h +# Command Line History (newest to oldest): +:x + +# Search String History (newest to oldest): + +# Expression History (newest to oldest): + +# Input Line History (newest to oldest): + +# Input Line History (newest to oldest): + +# Registers: + +# File marks: +'0 1 0 ~/hello + +# Jumplist (newest first): +-' 1 0 ~/hello + +# History of marks within files (newest to oldest): + +> ~/hello + " 1 0 + ^ 1 1 + . 1 0 + + 1 0 diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 68e1a4a920..ed1f2c6e0c 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -14,10 +14,6 @@ FROM node:6.13.0 COPY --from=app /app /app WORKDIR /app - -# All app and node_modules will be owned by root. -# The app will run as the 'node' user, and so not have write permissions -# on any files it doesn't need. RUN ./install_deps.sh ENTRYPOINT ["/bin/sh", "entrypoint.sh"] diff --git a/services/clsi/Makefile b/services/clsi/Makefile index 5c46c268ba..2a1196c2ca 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -21,13 +21,13 @@ clean: test: test_unit test_acceptance test_unit: - @[ -d test/unit ] && $(DOCKER_COMPOSE) run --rm test_unit -- ${MOCHA_ARGS} || echo "clsi has no unit tests" + @[ ! -d test/unit ] && echo "clsi has no unit tests" || $(DOCKER_COMPOSE) run --rm test_unit -- ${MOCHA_ARGS} test_acceptance: test_clean # clear the database before each acceptance test run - @[ -d test/acceptance ] && $(DOCKER_COMPOSE) run --rm test_acceptance -- ${MOCHA_ARGS} || echo "clsi has no acceptance tests" + @[ ! -d test/acceptance ] && echo "clsi has no acceptance tests" || $(DOCKER_COMPOSE) run --rm test_acceptance -- ${MOCHA_ARGS} test_clean: - $(DOCKER_COMPOSE) down -t 0 + $(DOCKER_COMPOSE) down -t -v 0 build: docker build --pull --tag quay.io/sharelatex/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) . diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 7bd056460e..a3f4ef17e1 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -169,13 +169,54 @@ app.use (error, req, res, next) -> logger.error {err: error, url: req.url}, "server error" res.sendStatus(error?.statusCode || 500) +net = require "net" +os = require "os" + +STATE = "up" + +server = net.createServer (socket) -> + socket.on "error", (err)-> + if err.code == "ECONNRESET" + # this always comes up, we don't know why + return + logger.err err:err, "error with socket on load check" + socket.destroy() + + if STATE == "up" and settings.load_balancer_agent.report_load + currentLoad = os.loadavg()[0] + + # staging clis's have 1 cpu core only + if os.cpus().length == 1 + availableWorkingCpus = 1 + else + availableWorkingCpus = os.cpus().length - 1 + + freeLoad = availableWorkingCpus - currentLoad + freeLoadPercentage = Math.round((freeLoad / availableWorkingCpus) * 100) + if freeLoadPercentage <= 0 + freeLoadPercentage = 1 # when its 0 the server is set to drain and will move projects to different servers + socket.write("up, #{freeLoadPercentage}%\n", "ASCII") + socket.end() + else + socket.write("#{STATE}\n", "ASCII") + socket.end() + + + + port = (Settings.internal?.clsi?.port or 3013) host = (Settings.internal?.clsi?.host or "localhost") +load_port = settings.internal.clsi.load_port or 3048 + + if !module.parent # Called directly app.listen port, host, (error) -> logger.info "CLSI starting up, listening on #{host}:#{port}" + server.listen load_port, host, (error) -> + throw error if error? + logger.info "Load agent listening on load port #{load_port}" module.exports = app diff --git a/services/clsi/debug b/services/clsi/debug new file mode 100755 index 0000000000..fcc371c36e --- /dev/null +++ b/services/clsi/debug @@ -0,0 +1,5 @@ +#!/bin/bash +echo "hello world" +sleep 3 +echo "awake" +/opt/synctex pdf /compile/output.pdf 1 100 200 diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 85512cf7f1..370e2a80a2 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -8,7 +8,8 @@ version: "2" services: test_unit: image: quay.io/sharelatex/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER - command: npm run test:unit:_run + user: node + entrypoint: npm run test:unit:_run test_acceptance: build: . @@ -22,7 +23,8 @@ services: depends_on: - redis - mongo - command: npm run test:acceptance:_run + user: node + entrypoint: npm run test:acceptance:_run redis: image: redis diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 1850687648..32cbf61e55 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -11,7 +11,8 @@ services: volumes: - .:/app working_dir: /app - command: npm run test:unit + user: node + entrypoint: npm run test:unit test_acceptance: build: . @@ -24,10 +25,11 @@ services: environment: REDIS_HOST: redis MONGO_HOST: mongo + user: node depends_on: - redis - mongo - command: npm run test:acceptance + entrypoint: npm run test:acceptance redis: image: redis diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh index 423a5d284c..ee5ea47f90 100755 --- a/services/clsi/entrypoint.sh +++ b/services/clsi/entrypoint.sh @@ -11,4 +11,6 @@ mkdir -p /app/compiles chown -R node:node /app/compiles ./bin/install_texlive_gce.sh +echo "HELOOOo" +echo "$@" exec runuser -u node "$@" \ No newline at end of file diff --git a/services/clsi/nodemon.json b/services/clsi/nodemon.json index 9a3be8d966..815f39223e 100644 --- a/services/clsi/nodemon.json +++ b/services/clsi/nodemon.json @@ -10,7 +10,8 @@ }, "watch": [ "app/coffee/", - "app.coffee" + "app.coffee", + "config/" ], "ext": "coffee" } diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index a5b7847cdb..d00b345e5b 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -4,70 +4,12 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "JSONStream": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.10.0.tgz", - "integrity": "sha1-dDSdDYlSK3HzDwoD/5vSDKbxKsA=", - "requires": { - "jsonparse": "0.0.5", - "through": "2.3.8" - } - }, - "async": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/async/-/async-0.2.9.tgz", - "integrity": "sha1-32MGD789Myhqdqr21Vophtn/hhk=" - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "optional": true }, - "bl": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz", - "integrity": "sha1-ysMo977kVzDUBLaSID/LWQ4XLV4=", - "requires": { - "readable-stream": "2.3.3" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "requires": { - "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "isarray": "1.0.0", - "process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "string_decoder": "1.0.3", - "util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" - } - } - } - }, - "block-stream": { - "version": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", - "requires": { - "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" - } - }, "body-parser": { "version": "1.18.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", @@ -224,292 +166,12 @@ "concat-map": "0.0.1" } }, - "browser-stdout": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", - "dev": true - }, - "bunyan": { - "version": "0.22.3", - "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-0.22.3.tgz", - "integrity": "sha1-ehncG0yMZF90AkGnQPIkUUfGfsI=", - "dev": true, - "requires": { - "mv": "2.1.1" - }, - "dependencies": { - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "optional": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true, - "optional": true - }, - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "dev": true, - "optional": true, - "requires": { - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "optional": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "optional": true, - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true, - "optional": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "mv": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", - "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", - "dev": true, - "optional": true, - "requires": { - "mkdirp": "0.5.1", - "ncp": "2.0.0", - "rimraf": "2.4.5" - } - }, - "ncp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "optional": true - }, - "rimraf": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", - "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", - "dev": true, - "optional": true, - "requires": { - "glob": "6.0.4" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - } - } - }, - "chai": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-1.8.1.tgz", - "integrity": "sha1-zHeGbV5+vKK9dRRLHtw3Coh4X3I=", - "dev": true, - "requires": { - "assertion-error": "1.0.0", - "deep-eql": "0.1.3" - }, - "dependencies": { - "assertion-error": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", - "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", - "dev": true - }, - "deep-eql": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", - "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", - "dev": true, - "requires": { - "type-detect": "0.1.1" - } - }, - "type-detect": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", - "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", - "dev": true - } - } - }, - "coffee-script": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", - "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=" - }, - "commander": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz", - "integrity": "sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg=" - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "optional": true }, - "concat-stream": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", - "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", - "requires": { - "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "readable-stream": "2.0.6", - "typedarray": "0.0.6" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", - "requires": { - "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "isarray": "1.0.0", - "process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" - } - } - } - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" - }, - "core-util-is": { - "version": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "debug": { - "version": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", - "requires": { - "ms": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" - } - }, - "diff": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", - "integrity": "sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ=" - }, - "docker-modem": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-1.0.4.tgz", - "integrity": "sha1-JEJUsiax1/YCJfgjlb2FGQz3Ves=", - "requires": { - "JSONStream": "0.10.0", - "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "readable-stream": "1.0.34", - "split-ca": "1.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "requires": { - "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "isarray": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" - } - } - } - }, - "dockerode": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-2.5.3.tgz", - "integrity": "sha1-Wsw8Bx96JuRDpbWM83U+8vPCvAQ=", - "requires": { - "concat-stream": "1.5.2", - "docker-modem": "1.0.4", - "tar-fs": "1.12.0" - } - }, "dtrace-provider": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.6.0.tgz", @@ -519,20 +181,6 @@ "nan": "2.8.0" } }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=", - "requires": { - "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, "express": { "version": "4.16.2", "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", @@ -851,132 +499,6 @@ } } }, - "fs-extra": { - "version": "0.16.5", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.16.5.tgz", - "integrity": "sha1-GtZh+myGyWCM0bSe/G/Og0k5p1A=", - "requires": { - "graceful-fs": "3.0.11", - "jsonfile": "2.4.0", - "rimraf": "2.6.2" - }, - "dependencies": { - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "graceful-fs": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", - "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", - "requires": { - "natives": "1.1.1" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "requires": { - "graceful-fs": "4.1.11" - }, - "dependencies": { - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "optional": true - } - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "1.1.11" - } - }, - "natives": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.1.tgz", - "integrity": "sha512-8eRaxn8u/4wN8tGkhlc2cgwwvOLMLUMUn4IYTexMgWd+LyUDfeXVkk2ygQR0hvIHbJQXgHujia3ieUUDwNGkEA==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "requires": { - "glob": "7.1.2" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - } - } - }, "glob": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", @@ -1013,562 +535,6 @@ } } }, - "graceful-fs": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", - "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=" - }, - "growl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", - "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=" - }, - "grunt": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", - "integrity": "sha1-VpN81RlDJK3/bSB2MYMqnWuk5/A=", - "dev": true, - "requires": { - "async": "0.1.22", - "coffee-script": "1.3.3", - "colors": "0.6.2", - "dateformat": "1.0.2-1.2.3", - "eventemitter2": "0.4.14", - "exit": "0.1.2", - "findup-sync": "0.1.3", - "getobject": "0.1.0", - "glob": "3.1.21", - "grunt-legacy-log": "0.1.3", - "grunt-legacy-util": "0.2.0", - "hooker": "0.2.3", - "iconv-lite": "0.2.11", - "js-yaml": "2.0.5", - "lodash": "0.9.2", - "minimatch": "0.2.14", - "nopt": "1.0.10", - "rimraf": "2.2.8", - "underscore.string": "2.2.1", - "which": "1.0.9" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "argparse": { - "version": "0.1.16", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", - "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", - "dev": true, - "requires": { - "underscore": "1.7.0", - "underscore.string": "2.4.0" - }, - "dependencies": { - "underscore.string": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", - "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=", - "dev": true - } - } - }, - "async": { - "version": "0.1.22", - "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", - "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=", - "dev": true - }, - "coffee-script": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz", - "integrity": "sha1-FQ1rTLUiiUNp7+1qIQHCC8f0pPQ=", - "dev": true - }, - "colors": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", - "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", - "dev": true - }, - "dateformat": { - "version": "1.0.2-1.2.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", - "integrity": "sha1-sCIMAt6YYXQztyhRz0fePfLNvuk=", - "dev": true - }, - "esprima": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", - "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", - "dev": true - }, - "eventemitter2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", - "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", - "dev": true - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "findup-sync": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", - "integrity": "sha1-fz56l7gjksZTvwZYm9hRkOk8NoM=", - "dev": true, - "requires": { - "glob": "3.2.11", - "lodash": "2.4.2" - }, - "dependencies": { - "glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", - "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "minimatch": "0.3.0" - } - }, - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", - "dev": true - }, - "minimatch": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", - "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", - "dev": true, - "requires": { - "lru-cache": "2.7.3", - "sigmund": "1.0.1" - } - } - } - }, - "getobject": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", - "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", - "dev": true - }, - "glob": { - "version": "3.1.21", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", - "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", - "dev": true, - "requires": { - "graceful-fs": "1.2.3", - "inherits": "1.0.2", - "minimatch": "0.2.14" - }, - "dependencies": { - "inherits": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", - "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", - "dev": true - } - } - }, - "graceful-fs": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", - "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", - "dev": true - }, - "grunt-legacy-log": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", - "integrity": "sha1-7ClCboAwIa9ZAp+H0vnNczWgVTE=", - "dev": true, - "requires": { - "colors": "0.6.2", - "grunt-legacy-log-utils": "0.1.1", - "hooker": "0.2.3", - "lodash": "2.4.2", - "underscore.string": "2.3.3" - }, - "dependencies": { - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", - "dev": true - }, - "underscore.string": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", - "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", - "dev": true - } - } - }, - "grunt-legacy-log-utils": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", - "integrity": "sha1-wHBrndkGThFvNvI/5OawSGcsD34=", - "dev": true, - "requires": { - "colors": "0.6.2", - "lodash": "2.4.2", - "underscore.string": "2.3.3" - }, - "dependencies": { - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", - "dev": true - }, - "underscore.string": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", - "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", - "dev": true - } - } - }, - "grunt-legacy-util": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", - "integrity": "sha1-kzJIhNv343qf98Am3/RR2UqeVUs=", - "dev": true, - "requires": { - "async": "0.1.22", - "exit": "0.1.2", - "getobject": "0.1.0", - "hooker": "0.2.3", - "lodash": "0.9.2", - "underscore.string": "2.2.1", - "which": "1.0.9" - } - }, - "hooker": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", - "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", - "dev": true - }, - "iconv-lite": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", - "integrity": "sha1-HOYKOleGSiktEyH/RgnKS7llrcg=", - "dev": true - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "js-yaml": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", - "integrity": "sha1-olrmUJmZ6X3yeMZxnaEb0Gh3Q6g=", - "dev": true, - "requires": { - "argparse": "0.1.16", - "esprima": "1.0.4" - } - }, - "lodash": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", - "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=", - "dev": true - }, - "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", - "dev": true - }, - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "dev": true, - "requires": { - "lru-cache": "2.7.3", - "sigmund": "1.0.1" - } - }, - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "dev": true, - "requires": { - "abbrev": "1.1.1" - } - }, - "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", - "dev": true - }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", - "dev": true - }, - "underscore": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", - "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", - "dev": true - }, - "underscore.string": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", - "integrity": "sha1-18D6KvXVoaZ/QlPa7pgTLnM/Dxk=", - "dev": true - }, - "which": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", - "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=", - "dev": true - } - } - }, - "grunt-bunyan": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/grunt-bunyan/-/grunt-bunyan-0.5.0.tgz", - "integrity": "sha1-aCnXbgGZQ9owQTk2MaNuKsgpsWw=", - "dev": true, - "requires": { - "lodash": "2.4.2" - }, - "dependencies": { - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", - "dev": true - } - } - }, - "grunt-contrib-clean": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-0.5.0.tgz", - "integrity": "sha1-9T397ghJsce0Dp67umn0jExgecU=", - "dev": true, - "requires": { - "rimraf": "2.2.8" - }, - "dependencies": { - "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", - "dev": true - } - } - }, - "grunt-contrib-coffee": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-coffee/-/grunt-contrib-coffee-0.7.0.tgz", - "integrity": "sha1-ixIme3TnM4sfKcW4txj7n4mYLxM=", - "dev": true, - "requires": { - "coffee-script": "1.6.3" - }, - "dependencies": { - "coffee-script": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.3.tgz", - "integrity": "sha1-Y1XTLPGwTN/2tITl5xF4Ky8MOb4=", - "dev": true - } - } - }, - "grunt-execute": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/grunt-execute/-/grunt-execute-0.1.5.tgz", - "integrity": "sha1-yX64lDYS/vu3L749Mu+VIzxfouk=", - "dev": true - }, - "grunt-mkdir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-mkdir/-/grunt-mkdir-1.0.0.tgz", - "integrity": "sha1-c+GiasJKCFljY/TdlUsNMkheWOk=" - }, - "grunt-mocha-test": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/grunt-mocha-test/-/grunt-mocha-test-0.8.2.tgz", - "integrity": "sha1-emGEuYhg0Phb3qrWvqob199bvus=", - "dev": true, - "requires": { - "mocha": "1.14.0" - }, - "dependencies": { - "commander": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz", - "integrity": "sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg=", - "dev": true - }, - "diff": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", - "integrity": "sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ=", - "dev": true - }, - "glob": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", - "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", - "dev": true, - "requires": { - "graceful-fs": "2.0.3", - "inherits": "2.0.3", - "minimatch": "0.2.14" - } - }, - "graceful-fs": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", - "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=", - "dev": true - }, - "growl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", - "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=", - "dev": true - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "jade": { - "version": "0.26.3", - "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", - "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", - "dev": true, - "requires": { - "commander": "0.6.1", - "mkdirp": "0.3.0" - }, - "dependencies": { - "commander": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", - "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", - "dev": true - }, - "mkdirp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", - "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", - "dev": true - } - } - }, - "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", - "dev": true - }, - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "dev": true, - "requires": { - "lru-cache": "2.7.3", - "sigmund": "1.0.1" - } - }, - "mocha": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.14.0.tgz", - "integrity": "sha1-cT223FAAGRqdA1gZXQkIeQ7LYVc=", - "dev": true, - "requires": { - "commander": "2.0.0", - "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "diff": "1.0.7", - "glob": "3.2.3", - "growl": "1.7.0", - "jade": "0.26.3", - "mkdirp": "0.3.5" - } - }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", - "dev": true - } - } - }, - "grunt-shell": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/grunt-shell/-/grunt-shell-0.6.4.tgz", - "integrity": "sha1-5KbRuSkSd2/ZOimcX2zGTpUlNlw=", - "dev": true, - "requires": { - "chalk": "0.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-0.2.0.tgz", - "integrity": "sha1-NZq0sV3NZLptdHNLcsNjYKmvLBk=", - "dev": true - }, - "chalk": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.3.0.tgz", - "integrity": "sha1-HJhDdzfxGZ68wdTEj9Qbn5yOjyM=", - "dev": true, - "requires": { - "ansi-styles": "0.2.0", - "has-color": "0.1.7" - } - }, - "has-color": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", - "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", - "dev": true - } - } - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, - "heapdump": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/heapdump/-/heapdump-0.3.9.tgz", - "integrity": "sha1-A8dOsN9dZ74Jgug0KbqcnSs7f3g=" - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1595,56 +561,12 @@ } } }, - "inherits": { - "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "isarray": { - "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "jade": { - "version": "0.26.3", - "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", - "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "load-balancer-agent-sharelatex": { + "version": "git+https://github.com/sharelatex/load-balancer-agent-sharelatex.git#241a128c1e9fdec384186061b27704c8891d98ef", "requires": { - "commander": "0.6.1", - "mkdirp": "0.3.0" - }, - "dependencies": { - "commander": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", - "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=" - }, - "mkdirp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", - "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=" - } - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsonparse": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz", - "integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=" - }, - "lockfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.3.tgz", - "integrity": "sha1-Jjj8OaAzHpysGgS3F5mTHJxQ33k=" - }, - "logger-sharelatex": { - "version": "git+https://github.com/sharelatex/logger-sharelatex.git#9ee7b52eb2bbd8fcbb1e2c708587c1e93fd4c733", - "requires": { - "bunyan": "1.5.1", - "coffee-script": "1.4.0", - "raven": "1.2.1" + "express": "4.16.2", + "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#405cf1350ca5ae5f7bb1e7091e28d5aa3aaaa72c", + "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#b4fb8404c5de571d029bf4c29e96a60b21206f94" }, "dependencies": { "bunyan": { @@ -1661,66 +583,53 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.4.0.tgz", "integrity": "sha1-XjvIqsJsAajie/EHcixWVfWtfTY=" - } - } - }, - "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" - }, - "lsmod": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz", - "integrity": "sha1-mgD3bco26yP6BTUK/htYXUKZ5ks=" - }, - "lynx": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.0.11.tgz", - "integrity": "sha1-LPoU5EP9LZKlm3efQVZ84cxpZaM=", - "requires": { - "mersenne": "0.0.4", - "statsd-parser": "0.0.4" - }, - "dependencies": { - "mersenne": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz", - "integrity": "sha1-QB/ex+whzbngPNPTAhOY2iGycIU=" }, - "statsd-parser": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/statsd-parser/-/statsd-parser-0.0.4.tgz", - "integrity": "sha1-y9JDlTzELv/VSLXSI4jtaJ7GOb0=" - } - } - }, - "mersenne": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz", - "integrity": "sha1-QB/ex+whzbngPNPTAhOY2iGycIU=" - }, - "metrics-sharelatex": { - "version": "git+https://github.com/sharelatex/metrics-sharelatex.git#05bd57604115ae5efdca2a419fbef4f25e9a780f", - "requires": { - "coffee-script": "1.6.0", - "lynx": "0.1.1", - "underscore": "1.6.0" - }, - "dependencies": { - "lynx": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.1.1.tgz", - "integrity": "sha1-Mxjc7xaQi4KG6Bisz9sxzXQkj50=", + "cookie": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.0.tgz", + "integrity": "sha1-kOtGndzpBchm3mh+/EMTHYgB+dA=" + }, + "logger-sharelatex": { + "version": "git+https://github.com/sharelatex/logger-sharelatex.git#405cf1350ca5ae5f7bb1e7091e28d5aa3aaaa72c", "requires": { - "mersenne": "0.0.4", - "statsd-parser": "0.0.4" + "bunyan": "1.5.1", + "coffee-script": "1.4.0", + "raven": "0.8.1" } }, - "underscore": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", - "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" + "lsmod": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-0.0.3.tgz", + "integrity": "sha1-F+E9ThrpF1DqVlNUjNiecUetAkQ=" + }, + "raven": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/raven/-/raven-0.8.1.tgz", + "integrity": "sha1-UVk7tlnHcnjc00gitlq+d7dRuvU=", + "requires": { + "cookie": "0.1.0", + "lsmod": "0.0.3", + "node-uuid": "1.4.8", + "stack-trace": "0.0.7" + } + }, + "settings-sharelatex": { + "version": "git+https://github.com/sharelatex/settings-sharelatex.git#b4fb8404c5de571d029bf4c29e96a60b21206f94", + "requires": { + "coffee-script": "1.6.0" + }, + "dependencies": { + "coffee-script": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", + "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=" + } + } + }, + "stack-trace": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.7.tgz", + "integrity": "sha1-xy4Il0T8Nln1CM3ONiGvVjTsD/8=" } } }, @@ -1733,175 +642,6 @@ "brace-expansion": "1.1.11" } }, - "minimist": { - "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - }, - "mkdirp": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", - "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=" - }, - "mocha": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz", - "integrity": "sha1-fYbPvPNcuCnidUwy4XNV7AUzh5Q=", - "dev": true, - "requires": { - "browser-stdout": "1.3.0", - "commander": "2.11.0", - "debug": "3.1.0", - "diff": "3.3.1", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.3", - "he": "1.1.1", - "mkdirp": "0.5.1", - "supports-color": "4.4.0" - }, - "dependencies": { - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "commander": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha1-FXFS/R56bI2YpbcVzzdt+SgARWM=", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "diff": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", - "integrity": "sha1-qoVnpu7QPFMfyJ0/cRzQ5SWd7HU=", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "growl": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", - "integrity": "sha1-GSa6kM8+3+KttJJ/WIC8IsZseQ8=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - } - } - }, - "ms": { - "version": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, "mv": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", @@ -1930,59 +670,6 @@ } } }, - "mysql": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.6.2.tgz", - "integrity": "sha1-k3Gd0yT1fUHET7bX+GDPjmOFk7A=", - "requires": { - "bignumber.js": "2.0.7", - "readable-stream": "1.1.14", - "require-all": "1.0.0" - }, - "dependencies": { - "bignumber.js": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.7.tgz", - "integrity": "sha1-husHB89qURCQnSPm6nQ0wU9QDxw=" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "require-all": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/require-all/-/require-all-1.0.0.tgz", - "integrity": "sha1-hINwjnzkxt+tmItQgPl4KbktIic=" - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - } - } - }, "nan": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", @@ -2000,419 +687,12 @@ "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" }, - "once": { - "version": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" - } - }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "optional": true }, - "process-nextick-args": { - "version": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" - }, - "pump": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", - "integrity": "sha1-Xf6DEcM7v2/BgmH580cCxHwIqVQ=", - "requires": { - "end-of-stream": "1.4.1", - "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz" - } - }, - "raven": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/raven/-/raven-1.2.1.tgz", - "integrity": "sha1-lJwTTbAooZC3u/j3kKrlQbfAIL0=", - "requires": { - "cookie": "0.3.1", - "json-stringify-safe": "5.0.1", - "lsmod": "1.0.0", - "stack-trace": "0.0.9", - "uuid": "3.0.0" - } - }, - "request": { - "version": "2.83.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", - "integrity": "sha1-ygtl2gLtYpNYh4COb1EDgQNOM1Y=", - "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.3.2", - "har-validator": "5.0.3", - "hawk": "6.0.2", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.17", - "oauth-sign": "0.8.2", - "performance-now": "2.1.0", - "qs": "6.5.1", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.3", - "tunnel-agent": "0.6.0", - "uuid": "3.2.1" - }, - "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.0.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" - } - }, - "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - } - }, - "boom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", - "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", - "requires": { - "hoek": "4.2.0" - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "requires": { - "delayed-stream": "1.0.0" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "cryptiles": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", - "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", - "requires": { - "boom": "5.2.0" - }, - "dependencies": { - "boom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", - "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", - "requires": { - "hoek": "4.2.0" - } - } - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "1.0.0" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fast-deep-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", - "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=" - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.17" - } - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "1.0.0" - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", - "requires": { - "ajv": "5.5.2", - "har-schema": "2.0.0" - } - }, - "hawk": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", - "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", - "requires": { - "boom": "4.3.1", - "cryptiles": "3.1.2", - "hoek": "4.2.0", - "sntp": "2.1.0" - } - }, - "hoek": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", - "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.1", - "sshpk": "1.13.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "mime-db": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" - }, - "mime-types": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", - "requires": { - "mime-db": "1.30.0" - } - }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, - "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" - }, - "sntp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", - "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", - "requires": { - "hoek": "4.2.0" - } - }, - "sshpk": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", - "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" - } - }, - "stringstream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" - }, - "tough-cookie": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", - "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", - "requires": { - "punycode": "1.4.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "5.1.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true - }, - "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha1-EsUou51Y0LkmXZovbw/ovhf/HxQ=" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "1.3.0" - } - } - } - }, "rimraf": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", @@ -2422,1856 +702,11 @@ "glob": "6.0.4" } }, - "safe-buffer": { - "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" - }, "safe-json-stringify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz", "integrity": "sha1-gaCY9Efku8P/MxKiQ1IbwGDvWRE=", "optional": true - }, - "sandboxed-module": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/sandboxed-module/-/sandboxed-module-0.3.0.tgz", - "integrity": "sha1-8fvvvYCaT2kHO9B8rm/H2y6vX2o=", - "dev": true, - "requires": { - "require-like": "0.1.2", - "stack-trace": "0.0.6" - }, - "dependencies": { - "require-like": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", - "integrity": "sha1-rW8wwTvs15cBDEaK+ndcDAprR/o=", - "dev": true - }, - "stack-trace": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.6.tgz", - "integrity": "sha1-HnGb1qJin/CcGJ4Xqe+QKpT8XbA=", - "dev": true - } - } - }, - "sequelize": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-2.1.3.tgz", - "integrity": "sha1-FrpROxNnlY/oIzhYEelRFFHt6tE=", - "requires": { - "bluebird": "2.9.34", - "dottie": "0.3.1", - "generic-pool": "2.2.0", - "inflection": "1.12.0", - "lodash": "3.10.1", - "moment": "2.20.1", - "node-uuid": "1.4.8", - "toposort-class": "0.3.1", - "validator": "3.43.0" - }, - "dependencies": { - "bluebird": { - "version": "2.9.34", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.9.34.tgz", - "integrity": "sha1-L3tOyAIWMoqf3evfacjUlC/v99g=" - }, - "dottie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/dottie/-/dottie-0.3.1.tgz", - "integrity": "sha1-QYX3d0iZYKwIQTHeP5oUoXm7dJ8=" - }, - "generic-pool": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.2.0.tgz", - "integrity": "sha1-i0ZcGnWI6p3SuxM72gu2a/74pj4=" - }, - "inflection": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", - "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" - }, - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" - }, - "moment": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz", - "integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg==" - }, - "toposort-class": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-0.3.1.tgz", - "integrity": "sha1-0TM67XNPhgjO0ShiGL0sRfYO/0g=" - }, - "validator": { - "version": "3.43.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-3.43.0.tgz", - "integrity": "sha1-lkZLmS1BloM9l6GUv0Cxn/VLrgU=" - } - } - }, - "settings-sharelatex": { - "version": "git+https://github.com/sharelatex/settings-sharelatex.git#cbc5e41c1dbe6789721a14b3fdae05bf22546559", - "requires": { - "coffee-script": "1.6.0" - } - }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" - }, - "sinon": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.7.3.tgz", - "integrity": "sha1-emnWnNApRYbHQyVO7/G1g6UJl/I=", - "dev": true, - "requires": { - "buster-format": "0.5.6" - }, - "dependencies": { - "buster-core": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/buster-core/-/buster-core-0.6.4.tgz", - "integrity": "sha1-J79rrWdCROpyDzEdkAoMoct4YFA=", - "dev": true - }, - "buster-format": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/buster-format/-/buster-format-0.5.6.tgz", - "integrity": "sha1-K4bDIuz14bCubm55Bev884fSq5U=", - "dev": true, - "requires": { - "buster-core": "0.6.4" - } - } - } - }, - "smoke-test-sharelatex": { - "version": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#bc3e93d18ccee219c0d99e8b02c984ccdd842e1c", - "requires": { - "mocha": "1.17.1" - }, - "dependencies": { - "glob": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", - "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", - "requires": { - "graceful-fs": "2.0.3", - "inherits": "2.0.3", - "minimatch": "0.2.14" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "requires": { - "lru-cache": "2.7.3", - "sigmund": "1.0.1" - } - }, - "mocha": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.17.1.tgz", - "integrity": "sha1-f3Zx1oUm0HS3uuZgyQmfh+DqHMs=", - "requires": { - "commander": "2.0.0", - "debug": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "diff": "1.0.7", - "glob": "3.2.3", - "growl": "1.7.0", - "jade": "0.26.3", - "mkdirp": "0.3.5" - } - } - } - }, - "split-ca": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", - "integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY=" - }, - "sqlite3": { - "version": "3.1.13", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-3.1.13.tgz", - "integrity": "sha512-JxXKPJnkZ6NuHRojq+g2WXWBt3M1G9sjZaYiHEWSTGijDM3cwju/0T2XbWqMXFmPqDgw+iB7zKQvnns4bvzXlw==", - "requires": { - "nan": "2.7.0", - "node-pre-gyp": "0.6.38" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true - }, - "ajv": { - "version": "4.11.8", - "bundled": true, - "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" - } - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true - }, - "are-we-there-yet": { - "version": "1.1.4", - "bundled": true, - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.3" - } - }, - "asn1": { - "version": "0.2.3", - "bundled": true - }, - "assert-plus": { - "version": "0.2.0", - "bundled": true - }, - "asynckit": { - "version": "0.4.0", - "bundled": true - }, - "aws-sign2": { - "version": "0.6.0", - "bundled": true - }, - "aws4": { - "version": "1.6.0", - "bundled": true - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "bundled": true, - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - } - }, - "block-stream": { - "version": "0.0.9", - "bundled": true, - "requires": { - "inherits": "2.0.3" - } - }, - "boom": { - "version": "2.10.1", - "bundled": true, - "requires": { - "hoek": "2.16.3" - } - }, - "brace-expansion": { - "version": "1.1.8", - "bundled": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "caseless": { - "version": "0.12.0", - "bundled": true - }, - "co": { - "version": "4.6.0", - "bundled": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true - }, - "combined-stream": { - "version": "1.0.5", - "bundled": true, - "requires": { - "delayed-stream": "1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "bundled": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true - }, - "cryptiles": { - "version": "2.0.5", - "bundled": true, - "requires": { - "boom": "2.10.1" - } - }, - "dashdash": { - "version": "1.14.1", - "bundled": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true - } - } - }, - "debug": { - "version": "2.6.9", - "bundled": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.4.2", - "bundled": true - }, - "delayed-stream": { - "version": "1.0.0", - "bundled": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true - }, - "ecc-jsbn": { - "version": "0.1.1", - "bundled": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "extend": { - "version": "3.0.1", - "bundled": true - }, - "extsprintf": { - "version": "1.3.0", - "bundled": true - }, - "forever-agent": { - "version": "0.6.1", - "bundled": true - }, - "form-data": { - "version": "2.1.4", - "bundled": true, - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.17" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true - }, - "fstream": { - "version": "1.0.11", - "bundled": true, - "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.2" - } - }, - "fstream-ignore": { - "version": "1.0.5", - "bundled": true, - "requires": { - "fstream": "1.0.11", - "inherits": "2.0.3", - "minimatch": "3.0.4" - } - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" - } - }, - "getpass": { - "version": "0.1.7", - "bundled": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true - } - } - }, - "glob": { - "version": "7.1.2", - "bundled": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "graceful-fs": { - "version": "4.1.11", - "bundled": true - }, - "har-schema": { - "version": "1.0.5", - "bundled": true - }, - "har-validator": { - "version": "4.2.1", - "bundled": true, - "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true - }, - "hawk": { - "version": "3.1.3", - "bundled": true, - "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" - } - }, - "hoek": { - "version": "2.16.3", - "bundled": true - }, - "http-signature": { - "version": "1.1.1", - "bundled": true, - "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.1", - "sshpk": "1.13.1" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true - }, - "ini": { - "version": "1.3.4", - "bundled": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "bundled": true - }, - "isarray": { - "version": "1.0.0", - "bundled": true - }, - "isstream": { - "version": "0.1.2", - "bundled": true - }, - "jsbn": { - "version": "0.1.1", - "bundled": true, - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "bundled": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "bundled": true, - "requires": { - "jsonify": "0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "bundled": true - }, - "jsonify": { - "version": "0.0.0", - "bundled": true - }, - "jsprim": { - "version": "1.4.1", - "bundled": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true - } - } - }, - "mime-db": { - "version": "1.30.0", - "bundled": true - }, - "mime-types": { - "version": "2.1.17", - "bundled": true, - "requires": { - "mime-db": "1.30.0" - } - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "requires": { - "brace-expansion": "1.1.8" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true - }, - "nan": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", - "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=" - }, - "node-pre-gyp": { - "version": "0.6.38", - "bundled": true, - "requires": { - "hawk": "3.1.3", - "mkdirp": "0.5.1", - "nopt": "4.0.1", - "npmlog": "4.1.2", - "rc": "1.2.1", - "request": "2.81.0", - "rimraf": "2.6.2", - "semver": "5.4.1", - "tar": "2.2.1", - "tar-pack": "3.4.0" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.4" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true - }, - "oauth-sign": { - "version": "0.8.2", - "bundled": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true - }, - "osenv": { - "version": "0.1.4", - "bundled": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true - }, - "performance-now": { - "version": "0.2.0", - "bundled": true - }, - "process-nextick-args": { - "version": "1.0.7", - "bundled": true - }, - "punycode": { - "version": "1.4.1", - "bundled": true - }, - "qs": { - "version": "6.4.0", - "bundled": true - }, - "rc": { - "version": "1.2.1", - "bundled": true, - "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.4", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true - } - } - }, - "readable-stream": { - "version": "2.3.3", - "bundled": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "request": { - "version": "2.81.0", - "bundled": true, - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.17", - "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.3", - "tunnel-agent": "0.6.0", - "uuid": "3.1.0" - } - }, - "rimraf": { - "version": "2.6.2", - "bundled": true, - "requires": { - "glob": "7.1.2" - } - }, - "safe-buffer": { - "version": "5.1.1", - "bundled": true - }, - "semver": { - "version": "5.4.1", - "bundled": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true - }, - "sntp": { - "version": "1.0.9", - "bundled": true, - "requires": { - "hoek": "2.16.3" - } - }, - "sshpk": { - "version": "1.13.1", - "bundled": true, - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true - } - } - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string_decoder": { - "version": "1.0.3", - "bundled": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "stringstream": { - "version": "0.0.5", - "bundled": true - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true - }, - "tar": { - "version": "2.2.1", - "bundled": true, - "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" - } - }, - "tar-pack": { - "version": "3.4.0", - "bundled": true, - "requires": { - "debug": "2.6.9", - "fstream": "1.0.11", - "fstream-ignore": "1.0.5", - "once": "1.4.0", - "readable-stream": "2.3.3", - "rimraf": "2.6.2", - "tar": "2.2.1", - "uid-number": "0.0.6" - } - }, - "tough-cookie": { - "version": "2.3.3", - "bundled": true, - "requires": { - "punycode": "1.4.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "bundled": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "bundled": true, - "optional": true - }, - "uid-number": { - "version": "0.0.6", - "bundled": true - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true - }, - "uuid": { - "version": "3.1.0", - "bundled": true - }, - "verror": { - "version": "1.10.0", - "bundled": true, - "requires": { - "assert-plus": "1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "1.3.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true - } - } - }, - "wide-align": { - "version": "1.1.2", - "bundled": true, - "requires": { - "string-width": "1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true - } - } - }, - "stack-trace": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", - "integrity": "sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU=" - }, - "statsd-parser": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/statsd-parser/-/statsd-parser-0.0.4.tgz", - "integrity": "sha1-y9JDlTzELv/VSLXSI4jtaJ7GOb0=" - }, - "string_decoder": { - "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, - "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha1-iD992rwWUUKyphQn8zUt7RldGj4=", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - }, - "tar-fs": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.12.0.tgz", - "integrity": "sha1-pqgFU9ilTHPeHQrg553ncDVgXh0=", - "requires": { - "mkdirp": "0.5.1", - "pump": "1.0.3", - "tar-stream": "1.5.5" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" - } - } - } - }, - "tar-stream": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.5.tgz", - "integrity": "sha1-XK2Ed59FyDsfJQjZawnYjHIYr1U=", - "requires": { - "bl": "1.2.1", - "end-of-stream": "1.4.1", - "readable-stream": "2.3.3", - "xtend": "4.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "requires": { - "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "isarray": "1.0.0", - "process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "string_decoder": "1.0.3", - "util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" - } - } - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "timekeeper": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/timekeeper/-/timekeeper-0.0.4.tgz", - "integrity": "sha1-kNt58X2Ni1NiFUOJSSuXJ2LP0nY=", - "dev": true - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, - "underscore": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" - }, - "util-deprecate": { - "version": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "uuid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz", - "integrity": "sha1-Zyj8BFnEUNeWqZwxg3VpvfZy1yg=" - }, - "v8-profiler": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/v8-profiler/-/v8-profiler-5.7.0.tgz", - "integrity": "sha1-6DgcvrtbX9DKjSsJ9qAYGhWNs00=", - "requires": { - "nan": "2.8.0", - "node-pre-gyp": "0.6.39" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" - } - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" - }, - "are-we-there-yet": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", - "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.4" - } - }, - "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" - }, - "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" - }, - "aws4": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - } - }, - "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "requires": { - "hoek": "2.16.3" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "requires": { - "delayed-stream": "1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "cryptiles": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", - "requires": { - "boom": "2.10.1" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", - "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=" - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" - }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" - }, - "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.17" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "fstream": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", - "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", - "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.2" - } - }, - "fstream-ignore": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", - "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", - "requires": { - "fstream": "1.0.11", - "inherits": "2.0.3", - "minimatch": "3.0.4" - } - }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" - } - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" - }, - "har-schema": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", - "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" - }, - "har-validator": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", - "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", - "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" - } - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" - }, - "hawk": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", - "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" - } - }, - "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" - }, - "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", - "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.1", - "sshpk": "1.13.1" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "requires": { - "jsonify": "0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } - } - }, - "mime-db": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" - }, - "mime-types": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", - "requires": { - "mime-db": "1.30.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "nan": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", - "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=" - }, - "node-pre-gyp": { - "version": "0.6.39", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz", - "integrity": "sha512-OsJV74qxnvz/AMGgcfZoDaeDXKD3oY3QVIbBmwszTFkRisTSXbMQyn4UWzUMOtA5SVhrBZOTp0wcoSBgfMfMmQ==", - "requires": { - "detect-libc": "1.0.3", - "hawk": "3.1.3", - "mkdirp": "0.5.1", - "nopt": "4.0.1", - "npmlog": "4.1.2", - "rc": "1.2.5", - "request": "2.81.0", - "rimraf": "2.6.2", - "semver": "5.5.0", - "tar": "2.2.1", - "tar-pack": "3.4.1" - } - }, - "nopt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", - "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.4" - } - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1.0.2" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "osenv": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", - "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "performance-now": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, - "qs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" - }, - "rc": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.5.tgz", - "integrity": "sha1-J1zWh/bjs2zHVrqibf7oCnkDAf0=", - "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - } - } - }, - "readable-stream": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", - "integrity": "sha512-vuYxeWYM+fde14+rajzqgeohAI7YoJcHE7kXDAc4Nk0EbuKnJfqtY9YtRkLo/tqkuF7MsBQRhPnPeyjYITp3ZQ==", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "request": { - "version": "2.81.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", - "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.17", - "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.3", - "tunnel-agent": "0.6.0", - "uuid": "3.2.1" - } - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "requires": { - "glob": "7.1.2" - } - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" - }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", - "requires": { - "hoek": "2.16.3" - } - }, - "sshpk": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", - "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "5.1.1" - } - }, - "stringstream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, - "tar": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", - "requires": { - "block-stream": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "fstream": "1.0.11", - "inherits": "2.0.3" - } - }, - "tar-pack": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.1.tgz", - "integrity": "sha512-PPRybI9+jM5tjtCbN2cxmmRU7YmqT3Zv/UDy48tAh2XRkLa9bAORtSWLkVc13+GJF+cdTh1yEnHEk3cpTaL5Kg==", - "requires": { - "debug": "2.6.9", - "fstream": "1.0.11", - "fstream-ignore": "1.0.5", - "once": "1.4.0", - "readable-stream": "2.3.4", - "rimraf": "2.6.2", - "tar": "2.2.1", - "uid-number": "0.0.6" - } - }, - "tough-cookie": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", - "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", - "requires": { - "punycode": "1.4.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "5.1.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true - }, - "uid-number": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", - "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "1.3.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } - } - }, - "wide-align": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", - "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", - "requires": { - "string-width": "1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - } - } - }, - "wrappy": { - "version": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "wrench": { - "version": "1.5.9", - "resolved": "https://registry.npmjs.org/wrench/-/wrench-1.5.9.tgz", - "integrity": "sha1-QRaRxjqbJTGxcAJnJ5veyiOyFCo=" - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" } } } diff --git a/services/clsi/package.json b/services/clsi/package.json index fdb5030723..e0e4fdafc1 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -14,9 +14,9 @@ "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- $@", "test:unit:_run": "mocha --recursive --exit --reporter spec $@ test/unit/js", "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- $@", - "compile:unit_tests": "[ -e test/unit ] && coffee -o test/unit/js -c test/unit/coffee || echo 'No unit tests to compile, skipping'", - "compile:acceptance_tests": "[ -e test/acceptance ] && coffee -o test/acceptance/js -c test/acceptance/coffee || echo 'No acceptance tests to compile, skipping'", - "compile:all": "npm run compile:app && npm run compile:unit_tests && npm run compile:acceptance_tests && npm run compile:smoke_tests", + "compile:unit_tests": "[ -e test/unit ] && coffee -o test/unit/js -c test/unit/coffee || echo 'No unit tests to compile'", + "compile:acceptance_tests": "[ -e test/acceptance ] && coffee -o test/acceptance/js -c test/acceptance/coffee || echo 'No acceptance tests to compile'", + "compile:all": "npm run compile:app && npm run compile:unit_tests && npm run compile:acceptance_tests", "nodemon": "nodemon --config nodemon.json", "compile:smoke_tests": "[ -e test/smoke ] && coffee -o test/smoke/js -c test/smoke/coffee || echo 'No smoke tests to compile, skipping'" }, diff --git a/services/clsi/patch-texlive-dockerfile b/services/clsi/patch-texlive-dockerfile new file mode 100644 index 0000000000..61cb796414 --- /dev/null +++ b/services/clsi/patch-texlive-dockerfile @@ -0,0 +1,3 @@ +FROM quay.io/sharelatex/texlive-full:2017.1 + +# RUN usermod -u 1001 tex diff --git a/services/clsi/synctex.profile b/services/clsi/synctex.profile new file mode 100644 index 0000000000..577a901921 --- /dev/null +++ b/services/clsi/synctex.profile @@ -0,0 +1,34 @@ +include /etc/firejail/disable-common.inc +include /etc/firejail/disable-devel.inc +# include /etc/firejail/disable-mgmt.inc ## removed in 0.9.40 +# include /etc/firejail/disable-secret.inc ## removed in 0.9.40 + +read-only /bin +blacklist /boot +blacklist /dev +read-only /etc +blacklist /home # blacklisted for synctex +read-only /lib +read-only /lib64 +blacklist /media +blacklist /mnt +blacklist /opt +blacklist /root +read-only /run +blacklist /sbin +blacklist /selinux +blacklist /src +blacklist /sys +read-only /usr + +caps.drop all +noroot +nogroups +net none +private-tmp +private-dev +shell none +seccomp +nonewprivs + + From 4c96abd6c5fa14b6d658b258cbe091c401aac4ad Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 29 Mar 2018 13:07:55 +0100 Subject: [PATCH 375/754] grep works with command updated build scripts acceptence tests break, files are written as root when user is node --- services/clsi/Makefile | 7 ++++--- services/clsi/app.coffee | 4 ++-- services/clsi/app/coffee/ResourceWriter.coffee | 6 ++++++ services/clsi/docker-compose-config.yml | 4 +++- services/clsi/docker-compose.ci.yml | 6 ++---- services/clsi/docker-compose.yml | 10 ++++++---- services/clsi/entrypoint.sh | 12 +++++++++--- services/clsi/install_deps.sh | 9 --------- services/clsi/package.json | 4 ++-- .../coffee/ExampleDocumentTests.coffee | 17 +++++++++++++---- 10 files changed, 47 insertions(+), 32 deletions(-) diff --git a/services/clsi/Makefile b/services/clsi/Makefile index 2a1196c2ca..2db377abee 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -10,6 +10,7 @@ DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ BRANCH_NAME=$(BRANCH_NAME) \ PROJECT_NAME=$(PROJECT_NAME) \ + MOCHA_GREP=${MOCHA_GREP} \ docker-compose ${DOCKER_COMPOSE_FLAGS} clean: @@ -21,13 +22,13 @@ clean: test: test_unit test_acceptance test_unit: - @[ ! -d test/unit ] && echo "clsi has no unit tests" || $(DOCKER_COMPOSE) run --rm test_unit -- ${MOCHA_ARGS} + @[ ! -d test/unit ] && echo "clsi has no unit tests" || $(DOCKER_COMPOSE) run --rm test_unit test_acceptance: test_clean # clear the database before each acceptance test run - @[ ! -d test/acceptance ] && echo "clsi has no acceptance tests" || $(DOCKER_COMPOSE) run --rm test_acceptance -- ${MOCHA_ARGS} + @[ ! -d test/acceptance ] && echo "clsi has no acceptance tests" || $(DOCKER_COMPOSE) run --rm test_acceptance test_clean: - $(DOCKER_COMPOSE) down -t -v 0 + $(DOCKER_COMPOSE) down -v -t 0 build: docker build --pull --tag quay.io/sharelatex/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) . diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index a3f4ef17e1..11981a72aa 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -182,7 +182,7 @@ server = net.createServer (socket) -> logger.err err:err, "error with socket on load check" socket.destroy() - if STATE == "up" and settings.load_balancer_agent.report_load + if STATE == "up" and Settings.load_balancer_agent.report_load currentLoad = os.loadavg()[0] # staging clis's have 1 cpu core only @@ -206,7 +206,7 @@ server = net.createServer (socket) -> port = (Settings.internal?.clsi?.port or 3013) host = (Settings.internal?.clsi?.host or "localhost") -load_port = settings.internal.clsi.load_port or 3048 +load_port = Settings.internal.clsi.load_port or 3048 diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index 0b6aef5b72..90ada043b8 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -120,7 +120,13 @@ module.exports = ResourceWriter = logger.err err:err, project_id:project_id, path:path, resource_url:resource.url, modified:resource.modified, "error downloading file for resources" callback() #try and continue compiling even if http resource can not be downloaded at this time else + process = require("process") + console.log "writing file out", path, process.getuid() fs.writeFile path, resource.content, callback + try + result = fs.lstatSync(path) + console.log "path stats", result + catch e checkPath: (basePath, resourcePath, callback) -> path = Path.normalize(Path.join(basePath, resourcePath)) diff --git a/services/clsi/docker-compose-config.yml b/services/clsi/docker-compose-config.yml index f2bb8631c3..b6deacffcd 100644 --- a/services/clsi/docker-compose-config.yml +++ b/services/clsi/docker-compose-config.yml @@ -12,6 +12,8 @@ services: volumes: - /var/run/docker.sock:/var/run/docker.sock - ./compiles:/app/compiles + - ./cache:/app/cache + ci: environment: @@ -24,4 +26,4 @@ services: SQLITE_PATH: /app/compiles/db.sqlite volumes: - /var/run/docker.sock:/var/run/docker.sock:rw - - ./compiles:/app/compiles \ No newline at end of file + - ./cache:/app/cache diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 370e2a80a2..85512cf7f1 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -8,8 +8,7 @@ version: "2" services: test_unit: image: quay.io/sharelatex/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER - user: node - entrypoint: npm run test:unit:_run + command: npm run test:unit:_run test_acceptance: build: . @@ -23,8 +22,7 @@ services: depends_on: - redis - mongo - user: node - entrypoint: npm run test:acceptance:_run + command: npm run test:acceptance:_run redis: image: redis diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 32cbf61e55..5f53d69191 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -11,8 +11,9 @@ services: volumes: - .:/app working_dir: /app - user: node - entrypoint: npm run test:unit + environment: + MOCHA_GREP: ${MOCHA_GREP} + command: npm run test:unit test_acceptance: build: . @@ -25,11 +26,12 @@ services: environment: REDIS_HOST: redis MONGO_HOST: mongo - user: node + environment: + MOCHA_GREP: ${MOCHA_GREP} depends_on: - redis - mongo - entrypoint: npm run test:acceptance + command: npm run test:acceptance redis: image: redis diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh index ee5ea47f90..32776fa553 100755 --- a/services/clsi/entrypoint.sh +++ b/services/clsi/entrypoint.sh @@ -1,7 +1,10 @@ #!/bin/sh echo "Changing permissions of /var/run/docker.sock for sibling containers" - +ls -al /var/run/docker.sock +docker --version +cat /etc/passwd +usermod -aG docker node chown root:docker /var/run/docker.sock mkdir -p /app/cache @@ -10,7 +13,10 @@ chown -R node:node /app/cache mkdir -p /app/compiles chown -R node:node /app/compiles +chown -R node:node /app/bin/synctex +mkdir -p /app/test/acceptance/fixtures/tmp/ +chown -R node:node /app + + ./bin/install_texlive_gce.sh -echo "HELOOOo" -echo "$@" exec runuser -u node "$@" \ No newline at end of file diff --git a/services/clsi/install_deps.sh b/services/clsi/install_deps.sh index 4b06f22d88..49bdc5c963 100755 --- a/services/clsi/install_deps.sh +++ b/services/clsi/install_deps.sh @@ -2,12 +2,3 @@ wget -qO- https://get.docker.com/ | sh apt-get install poppler-utils vim ghostscript --yes npm rebuild -usermod -aG docker node - -mkdir -p /app/cache -chown -R node:node /app/cache - -mkdir -p /app/compiles -chown -R node:node /app/compiles - -chown -R node:node /app/bin/synctex diff --git a/services/clsi/package.json b/services/clsi/package.json index e0e4fdafc1..fe2bd26023 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -11,9 +11,9 @@ "compile": "npm run compile:app && npm run compile:test:acceptance && npm run compile:test:smoke", "start": "npm run compile:app && node $NODE_APP_OPTIONS app.js", "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", - "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- $@", + "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- --grep=$MOCHA_GREP", "test:unit:_run": "mocha --recursive --exit --reporter spec $@ test/unit/js", - "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- $@", + "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- --grep=$MOCHA_GREP", "compile:unit_tests": "[ -e test/unit ] && coffee -o test/unit/js -c test/unit/coffee || echo 'No unit tests to compile'", "compile:acceptance_tests": "[ -e test/acceptance ] && coffee -o test/acceptance/js -c test/acceptance/coffee || echo 'No acceptance tests to compile'", "compile:all": "npm run compile:app && npm run compile:unit_tests && npm run compile:acceptance_tests", diff --git a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee index ec569796b8..d4bd19f55c 100644 --- a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee +++ b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee @@ -4,15 +4,21 @@ require("chai").should() fs = require "fs" ChildProcess = require "child_process" ClsiApp = require "./helpers/ClsiApp" - -fixturePath = (path) -> __dirname + "/../fixtures/" + path - +logger = require("logger-sharelatex") +Path = require("path") +fixturePath = (path) -> Path.normalize(__dirname + "/../fixtures/" + path) +process = require "process" +console.log process.pid, process.ppid, process.getuid(),process.getgroups(), "PID" try + console.log "creating tmp directory", fixturePath("tmp") fs.mkdirSync(fixturePath("tmp")) -catch e +catch err + console.log err, fixturePath("tmp"), "unable to create fixture tmp path" convertToPng = (pdfPath, pngPath, callback = (error) ->) -> command = "convert #{fixturePath(pdfPath)} #{fixturePath(pngPath)}" + console.log "COMMAND" + console.log command convert = ChildProcess.exec command stdout = "" convert.stdout.on "data", (chunk) -> console.log "STDOUT", chunk.toString() @@ -58,6 +64,8 @@ compareMultiplePages = (project_id, callback = (error) ->) -> compareNext 0, callback comparePdf = (project_id, example_dir, callback = (error) ->) -> + console.log "CONVERT" + console.log "tmp/#{project_id}.pdf", "tmp/#{project_id}-generated.png" convertToPng "tmp/#{project_id}.pdf", "tmp/#{project_id}-generated.png", (error) => throw error if error? convertToPng "examples/#{example_dir}/output.pdf", "tmp/#{project_id}-source.png", (error) => @@ -76,6 +84,7 @@ comparePdf = (project_id, example_dir, callback = (error) ->) -> downloadAndComparePdf = (project_id, example_dir, url, callback = (error) ->) -> writeStream = fs.createWriteStream(fixturePath("tmp/#{project_id}.pdf")) request.get(url).pipe(writeStream) + console.log("writing file out", fixturePath("tmp/#{project_id}.pdf")) writeStream.on "close", () => checkPdfInfo "tmp/#{project_id}.pdf", (error, optimised) => throw error if error? From 35da59ac8adb4e6fd6df02bb647d92307e23112c Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 9 Apr 2018 11:06:35 +0100 Subject: [PATCH 376/754] update package.json scripts --- services/clsi/docker-compose.yml | 1 - services/clsi/package.json | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 5f53d69191..5a25a011c4 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -26,7 +26,6 @@ services: environment: REDIS_HOST: redis MONGO_HOST: mongo - environment: MOCHA_GREP: ${MOCHA_GREP} depends_on: - redis diff --git a/services/clsi/package.json b/services/clsi/package.json index fe2bd26023..49835cc759 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -13,9 +13,9 @@ "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- --grep=$MOCHA_GREP", "test:unit:_run": "mocha --recursive --exit --reporter spec $@ test/unit/js", - "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- --grep=$MOCHA_GREP", - "compile:unit_tests": "[ -e test/unit ] && coffee -o test/unit/js -c test/unit/coffee || echo 'No unit tests to compile'", - "compile:acceptance_tests": "[ -e test/acceptance ] && coffee -o test/acceptance/js -c test/acceptance/coffee || echo 'No acceptance tests to compile'", + "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- --grep=$MOCHA_GREP", + "compile:unit_tests": "[ ! -e test/unit/coffee ] && echo 'No unit tests to compile' || coffee -o test/unit/js -c test/unit/coffee", + "compile:acceptance_tests": "[ ! -e test/acceptance/coffee ] && echo 'No acceptance tests to compile' || coffee -o test/acceptance/js -c test/acceptance/coffee", "compile:all": "npm run compile:app && npm run compile:unit_tests && npm run compile:acceptance_tests", "nodemon": "nodemon --config nodemon.json", "compile:smoke_tests": "[ -e test/smoke ] && coffee -o test/smoke/js -c test/smoke/coffee || echo 'No smoke tests to compile, skipping'" From 3ad8e9f2f172bd33a532bae0e9ba75b013ddb286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Fern=C3=A1ndez=20Capel?= <alberto.fernandez-capel@overleaf.com> Date: Tue, 1 May 2018 09:25:37 +0100 Subject: [PATCH 377/754] Make travis read the node version from the .nvmrc file See https://docs.travis-ci.com/user/languages/javascript-with-nodejs/#Specifying-Node.js-versions-using-.nvmrc --- services/clsi/.travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/services/clsi/.travis.yml b/services/clsi/.travis.yml index 29f5884d60..b6fb0e91be 100644 --- a/services/clsi/.travis.yml +++ b/services/clsi/.travis.yml @@ -1,8 +1,5 @@ language: node_js -node_js: - - "0.10" - before_install: - npm install -g grunt-cli From 91bd54cff9306a8e3fc5f1b011a95b9b84f76cd2 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 24 May 2018 19:03:57 +0100 Subject: [PATCH 378/754] update to 1.1.3 build scripts --- services/clsi/.dockerignore | 6 ++ services/clsi/.nvmrc | 2 +- services/clsi/Dockerfile | 15 ++-- services/clsi/Makefile | 11 +-- services/clsi/bin/synctex | Bin 90472 -> 92840 bytes services/clsi/docker-compose.ci.yml | 10 +-- services/clsi/docker-compose.yml | 9 +-- services/clsi/nodemon.json | 2 + services/clsi/package.json | 104 ++++++++++++++-------------- 9 files changed, 88 insertions(+), 71 deletions(-) diff --git a/services/clsi/.dockerignore b/services/clsi/.dockerignore index a99835353f..386f26df30 100644 --- a/services/clsi/.dockerignore +++ b/services/clsi/.dockerignore @@ -1,3 +1,9 @@ node_modules/* +gitrev +.git +.gitignore +.npm +.nvmrc +nodemon.json app.js **/js/* diff --git a/services/clsi/.nvmrc b/services/clsi/.nvmrc index 5917993c09..bbf0c5a541 100644 --- a/services/clsi/.nvmrc +++ b/services/clsi/.nvmrc @@ -1 +1 @@ -6.13.0 +6.14.1 diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index ed1f2c6e0c..1ccc689d42 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -1,20 +1,23 @@ -FROM node:6.13.0 as app - -COPY ./ /app +FROM node:6.14.1 as app WORKDIR /app -RUN npm install +#wildcard as some files may not be in all repos +COPY package*.json npm-shrink*.json /app/ + +RUN npm install --quiet + +COPY . /app RUN npm run compile:all -FROM node:6.13.0 +FROM node:6.14.1 COPY --from=app /app /app WORKDIR /app -RUN ./install_deps.sh +RUN chmod 0755 ./install_deps.sh && ./install_deps.sh ENTRYPOINT ["/bin/sh", "entrypoint.sh"] CMD ["node","app.js"] diff --git a/services/clsi/Makefile b/services/clsi/Makefile index 2db377abee..63ff8c02fc 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.0 +# Version: 1.1.3 BUILD_NUMBER ?= local BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) @@ -13,6 +13,7 @@ DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ MOCHA_GREP=${MOCHA_GREP} \ docker-compose ${DOCKER_COMPOSE_FLAGS} + clean: rm -f app.js rm -rf app/js @@ -24,16 +25,18 @@ test: test_unit test_acceptance test_unit: @[ ! -d test/unit ] && echo "clsi has no unit tests" || $(DOCKER_COMPOSE) run --rm test_unit -test_acceptance: test_clean # clear the database before each acceptance test run +test_acceptance: test_clean test_acceptance_pre_run # clear the database before each acceptance test run @[ ! -d test/acceptance ] && echo "clsi has no acceptance tests" || $(DOCKER_COMPOSE) run --rm test_acceptance test_clean: $(DOCKER_COMPOSE) down -v -t 0 +test_acceptance_pre_run: + @[ ! -f test/acceptance/scripts/pre-run ] && echo "clsi has no pre acceptance tests task" || $(DOCKER_COMPOSE) run --rm test_acceptance test/acceptance/scripts/pre-run build: - docker build --pull --tag quay.io/sharelatex/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) . + docker build --pull --tag gcr.io/csh-gcdm-test/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) . publish: - docker push quay.io/sharelatex/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) + docker push gcr.io/csh-gcdm-test/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) .PHONY: clean test test_unit test_acceptance test_clean build publish diff --git a/services/clsi/bin/synctex b/services/clsi/bin/synctex index 99044b97a0259b30afbf0bfb34365e4f1270479e..e793e248bb52d48f28a105fb6dd2ea9ead1659e1 100755 GIT binary patch literal 92840 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 literal 90472 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:nwe6m+2D>* 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 Ww?EQ8J?~lm(LUq<N}>K2<Nq%(A5^6P diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 85512cf7f1..0e14b0f0dc 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -1,27 +1,28 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.0 +# Version: 1.1.3 version: "2" services: test_unit: - image: quay.io/sharelatex/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER + image: gcr.io/csh-gcdm-test/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER command: npm run test:unit:_run test_acceptance: build: . - image: quay.io/sharelatex/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER + image: gcr.io/csh-gcdm-test/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER extends: file: docker-compose-config.yml service: ci environment: REDIS_HOST: redis MONGO_HOST: mongo + POSTGRES_HOST: postgres depends_on: - - redis - mongo + - redis command: npm run test:acceptance:_run redis: @@ -29,3 +30,4 @@ services: mongo: image: mongo:3.4 + diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 5a25a011c4..2283746133 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -1,13 +1,13 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.0 +# Version: 1.1.3 version: "2" services: test_unit: - image: node:6.13.0 + build: . volumes: - .:/app working_dir: /app @@ -26,14 +26,15 @@ services: environment: REDIS_HOST: redis MONGO_HOST: mongo + POSTGRES_HOST: postgres MOCHA_GREP: ${MOCHA_GREP} depends_on: - - redis - mongo + - redis command: npm run test:acceptance - redis: image: redis mongo: image: mongo:3.4 + diff --git a/services/clsi/nodemon.json b/services/clsi/nodemon.json index 815f39223e..98db38d71b 100644 --- a/services/clsi/nodemon.json +++ b/services/clsi/nodemon.json @@ -8,10 +8,12 @@ "execMap": { "js": "npm run start" }, + "watch": [ "app/coffee/", "app.coffee", "config/" ], "ext": "coffee" + } diff --git a/services/clsi/package.json b/services/clsi/package.json index 49835cc759..c925708029 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -1,63 +1,63 @@ { - "name": "node-clsi", - "description": "A Node.js implementation of the CLSI LaTeX web-API", - "version": "0.1.4", + "name": "node-clsi", + "description": "A Node.js implementation of the CLSI LaTeX web-API", + "version": "0.1.4", "repository": { - "type": "git", + "type": "git", "url": "https://github.com/sharelatex/clsi-sharelatex.git" - }, + }, "scripts": { - "compile:app": "coffee $COFFEE_OPTIONS -o app/js -c app/coffee && coffee $COFFEE_OPTIONS -c app.coffee", - "compile": "npm run compile:app && npm run compile:test:acceptance && npm run compile:test:smoke", - "start": "npm run compile:app && node $NODE_APP_OPTIONS app.js", - "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", - "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- --grep=$MOCHA_GREP", - "test:unit:_run": "mocha --recursive --exit --reporter spec $@ test/unit/js", - "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- --grep=$MOCHA_GREP", - "compile:unit_tests": "[ ! -e test/unit/coffee ] && echo 'No unit tests to compile' || coffee -o test/unit/js -c test/unit/coffee", - "compile:acceptance_tests": "[ ! -e test/acceptance/coffee ] && echo 'No acceptance tests to compile' || coffee -o test/acceptance/js -c test/acceptance/coffee", - "compile:all": "npm run compile:app && npm run compile:unit_tests && npm run compile:acceptance_tests", - "nodemon": "nodemon --config nodemon.json", + "compile:app": "([ -e app/coffee ] && coffee $COFFEE_OPTIONS -o app/js -c app/coffee || echo 'No CoffeeScript folder to compile') && ( [ -e app.coffee ] && coffee $COFFEE_OPTIONS -c app.coffee || echo 'No CoffeeScript app to compile')", + "compile": "npm run compile:app && npm run compile:test:acceptance && npm run compile:test:smoke", + "start": "npm run compile:app && node $NODE_APP_OPTIONS app.js", + "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", + "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- --grep=$MOCHA_GREP", + "test:unit:_run": "mocha --recursive --reporter spec $@ test/unit/js", + "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- --grep=$MOCHA_GREP", + "compile:unit_tests": "[ ! -e test/unit/coffee ] && echo 'No unit tests to compile' || coffee -o test/unit/js -c test/unit/coffee", + "compile:acceptance_tests": "[ ! -e test/acceptance/coffee ] && echo 'No acceptance tests to compile' || coffee -o test/acceptance/js -c test/acceptance/coffee", + "compile:all": "npm run compile:app && npm run compile:unit_tests && npm run compile:acceptance_tests", + "nodemon": "nodemon --config nodemon.json", "compile:smoke_tests": "[ -e test/smoke ] && coffee -o test/smoke/js -c test/smoke/coffee || echo 'No smoke tests to compile, skipping'" - }, - "author": "James Allen <james@sharelatex.com>", + }, + "author": "James Allen <james@sharelatex.com>", "dependencies": { - "async": "0.2.9", - "body-parser": "^1.2.0", - "dockerode": "^2.5.3", - "express": "^4.2.0", - "fs-extra": "^0.16.3", - "grunt-mkdir": "^1.0.0", - "heapdump": "^0.3.5", - "lockfile": "^1.0.3", - "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", - "lynx": "0.0.11", - "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.5.0", - "mkdirp": "0.3.5", - "mysql": "2.6.2", - "request": "^2.21.0", - "sequelize": "^2.1.3", - "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", - "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", - "sqlite3": "^3.1.13", - "underscore": "^1.8.2", - "v8-profiler": "^5.2.4", + "async": "0.2.9", + "body-parser": "^1.2.0", + "dockerode": "^2.5.3", + "express": "^4.2.0", + "fs-extra": "^0.16.3", + "grunt-mkdir": "^1.0.0", + "heapdump": "^0.3.5", + "lockfile": "^1.0.3", + "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", + "lynx": "0.0.11", + "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.5.0", + "mkdirp": "0.3.5", + "mysql": "2.6.2", + "request": "^2.21.0", + "sequelize": "^2.1.3", + "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", + "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", + "sqlite3": "^3.1.13", + "underscore": "^1.8.2", + "v8-profiler": "^5.2.4", "wrench": "~1.5.4" - }, + }, "devDependencies": { - "mocha": "^4.0.1", - "coffee-script": "1.6.0", - "chai": "~1.8.1", - "sinon": "~1.7.3", - "grunt": "~0.4.2", - "grunt-contrib-coffee": "~0.7.0", - "grunt-contrib-clean": "~0.5.0", - "grunt-shell": "~0.6.1", - "grunt-mocha-test": "~0.8.1", - "sandboxed-module": "~0.3.0", - "timekeeper": "0.0.4", - "grunt-execute": "^0.1.5", - "bunyan": "^0.22.1", + "mocha": "^4.0.1", + "coffee-script": "1.6.0", + "chai": "~1.8.1", + "sinon": "~1.7.3", + "grunt": "~0.4.2", + "grunt-contrib-coffee": "~0.7.0", + "grunt-contrib-clean": "~0.5.0", + "grunt-shell": "~0.6.1", + "grunt-mocha-test": "~0.8.1", + "sandboxed-module": "~0.3.0", + "timekeeper": "0.0.4", + "grunt-execute": "^0.1.5", + "bunyan": "^0.22.1", "grunt-bunyan": "^0.5.0" } } From f769765923189e2c3ad775437cdbad0bd9efbbbe Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 24 May 2018 21:59:02 +0100 Subject: [PATCH 379/754] added --exit to unit tests --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index c925708029..a0ea4f036a 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -12,7 +12,7 @@ "start": "npm run compile:app && node $NODE_APP_OPTIONS app.js", "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- --grep=$MOCHA_GREP", - "test:unit:_run": "mocha --recursive --reporter spec $@ test/unit/js", + "test:unit:_run": "mocha --recursive --reporter spec --exit $@ test/unit/js", "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- --grep=$MOCHA_GREP", "compile:unit_tests": "[ ! -e test/unit/coffee ] && echo 'No unit tests to compile' || coffee -o test/unit/js -c test/unit/coffee", "compile:acceptance_tests": "[ ! -e test/acceptance/coffee ] && echo 'No acceptance tests to compile' || coffee -o test/acceptance/js -c test/acceptance/coffee", From 64f01a125f51165a31ab34f702f3ca1899d9e84f Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 25 May 2018 11:51:34 +0100 Subject: [PATCH 380/754] set user to tex for tests run on ci box --- services/clsi/docker-compose-config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/docker-compose-config.yml b/services/clsi/docker-compose-config.yml index b6deacffcd..ce0c502d0e 100644 --- a/services/clsi/docker-compose-config.yml +++ b/services/clsi/docker-compose-config.yml @@ -18,7 +18,7 @@ services: ci: environment: TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2017.1 - TEXLIVE_IMAGE_USER: root + TEXLIVE_IMAGE_USER: tex SHARELATEX_CONFIG: /app/config/settings.defaults.coffee DOCKER_RUNNER: "true" COMPILES_HOST_DIR: $PWD/compiles From c3ae6b5d238bb7342f0fb7dafc54446649c4f161 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 25 May 2018 12:01:16 +0100 Subject: [PATCH 381/754] log settings on startup --- services/clsi/config/settings.defaults.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index de853f5924..ab6e7e6df0 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -46,3 +46,4 @@ if process.env["DOCKER_RUNNER"] module.exports.path.sandboxedCompilesHostDir = process.env["COMPILES_HOST_DIR"] module.exports.path.synctexBinHostPath = process.env["SYNCTEX_BIN_HOST_PATH"] +console.log module.exports \ No newline at end of file From 1101763f50d5c1511f1f172af0bc0e7e70220168 Mon Sep 17 00:00:00 2001 From: henry oswald <henry.oswald@gmail.com> Date: Fri, 25 May 2018 12:43:12 +0000 Subject: [PATCH 382/754] all but the sync tests should pass --- services/clsi/config/settings.defaults.coffee | 3 ++- services/clsi/docker-compose-config.yml | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index ab6e7e6df0..2da6834955 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -46,4 +46,5 @@ if process.env["DOCKER_RUNNER"] module.exports.path.sandboxedCompilesHostDir = process.env["COMPILES_HOST_DIR"] module.exports.path.synctexBinHostPath = process.env["SYNCTEX_BIN_HOST_PATH"] -console.log module.exports \ No newline at end of file +console.log "configggggg" +console.log module.exports diff --git a/services/clsi/docker-compose-config.yml b/services/clsi/docker-compose-config.yml index ce0c502d0e..3b780604fe 100644 --- a/services/clsi/docker-compose-config.yml +++ b/services/clsi/docker-compose-config.yml @@ -4,7 +4,7 @@ services: dev: environment: TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2017.1 - TEXLIVE_IMAGE_USER: root + TEXLIVE_IMAGE_USER: "tex" SHARELATEX_CONFIG: /app/config/settings.defaults.coffee DOCKER_RUNNER: "true" COMPILES_HOST_DIR: $PWD/compiles @@ -18,7 +18,7 @@ services: ci: environment: TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2017.1 - TEXLIVE_IMAGE_USER: tex + TEXLIVE_IMAGE_USER: "tex" SHARELATEX_CONFIG: /app/config/settings.defaults.coffee DOCKER_RUNNER: "true" COMPILES_HOST_DIR: $PWD/compiles @@ -27,3 +27,4 @@ services: volumes: - /var/run/docker.sock:/var/run/docker.sock:rw - ./cache:/app/cache + - ./compiles:/app/compiles From 0e2603e4be1d4f2b2c71d633de1efa5014abf473 Mon Sep 17 00:00:00 2001 From: henry oswald <henry.oswald@gmail.com> Date: Fri, 25 May 2018 13:45:07 +0000 Subject: [PATCH 383/754] change synctex binary and added it to mounted volumes in docker config --- services/clsi/bin/synctex | Bin 92840 -> 92776 bytes services/clsi/docker-compose-config.yml | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/services/clsi/bin/synctex b/services/clsi/bin/synctex index e793e248bb52d48f28a105fb6dd2ea9ead1659e1..89b8cc698827b5e02a1b6053f32098766304b739 100755 GIT binary patch literal 92776 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 literal 92840 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 diff --git a/services/clsi/docker-compose-config.yml b/services/clsi/docker-compose-config.yml index 3b780604fe..c8b7dcc274 100644 --- a/services/clsi/docker-compose-config.yml +++ b/services/clsi/docker-compose-config.yml @@ -13,6 +13,7 @@ services: - /var/run/docker.sock:/var/run/docker.sock - ./compiles:/app/compiles - ./cache:/app/cache + - ./bin/synctex:/app/bin/synctex ci: @@ -26,5 +27,6 @@ services: SQLITE_PATH: /app/compiles/db.sqlite volumes: - /var/run/docker.sock:/var/run/docker.sock:rw - - ./cache:/app/cache - ./compiles:/app/compiles + - ./cache:/app/cache + - ./bin/synctex:/app/bin/synctex From b2fa2e7d55850c93abd9dc1a2f7828c099ea5f77 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 25 May 2018 15:30:26 +0100 Subject: [PATCH 384/754] bumped timeout to 30 seconds --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index a0ea4f036a..49a259a956 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -10,7 +10,7 @@ "compile:app": "([ -e app/coffee ] && coffee $COFFEE_OPTIONS -o app/js -c app/coffee || echo 'No CoffeeScript folder to compile') && ( [ -e app.coffee ] && coffee $COFFEE_OPTIONS -c app.coffee || echo 'No CoffeeScript app to compile')", "compile": "npm run compile:app && npm run compile:test:acceptance && npm run compile:test:smoke", "start": "npm run compile:app && node $NODE_APP_OPTIONS app.js", - "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", + "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 30000 --exit $@ test/acceptance/js", "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- --grep=$MOCHA_GREP", "test:unit:_run": "mocha --recursive --reporter spec --exit $@ test/unit/js", "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- --grep=$MOCHA_GREP", From 15aae19d2d3e8e50d5fc69b3eb830eaf0050c0c8 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Tue, 12 Jun 2018 10:15:17 +0100 Subject: [PATCH 385/754] Use metadata to determine Google Cloud project dynamically. Fixes: #601 --- services/clsi/bin/install_texlive_gce.sh | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/services/clsi/bin/install_texlive_gce.sh b/services/clsi/bin/install_texlive_gce.sh index 85ab709694..4f98fed7e6 100755 --- a/services/clsi/bin/install_texlive_gce.sh +++ b/services/clsi/bin/install_texlive_gce.sh @@ -1,13 +1,19 @@ #!/bin/sh METADATA=http://metadata.google.internal./computeMetadata/v1 SVC_ACCT=$METADATA/instance/service-accounts/default +PROJECT_URL=$METADATA/project/project-id ACCESS_TOKEN=$(curl -s -H 'Metadata-Flavor: Google' $SVC_ACCT/token | cut -d'"' -f 4) -if [ -z "$ACCESS_TOKEN" ]; then - echo "No acccess token to download texlive-full images from google container, continuing without downloading. This is likely not a google cloud enviroment." +#if [ -z "$ACCESS_TOKEN" ]; then +# echo "No acccess token to download texlive-full images from google container, continuing without downloading. This is likely not a google cloud enviroment." +# exit 0 +#fi +PROJECT=$(curl -s -H 'Metadata-Flavor: Google' $PROJECT_URL) +if [ -z "$PROJECT" ]; then + echo "No project name to download texlive-full images from google container, continuing without downloading. This is likely not a google cloud enviroment." exit 0 fi docker login -u '_token' -p $ACCESS_TOKEN https://gcr.io -docker pull --all-tags gcr.io/henry-terraform-admin/texlive-full #TODO NEED TO MAKE THIS AN ENV VAR +docker pull --all-tags gcr.io/$PROJECT/texlive-full cp /app/bin/synctex /app/bin/synctex-mount/synctex echo "Finished downloading texlive-full images" From 824745dfbc0537e89a26f448acc4cb5175df0569 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Tue, 12 Jun 2018 10:22:30 +0100 Subject: [PATCH 386/754] Update build scripts from 1.1.3 to 1.1.6 --- services/clsi/Jenkinsfile | 9 ++++++++- services/clsi/Makefile | 11 ++++++++--- services/clsi/docker-compose.ci.yml | 9 +++++---- services/clsi/docker-compose.yml | 4 +++- services/clsi/package.json | 4 ++-- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index bc9ba0142f..eddce50dba 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -29,7 +29,13 @@ pipeline { stage('Package and publish build') { steps { - sh 'make publish' + + withCredentials([file(credentialsId: 'csh-gcdm-test', variable: 'DOCKER_REPO_KEY_PATH')]) { + sh 'docker login -u _json_key --password-stdin https://csh-gcdm-test < ${DOCKER_REPO_KEY_PATH}' + } + sh 'DOCKER_REPO=csh-gcdm-test make publish' + sh 'docker logout https://csh-gcdm-test' + } } @@ -47,6 +53,7 @@ pipeline { post { always { sh 'DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" make test_clean' + sh 'make clean' } failure { diff --git a/services/clsi/Makefile b/services/clsi/Makefile index 63ff8c02fc..ae7cf16eb8 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.3 +# Version: 1.1.6 BUILD_NUMBER ?= local BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) @@ -15,6 +15,8 @@ DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ clean: + docker rmi ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) + docker rmi csh-gcdm-test/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) rm -f app.js rm -rf app/js rm -rf test/unit/js @@ -34,9 +36,12 @@ test_clean: test_acceptance_pre_run: @[ ! -f test/acceptance/scripts/pre-run ] && echo "clsi has no pre acceptance tests task" || $(DOCKER_COMPOSE) run --rm test_acceptance test/acceptance/scripts/pre-run build: - docker build --pull --tag gcr.io/csh-gcdm-test/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) . + docker build --pull --tag ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ + --tag csh-gcdm-test/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ + . publish: - docker push gcr.io/csh-gcdm-test/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) + + docker push $(DOCKER_REPO)/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) .PHONY: clean test test_unit test_acceptance test_clean build publish diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 0e14b0f0dc..651b4546d4 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -1,25 +1,27 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.3 +# Version: 1.1.6 version: "2" services: test_unit: - image: gcr.io/csh-gcdm-test/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER + image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER command: npm run test:unit:_run test_acceptance: build: . - image: gcr.io/csh-gcdm-test/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER + image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER extends: file: docker-compose-config.yml service: ci environment: + ELASTIC_SEARCH_DSN: es:9200 REDIS_HOST: redis MONGO_HOST: mongo POSTGRES_HOST: postgres + MOCHA_GREP: ${MOCHA_GREP} depends_on: - mongo - redis @@ -30,4 +32,3 @@ services: mongo: image: mongo:3.4 - diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 2283746133..a0b426195b 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.3 +# Version: 1.1.6 version: "2" @@ -24,6 +24,7 @@ services: file: docker-compose-config.yml service: dev environment: + ELASTIC_SEARCH_DSN: es:9200 REDIS_HOST: redis MONGO_HOST: mongo POSTGRES_HOST: postgres @@ -32,6 +33,7 @@ services: - mongo - redis command: npm run test:acceptance + redis: image: redis diff --git a/services/clsi/package.json b/services/clsi/package.json index 49a259a956..8bcdba29c1 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -10,9 +10,9 @@ "compile:app": "([ -e app/coffee ] && coffee $COFFEE_OPTIONS -o app/js -c app/coffee || echo 'No CoffeeScript folder to compile') && ( [ -e app.coffee ] && coffee $COFFEE_OPTIONS -c app.coffee || echo 'No CoffeeScript app to compile')", "compile": "npm run compile:app && npm run compile:test:acceptance && npm run compile:test:smoke", "start": "npm run compile:app && node $NODE_APP_OPTIONS app.js", - "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 30000 --exit $@ test/acceptance/js", + "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- --grep=$MOCHA_GREP", - "test:unit:_run": "mocha --recursive --reporter spec --exit $@ test/unit/js", + "test:unit:_run": "mocha --recursive --reporter spec --exit $@ test/unit/js", "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- --grep=$MOCHA_GREP", "compile:unit_tests": "[ ! -e test/unit/coffee ] && echo 'No unit tests to compile' || coffee -o test/unit/js -c test/unit/coffee", "compile:acceptance_tests": "[ ! -e test/acceptance/coffee ] && echo 'No acceptance tests to compile' || coffee -o test/acceptance/js -c test/acceptance/coffee", From 2155657651c6565b3d9f6705b8583e7039f25003 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Tue, 12 Jun 2018 11:17:26 +0100 Subject: [PATCH 387/754] Accidently left warning message commented out :( --- services/clsi/bin/install_texlive_gce.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/clsi/bin/install_texlive_gce.sh b/services/clsi/bin/install_texlive_gce.sh index 4f98fed7e6..ee6efba471 100755 --- a/services/clsi/bin/install_texlive_gce.sh +++ b/services/clsi/bin/install_texlive_gce.sh @@ -3,10 +3,10 @@ METADATA=http://metadata.google.internal./computeMetadata/v1 SVC_ACCT=$METADATA/instance/service-accounts/default PROJECT_URL=$METADATA/project/project-id ACCESS_TOKEN=$(curl -s -H 'Metadata-Flavor: Google' $SVC_ACCT/token | cut -d'"' -f 4) -#if [ -z "$ACCESS_TOKEN" ]; then -# echo "No acccess token to download texlive-full images from google container, continuing without downloading. This is likely not a google cloud enviroment." -# exit 0 -#fi +if [ -z "$ACCESS_TOKEN" ]; then + echo "No acccess token to download texlive-full images from google container, continuing without downloading. This is likely not a google cloud enviroment." + exit 0 +fi PROJECT=$(curl -s -H 'Metadata-Flavor: Google' $PROJECT_URL) if [ -z "$PROJECT" ]; then echo "No project name to download texlive-full images from google container, continuing without downloading. This is likely not a google cloud enviroment." From cfa1b8ef649a6aaadc01986994486cc1db3cb603 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Tue, 12 Jun 2018 15:04:14 +0100 Subject: [PATCH 388/754] Increase acceptance test timeout. --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index 8bcdba29c1..92265ebf63 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -10,7 +10,7 @@ "compile:app": "([ -e app/coffee ] && coffee $COFFEE_OPTIONS -o app/js -c app/coffee || echo 'No CoffeeScript folder to compile') && ( [ -e app.coffee ] && coffee $COFFEE_OPTIONS -c app.coffee || echo 'No CoffeeScript app to compile')", "compile": "npm run compile:app && npm run compile:test:acceptance && npm run compile:test:smoke", "start": "npm run compile:app && node $NODE_APP_OPTIONS app.js", - "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", + "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 30000 --exit $@ test/acceptance/js", "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- --grep=$MOCHA_GREP", "test:unit:_run": "mocha --recursive --reporter spec --exit $@ test/unit/js", "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- --grep=$MOCHA_GREP", From 8ff001ad54d6d2cbfc9e920b87d7662b80fa1d62 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Tue, 12 Jun 2018 15:26:10 +0100 Subject: [PATCH 389/754] Specify repo correctly --- services/clsi/Jenkinsfile | 8 ++++---- services/clsi/Makefile | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index eddce50dba..b88e3e4412 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -30,11 +30,11 @@ pipeline { stage('Package and publish build') { steps { - withCredentials([file(credentialsId: 'csh-gcdm-test', variable: 'DOCKER_REPO_KEY_PATH')]) { - sh 'docker login -u _json_key --password-stdin https://csh-gcdm-test < ${DOCKER_REPO_KEY_PATH}' + withCredentials([file(credentialsId: 'gcr.io_csh-gcdm-test', variable: 'DOCKER_REPO_KEY_PATH')]) { + sh 'docker login -u _json_key --password-stdin https://gcr.io/csh-gcdm-test < ${DOCKER_REPO_KEY_PATH}' } - sh 'DOCKER_REPO=csh-gcdm-test make publish' - sh 'docker logout https://csh-gcdm-test' + sh 'DOCKER_REPO=gcr.io/csh-gcdm-test make publish' + sh 'docker logout https://gcr.io/csh-gcdm-test' } } diff --git a/services/clsi/Makefile b/services/clsi/Makefile index ae7cf16eb8..a1cefa4cbf 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -16,7 +16,7 @@ DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ clean: docker rmi ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) - docker rmi csh-gcdm-test/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) + docker rmi gcr.io/csh-gcdm-test/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) rm -f app.js rm -rf app/js rm -rf test/unit/js @@ -37,7 +37,7 @@ test_acceptance_pre_run: @[ ! -f test/acceptance/scripts/pre-run ] && echo "clsi has no pre acceptance tests task" || $(DOCKER_COMPOSE) run --rm test_acceptance test/acceptance/scripts/pre-run build: docker build --pull --tag ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ - --tag csh-gcdm-test/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ + --tag gcr.io/csh-gcdm-test/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ . publish: From 62a33701e972984a5f8ba758bc561167efd11438 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 12 Jun 2018 17:44:13 +0100 Subject: [PATCH 390/754] update build scripts so smoke tests are compiled --- services/clsi/Makefile | 2 +- services/clsi/docker-compose.ci.yml | 2 +- services/clsi/docker-compose.yml | 2 +- services/clsi/package.json | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/services/clsi/Makefile b/services/clsi/Makefile index a1cefa4cbf..ca126e8e48 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.6 +# Version: 1.1.7 BUILD_NUMBER ?= local BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 651b4546d4..9f3d54d1f4 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.6 +# Version: 1.1.7 version: "2" diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index a0b426195b..4c1cfc73f2 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.6 +# Version: 1.1.7 version: "2" diff --git a/services/clsi/package.json b/services/clsi/package.json index 92265ebf63..54625fd273 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -16,9 +16,9 @@ "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- --grep=$MOCHA_GREP", "compile:unit_tests": "[ ! -e test/unit/coffee ] && echo 'No unit tests to compile' || coffee -o test/unit/js -c test/unit/coffee", "compile:acceptance_tests": "[ ! -e test/acceptance/coffee ] && echo 'No acceptance tests to compile' || coffee -o test/acceptance/js -c test/acceptance/coffee", - "compile:all": "npm run compile:app && npm run compile:unit_tests && npm run compile:acceptance_tests", + "compile:all": "npm run compile:app && npm run compile:unit_tests && npm run compile:acceptance_tests && npm run compile:smoke_tests", "nodemon": "nodemon --config nodemon.json", - "compile:smoke_tests": "[ -e test/smoke ] && coffee -o test/smoke/js -c test/smoke/coffee || echo 'No smoke tests to compile, skipping'" + "compile:smoke_tests": "[ ! -e test/smoke/coffee] && echo 'No smoke tests to compile' || coffee -o test/smoke/js -c test/smoke/coffee" }, "author": "James Allen <james@sharelatex.com>", "dependencies": { From 2c10ac06f249af9bd3c8d21d175f9b3b69cea376 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 12 Jun 2018 17:48:23 +0100 Subject: [PATCH 391/754] remove the compile npm command, it isn't needed --- services/clsi/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index 54625fd273..79f9c806d5 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -8,7 +8,6 @@ }, "scripts": { "compile:app": "([ -e app/coffee ] && coffee $COFFEE_OPTIONS -o app/js -c app/coffee || echo 'No CoffeeScript folder to compile') && ( [ -e app.coffee ] && coffee $COFFEE_OPTIONS -c app.coffee || echo 'No CoffeeScript app to compile')", - "compile": "npm run compile:app && npm run compile:test:acceptance && npm run compile:test:smoke", "start": "npm run compile:app && node $NODE_APP_OPTIONS app.js", "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 30000 --exit $@ test/acceptance/js", "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- --grep=$MOCHA_GREP", From ad8018ce0de4599dd285f419fe1373b1a30ff77d Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Wed, 13 Jun 2018 15:47:45 +0100 Subject: [PATCH 392/754] Add csh-staging to repos --- services/clsi/Jenkinsfile | 6 ++++++ services/clsi/Makefile | 2 ++ 2 files changed, 8 insertions(+) diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index b88e3e4412..efe844839b 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -36,6 +36,12 @@ pipeline { sh 'DOCKER_REPO=gcr.io/csh-gcdm-test make publish' sh 'docker logout https://gcr.io/csh-gcdm-test' + withCredentials([file(credentialsId: 'gcr.io_csh-staging', variable: 'DOCKER_REPO_KEY_PATH')]) { + sh 'docker login -u _json_key --password-stdin https://gcr.io/csh-staging < ${DOCKER_REPO_KEY_PATH}' + } + sh 'DOCKER_REPO=gcr.io/csh-staging make publish' + sh 'docker logout https://gcr.io/csh-staging' + } } diff --git a/services/clsi/Makefile b/services/clsi/Makefile index ca126e8e48..b7c96a2b18 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -17,6 +17,7 @@ DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ clean: docker rmi ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) docker rmi gcr.io/csh-gcdm-test/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) + docker rmi gcr.io/csh-staging/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) rm -f app.js rm -rf app/js rm -rf test/unit/js @@ -38,6 +39,7 @@ test_acceptance_pre_run: build: docker build --pull --tag ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ --tag gcr.io/csh-gcdm-test/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ + --tag gcr.io/csh-staging/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ . publish: From b95317c8fe49f3cc4b2bf90eff6a325db7b5c95b Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Mon, 25 Jun 2018 14:06:18 +0100 Subject: [PATCH 393/754] increase timeout on wordcount --- services/clsi/app/coffee/CompileManager.coffee | 2 +- services/clsi/test/unit/coffee/CompileManagerTests.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 167a80e135..b0573d1a13 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -281,7 +281,7 @@ module.exports = CompileManager = file_path = "$COMPILE_DIR/" + file_name command = [ "texcount", '-nocol', '-inc', file_path, "-out=" + file_path + ".wc"] directory = getCompileDir(project_id, user_id) - timeout = 10 * 1000 + timeout = 60 * 1000 # increased to allow for large projects compileName = getCompileName(project_id, user_id) CommandRunner.run compileName, command, directory, image, timeout, {}, (error) -> diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 341ce2d0fe..14ddb2e11f 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -298,7 +298,7 @@ describe "CompileManager", -> @callback = sinon.stub() @project_id = "project-id-123" - @timeout = 10 * 1000 + @timeout = 60 * 1000 @file_name = "main.tex" @Settings.path.compilesDir = "/local/compile/directory" @image = "example.com/image" From c4e8d76427f08998bcc47b83b71712b0b4b04518 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 26 Jun 2018 12:43:47 +0100 Subject: [PATCH 394/754] added seccomp --- services/clsi/config/settings.defaults.coffee | 1 + services/clsi/seccomp/clsi-profile.json | 792 ++++++++++++++++++ 2 files changed, 793 insertions(+) create mode 100644 services/clsi/seccomp/clsi-profile.json diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index 2da6834955..ced53a917f 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -40,6 +40,7 @@ if process.env["DOCKER_RUNNER"] user: process.env["TEXLIVE_IMAGE_USER"] or "tex" expireProjectAfterIdleMs: 24 * 60 * 60 * 1000 checkProjectsIntervalMs: 10 * 60 * 1000 + seccomp_profile: JSON.stringify(JSON.parse(require("fs").readFileSync(Path.resolve(__dirname + "/../seccomp/clsi-profile.json")))) module.exports.path.synctexBaseDir = -> "/compile" diff --git a/services/clsi/seccomp/clsi-profile.json b/services/clsi/seccomp/clsi-profile.json new file mode 100644 index 0000000000..cab7a445a4 --- /dev/null +++ b/services/clsi/seccomp/clsi-profile.json @@ -0,0 +1,792 @@ +{ + "defaultAction": "SCMP_ACT_ERRNO", + "architectures": [ + "SCMP_ARCH_X86_64", + "SCMP_ARCH_X86", + "SCMP_ARCH_X32" + ], + "syscalls": [ + { + "name": "access", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "arch_prctl", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "brk", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "chdir", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "chmod", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "clone", + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 2080505856, + "valueTwo": 0, + "op": "SCMP_CMP_MASKED_EQ" + } + ] + }, + { + "name": "close", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "copy_file_range", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "creat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "dup", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "dup2", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "dup3", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "execve", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "execveat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "exit", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "exit_group", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "faccessat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fadvise64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fadvise64_64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fallocate", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fchdir", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fchmod", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fchmodat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fcntl", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fcntl64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fdatasync", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fork", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fstat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fstat64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fstatat64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fstatfs", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fstatfs64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fsync", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "ftruncate", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "ftruncate64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "futex", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "futimesat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getcpu", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getcwd", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getdents", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getdents64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getegid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getegid32", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "geteuid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "geteuid32", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getgid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getgid32", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getgroups", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getgroups32", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getpgid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getpgrp", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getpid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getppid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getpriority", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getresgid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getresgid32", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getresuid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getresuid32", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getrlimit", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "get_robust_list", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getrusage", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getsid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "gettid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getuid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getuid32", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "ioctl", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "kill", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "_llseek", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "lseek", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "lstat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "lstat64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "madvise", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "mkdir", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "mkdirat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "mmap", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "mmap2", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "mprotect", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "mremap", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "munmap", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "newfstatat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "open", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "openat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "pause", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "pipe", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "pipe2", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "prctl", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "pread64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "preadv", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "prlimit64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "pwrite64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "pwritev", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "read", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "readlink", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "readlinkat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "readv", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "rename", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "renameat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "renameat2", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "restart_syscall", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "rmdir", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "rt_sigaction", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "rt_sigpending", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "rt_sigprocmask", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "rt_sigqueueinfo", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "rt_sigreturn", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "rt_sigsuspend", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "rt_sigtimedwait", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "rt_tgsigqueueinfo", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sched_getaffinity", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sched_getparam", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sched_get_priority_max", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sched_get_priority_min", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sched_getscheduler", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sched_rr_get_interval", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sched_yield", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sendfile", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sendfile64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "setgroups", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "setgroups32", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "set_robust_list", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "set_tid_address", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sigaltstack", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "stat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "stat64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "statfs", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "statfs64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sync", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sync_file_range", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "syncfs", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sysinfo", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "tgkill", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "times", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "tkill", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "truncate", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "truncate64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "umask", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "uname", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "unlink", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "unlinkat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "utime", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "utimensat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "utimes", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "vfork", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "vhangup", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "wait4", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "waitid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "write", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "writev", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "pread", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "setgid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "setuid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "capget", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "capset", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fchown", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "gettimeofday", + "action": "SCMP_ACT_ALLOW", + "args": [] + } + ] +} \ No newline at end of file From 3640326a26ce395aa588870c84c0c155c2f70a36 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 26 Jun 2018 14:44:03 +0100 Subject: [PATCH 395/754] put seccomp_profile_path into variable and try catch --- services/clsi/config/settings.defaults.coffee | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index ced53a917f..901637cdf6 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -40,7 +40,12 @@ if process.env["DOCKER_RUNNER"] user: process.env["TEXLIVE_IMAGE_USER"] or "tex" expireProjectAfterIdleMs: 24 * 60 * 60 * 1000 checkProjectsIntervalMs: 10 * 60 * 1000 - seccomp_profile: JSON.stringify(JSON.parse(require("fs").readFileSync(Path.resolve(__dirname + "/../seccomp/clsi-profile.json")))) + + try + seccomp_profile_path = Path.resolve(__dirname + "/../seccomp/clsi-profile.json") + module.exports.clsi.seccomp_profile = JSON.stringify(JSON.parse(require("fs").readFileSync(seccomp_profile_path))) + catch + console.log "could not load seccom profile from #{seccomp_profile_path}" module.exports.path.synctexBaseDir = -> "/compile" From 5c3c39c7435e9112f4f363dc59dde2b1dd615b30 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 26 Jun 2018 15:04:56 +0100 Subject: [PATCH 396/754] add error catch to settings.defaults --- services/clsi/config/settings.defaults.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index 901637cdf6..a5b6f1bd6d 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -44,8 +44,8 @@ if process.env["DOCKER_RUNNER"] try seccomp_profile_path = Path.resolve(__dirname + "/../seccomp/clsi-profile.json") module.exports.clsi.seccomp_profile = JSON.stringify(JSON.parse(require("fs").readFileSync(seccomp_profile_path))) - catch - console.log "could not load seccom profile from #{seccomp_profile_path}" + catch error + console.log error, "could not load seccom profile from #{seccomp_profile_path}" module.exports.path.synctexBaseDir = -> "/compile" From c33c9f8fc7671a52e124160909e84c405a595bfe Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 26 Jun 2018 15:38:30 +0100 Subject: [PATCH 397/754] fix seccomp key --- services/clsi/config/settings.defaults.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index a5b6f1bd6d..da4aa82c17 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -43,7 +43,7 @@ if process.env["DOCKER_RUNNER"] try seccomp_profile_path = Path.resolve(__dirname + "/../seccomp/clsi-profile.json") - module.exports.clsi.seccomp_profile = JSON.stringify(JSON.parse(require("fs").readFileSync(seccomp_profile_path))) + module.exports.clsi.docker.seccomp_profile = JSON.stringify(JSON.parse(require("fs").readFileSync(seccomp_profile_path))) catch error console.log error, "could not load seccom profile from #{seccomp_profile_path}" From 2384eb83d935d691871e653be0e2ddba4f61ff3b Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 28 Jun 2018 16:04:34 +0100 Subject: [PATCH 398/754] add load balance http endpoints to shut box down --- services/clsi/app.coffee | 32 ++++++++++++++----- services/clsi/config/settings.defaults.coffee | 9 ++++-- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 11981a72aa..cf01b47943 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -174,7 +174,11 @@ os = require "os" STATE = "up" -server = net.createServer (socket) -> +process.on "SIGHUP", -> + console.log "got SIGHUP event" + STATE = "down" + +loadTcpServer = net.createServer (socket) -> socket.on "error", (err)-> if err.code == "ECONNRESET" # this always comes up, we don't know why @@ -182,7 +186,7 @@ server = net.createServer (socket) -> logger.err err:err, "error with socket on load check" socket.destroy() - if STATE == "up" and Settings.load_balancer_agent.report_load + if STATE == "up" and Settings.internal.load_balancer_agent.report_load currentLoad = os.loadavg()[0] # staging clis's have 1 cpu core only @@ -201,26 +205,38 @@ server = net.createServer (socket) -> socket.write("#{STATE}\n", "ASCII") socket.end() +loadHttpServer = express() +loadHttpServer.post "/state/up", (req, res, next) -> + STATE = "up" + logger.info "getting message to set server to down" + res.sendStatus 204 +loadHttpServer.post "/state/down", (req, res, next) -> + STATE = "down" + logger.info "getting message to set server to down" + res.sendStatus 204 port = (Settings.internal?.clsi?.port or 3013) host = (Settings.internal?.clsi?.host or "localhost") -load_port = Settings.internal.clsi.load_port or 3048 - +load_tcp_port = Settings.internal.load_balancer_agent.load_port +load_http_port = Settings.internal.load_balancer_agent.local_port if !module.parent # Called directly app.listen port, host, (error) -> logger.info "CLSI starting up, listening on #{host}:#{port}" - server.listen load_port, host, (error) -> + loadTcpServer.listen load_tcp_port, host, (error) -> throw error if error? - logger.info "Load agent listening on load port #{load_port}" + logger.info "Load tcp agent listening on load port #{load_tcp_port}" + + loadHttpServer.listen load_http_port, host, (error) -> + throw error if error? + logger.info "Load http agent listening on load port #{load_http_port}" + module.exports = app - - setInterval () -> ProjectPersistenceManager.clearExpiredProjects() , tenMinutes = 10 * 60 * 1000 diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index da4aa82c17..a64941f866 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -20,7 +20,11 @@ module.exports = clsi: port: 3013 host: process.env["LISTEN_ADDRESS"] or "localhost" - + + load_balancer_agent: + report_load:true + load_port: 3048 + local_port: 3049 apis: clsi: url: "http://#{process.env['CLSI_HOST'] or 'localhost'}:3013" @@ -29,6 +33,7 @@ module.exports = project_cache_length_ms: 1000 * 60 * 60 * 24 parallelFileDownloads:1 + if process.env["DOCKER_RUNNER"] module.exports.clsi = dockerRunner: process.env["DOCKER_RUNNER"] == "true" @@ -52,5 +57,3 @@ if process.env["DOCKER_RUNNER"] module.exports.path.sandboxedCompilesHostDir = process.env["COMPILES_HOST_DIR"] module.exports.path.synctexBinHostPath = process.env["SYNCTEX_BIN_HOST_PATH"] -console.log "configggggg" -console.log module.exports From eb89ca1d3b5b0b94b8c23f96c161efa66f97d545 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 3 Jul 2018 16:41:34 +0100 Subject: [PATCH 399/754] added filestoreDomainOveride --- services/clsi/app/coffee/UrlFetcher.coffee | 6 ++++ services/clsi/config/settings.defaults.coffee | 1 + .../test/unit/coffee/UrlFetcherTests.coffee | 35 +++++++++++++++++-- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/UrlFetcher.coffee b/services/clsi/app/coffee/UrlFetcher.coffee index 201306c01d..58645f0034 100644 --- a/services/clsi/app/coffee/UrlFetcher.coffee +++ b/services/clsi/app/coffee/UrlFetcher.coffee @@ -1,6 +1,8 @@ request = require("request").defaults(jar: false) fs = require("fs") logger = require "logger-sharelatex" +settings = require("settings-sharelatex") +URL = require('url'); oneMinute = 60 * 1000 @@ -11,6 +13,9 @@ module.exports = UrlFetcher = _callback(error) _callback = () -> + if settings.filestoreDomainOveride? + p = URL.parse(url).path + url = "#{settings.filestoreDomainOveride}#{p}" timeoutHandler = setTimeout () -> timeoutHandler = null logger.error url:url, filePath: filePath, "Timed out downloading file to cache" @@ -31,6 +36,7 @@ module.exports = UrlFetcher = logger.log url:url, filePath: filePath, "finished downloading file into cache" urlStream.on "response", (res) -> + console.log if res.statusCode >= 200 and res.statusCode < 300 fileStream = fs.createWriteStream(filePath) diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index a64941f866..081ded81a2 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -32,6 +32,7 @@ module.exports = smokeTest: process.env["SMOKE_TEST"] or false project_cache_length_ms: 1000 * 60 * 60 * 24 parallelFileDownloads:1 + filestoreDomainOveride: process.env["FILESTORE_DOMAIN_OVERRIDE"] if process.env["DOCKER_RUNNER"] diff --git a/services/clsi/test/unit/coffee/UrlFetcherTests.coffee b/services/clsi/test/unit/coffee/UrlFetcherTests.coffee index 4bd161bca5..e91720e52b 100644 --- a/services/clsi/test/unit/coffee/UrlFetcherTests.coffee +++ b/services/clsi/test/unit/coffee/UrlFetcherTests.coffee @@ -7,16 +7,47 @@ EventEmitter = require("events").EventEmitter describe "UrlFetcher", -> beforeEach -> @callback = sinon.stub() - @url = "www.example.com/file" + @url = "https://www.example.com/file/here?query=string" @UrlFetcher = SandboxedModule.require modulePath, requires: request: defaults: @defaults = sinon.stub().returns(@request = {}) fs: @fs = {} "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } + "settings-sharelatex": @settings = {} it "should turn off the cookie jar in request", -> @defaults.calledWith(jar: false) .should.equal true - + + describe "rewrite url domain if filestoreDomainOveride is set", -> + beforeEach -> + @path = "/path/to/file/on/disk" + @request.get = sinon.stub().returns(@urlStream = new EventEmitter) + @urlStream.pipe = sinon.stub() + @urlStream.pause = sinon.stub() + @urlStream.resume = sinon.stub() + @fs.createWriteStream = sinon.stub().returns(@fileStream = new EventEmitter) + @fs.unlink = (file, callback) -> callback() + + it "should use the normal domain when override not set", (done)-> + @UrlFetcher.pipeUrlToFile @url, @path, => + @request.get.args[0][0].url.should.equal @url + done() + @res = statusCode: 200 + @urlStream.emit "response", @res + @urlStream.emit "end" + @fileStream.emit "finish" + + + it "should use override domain when filestoreDomainOveride is set", (done)-> + @settings.filestoreDomainOveride = "192.11.11.11" + @UrlFetcher.pipeUrlToFile @url, @path, => + @request.get.args[0][0].url.should.equal "192.11.11.11/file/here?query=string" + done() + @res = statusCode: 200 + @urlStream.emit "response", @res + @urlStream.emit "end" + @fileStream.emit "finish" + describe "pipeUrlToFile", -> beforeEach (done)-> @path = "/path/to/file/on/disk" From 6825285b02ce24ce095a9f0f08e1ac3644119eae Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 5 Jul 2018 15:07:07 +0100 Subject: [PATCH 400/754] added maint down endpoint --- services/clsi/app.coffee | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index cf01b47943..88bb74b3bc 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -174,9 +174,6 @@ os = require "os" STATE = "up" -process.on "SIGHUP", -> - console.log "got SIGHUP event" - STATE = "down" loadTcpServer = net.createServer (socket) -> socket.on "error", (err)-> @@ -217,6 +214,12 @@ loadHttpServer.post "/state/down", (req, res, next) -> logger.info "getting message to set server to down" res.sendStatus 204 +loadHttpServer.post "/state/maint", (req, res, next) -> + STATE = "maint" + logger.info "getting message to set server to maint" + res.sendStatus 204 + + port = (Settings.internal?.clsi?.port or 3013) host = (Settings.internal?.clsi?.host or "localhost") From 12bfe122b9b2e73374de272d458116e355222da4 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Fri, 6 Jul 2018 15:08:38 +0100 Subject: [PATCH 401/754] Use our experimental metrics --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index 79f9c806d5..0edb11606f 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -31,7 +31,7 @@ "lockfile": "^1.0.3", "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", "lynx": "0.0.11", - "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.5.0", + "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.8.1-beta", "mkdirp": "0.3.5", "mysql": "2.6.2", "request": "^2.21.0", From 68f708cf555d5c6700c068a3c52c0cac1605bad9 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Thu, 12 Jul 2018 11:22:02 +0100 Subject: [PATCH 402/754] Depend on metrics v1.8.1 for remote StatsD host --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index 0edb11606f..ab34cda854 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -31,7 +31,7 @@ "lockfile": "^1.0.3", "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", "lynx": "0.0.11", - "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.8.1-beta", + "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.8.1", "mkdirp": "0.3.5", "mysql": "2.6.2", "request": "^2.21.0", From 4ec8a423cb02220262c25f37199e1c77ec9d601d Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 13 Jul 2018 10:37:22 +0100 Subject: [PATCH 403/754] added texliveImageNameOveride --- services/clsi/app/coffee/RequestParser.coffee | 6 ++++++ services/clsi/config/settings.defaults.coffee | 1 + .../unit/coffee/RequestParserTests.coffee | 19 ++++++++++++++++++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/RequestParser.coffee b/services/clsi/app/coffee/RequestParser.coffee index 596b52995c..d375e9e331 100644 --- a/services/clsi/app/coffee/RequestParser.coffee +++ b/services/clsi/app/coffee/RequestParser.coffee @@ -1,3 +1,5 @@ +settings = require("settings-sharelatex") + module.exports = RequestParser = VALID_COMPILERS: ["pdflatex", "latex", "xelatex", "lualatex"] MAX_TIMEOUT: 300 @@ -67,6 +69,10 @@ module.exports = RequestParser = sanitizedRootResourcePath = RequestParser._sanitizePath(rootResourcePath) response.rootResourcePath = RequestParser._checkPath(sanitizedRootResourcePath) + if settings.texliveImageNameOveride? + tag = compile.options.imageName.split(":")[1] + response.imageName = "#{settings.texliveImageNameOveride}:#{tag}" + for resource in response.resources if resource.path == originalRootResourcePath resource.path = sanitizedRootResourcePath diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index 081ded81a2..1f3fe8be02 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -33,6 +33,7 @@ module.exports = project_cache_length_ms: 1000 * 60 * 60 * 24 parallelFileDownloads:1 filestoreDomainOveride: process.env["FILESTORE_DOMAIN_OVERRIDE"] + texliveImageNameOveride: process.env["TEX_LIVE_IMAGE_NAME_OVERRIDE"] if process.env["DOCKER_RUNNER"] diff --git a/services/clsi/test/unit/coffee/RequestParserTests.coffee b/services/clsi/test/unit/coffee/RequestParserTests.coffee index 0b420b362d..04405ac6a3 100644 --- a/services/clsi/test/unit/coffee/RequestParserTests.coffee +++ b/services/clsi/test/unit/coffee/RequestParserTests.coffee @@ -16,10 +16,12 @@ describe "RequestParser", -> compile: token: "token-123" options: + imageName: "basicImageName/here:2017-1" compiler: "pdflatex" timeout: 42 resources: [] - @RequestParser = SandboxedModule.require modulePath + @RequestParser = SandboxedModule.require modulePath, requires: + "settings-sharelatex": @settings = {} afterEach -> tk.reset() @@ -57,6 +59,21 @@ describe "RequestParser", -> it "should set the compiler to pdflatex by default", -> @data.compiler.should.equal "pdflatex" + describe "with imageName set", -> + beforeEach -> + @RequestParser.parse @validRequest, (error, @data) => + + it "should set the imageName", -> + @data.imageName.should.equal "basicImageName/here:2017-1" + + describe "with texliveImageNameOveride set", -> + beforeEach -> + @settings.texliveImageNameOveride = "usethisoveride/overhere" + @RequestParser.parse @validRequest, (error, @data) => + + it "should override the image path", -> + @data.imageName.should.equal "usethisoveride/overhere:2017-1" + describe "without a timeout specified", -> beforeEach -> delete @validRequest.compile.options.timeout From c8d3c39beacb7e7aa52c752cc4aac2504b83db47 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 13 Jul 2018 11:46:37 +0100 Subject: [PATCH 404/754] quick hack to overright image name further down stack --- services/clsi/app/coffee/DockerRunner.coffee | 4 ++++ services/clsi/app/coffee/RequestParser.coffee | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/DockerRunner.coffee b/services/clsi/app/coffee/DockerRunner.coffee index 635243fd02..f48e904b25 100644 --- a/services/clsi/app/coffee/DockerRunner.coffee +++ b/services/clsi/app/coffee/DockerRunner.coffee @@ -38,6 +38,10 @@ module.exports = DockerRunner = if !image? image = Settings.clsi.docker.image + if Settings.texliveImageNameOveride? + tag = image.split(":")[1] + image = "#{Settings.texliveImageNameOveride}:#{tag}" + options = DockerRunner._getContainerOptions(command, image, volumes, timeout, environment) fingerprint = DockerRunner._fingerprintContainer(options) options.name = name = "project-#{project_id}-#{fingerprint}" diff --git a/services/clsi/app/coffee/RequestParser.coffee b/services/clsi/app/coffee/RequestParser.coffee index d375e9e331..78246169cb 100644 --- a/services/clsi/app/coffee/RequestParser.coffee +++ b/services/clsi/app/coffee/RequestParser.coffee @@ -69,7 +69,7 @@ module.exports = RequestParser = sanitizedRootResourcePath = RequestParser._sanitizePath(rootResourcePath) response.rootResourcePath = RequestParser._checkPath(sanitizedRootResourcePath) - if settings.texliveImageNameOveride? + if settings.texliveImageNameOveride? and compile.options?.imageName? tag = compile.options.imageName.split(":")[1] response.imageName = "#{settings.texliveImageNameOveride}:#{tag}" From 846c2b1e28fbef473095b80d2a2475ca32305dbd Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 13 Jul 2018 11:52:49 +0100 Subject: [PATCH 405/754] move texliveImageNameOveride further down request so it works for compile tests --- services/clsi/app/coffee/RequestParser.coffee | 4 ---- .../clsi/test/unit/coffee/DockerRunnerTests.coffee | 10 ++++++++++ .../clsi/test/unit/coffee/RequestParserTests.coffee | 8 -------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/services/clsi/app/coffee/RequestParser.coffee b/services/clsi/app/coffee/RequestParser.coffee index 78246169cb..cabbac38a4 100644 --- a/services/clsi/app/coffee/RequestParser.coffee +++ b/services/clsi/app/coffee/RequestParser.coffee @@ -69,10 +69,6 @@ module.exports = RequestParser = sanitizedRootResourcePath = RequestParser._sanitizePath(rootResourcePath) response.rootResourcePath = RequestParser._checkPath(sanitizedRootResourcePath) - if settings.texliveImageNameOveride? and compile.options?.imageName? - tag = compile.options.imageName.split(":")[1] - response.imageName = "#{settings.texliveImageNameOveride}:#{tag}" - for resource in response.resources if resource.path == originalRootResourcePath resource.path = sanitizedRootResourcePath diff --git a/services/clsi/test/unit/coffee/DockerRunnerTests.coffee b/services/clsi/test/unit/coffee/DockerRunnerTests.coffee index 5a697e2b8c..456f52b4b9 100644 --- a/services/clsi/test/unit/coffee/DockerRunnerTests.coffee +++ b/services/clsi/test/unit/coffee/DockerRunnerTests.coffee @@ -135,6 +135,16 @@ describe "DockerRunner", -> @DockerRunner._getContainerOptions .calledWith(@command_with_dir, @defaultImage, @volumes, @timeout) .should.equal true + + describe "with image override", -> + beforeEach -> + @Settings.texliveImageNameOveride = "overrideimage/here" + @DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, @output = "mock-output") + @DockerRunner.run @project_id, @command, @directory, @image, @timeout, @env, @callback + + it "should use the override and keep the tag", -> + image = @DockerRunner._getContainerOptions.args[0][1] + image.should.equal "overrideimage/here:2016.2" describe "_runAndWaitForContainer", -> beforeEach -> diff --git a/services/clsi/test/unit/coffee/RequestParserTests.coffee b/services/clsi/test/unit/coffee/RequestParserTests.coffee index 04405ac6a3..f63bc55e3d 100644 --- a/services/clsi/test/unit/coffee/RequestParserTests.coffee +++ b/services/clsi/test/unit/coffee/RequestParserTests.coffee @@ -66,14 +66,6 @@ describe "RequestParser", -> it "should set the imageName", -> @data.imageName.should.equal "basicImageName/here:2017-1" - describe "with texliveImageNameOveride set", -> - beforeEach -> - @settings.texliveImageNameOveride = "usethisoveride/overhere" - @RequestParser.parse @validRequest, (error, @data) => - - it "should override the image path", -> - @data.imageName.should.equal "usethisoveride/overhere:2017-1" - describe "without a timeout specified", -> beforeEach -> delete @validRequest.compile.options.timeout From a767bfe9642bbabd2d046f343a84404fe8c33195 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 16 Jul 2018 15:38:23 +0100 Subject: [PATCH 406/754] remove express header --- services/clsi/app.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 88bb74b3bc..7855e0924d 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -35,6 +35,7 @@ TIMEOUT = 6 * 60 * 1000 app.use (req, res, next) -> req.setTimeout TIMEOUT res.setTimeout TIMEOUT + res.removeHeader("X-Powered-By") next() app.param 'project_id', (req, res, next, project_id) -> From e46b6563c05461ee0d05d2f112545049fe00bf7d Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 16 Jul 2018 17:25:14 +0100 Subject: [PATCH 407/754] change override to leave image name so it works for wl_texlive --- services/clsi/app/coffee/DockerRunner.coffee | 4 ++-- services/clsi/test/unit/coffee/DockerRunnerTests.coffee | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/clsi/app/coffee/DockerRunner.coffee b/services/clsi/app/coffee/DockerRunner.coffee index f48e904b25..69a3df97fa 100644 --- a/services/clsi/app/coffee/DockerRunner.coffee +++ b/services/clsi/app/coffee/DockerRunner.coffee @@ -39,8 +39,8 @@ module.exports = DockerRunner = image = Settings.clsi.docker.image if Settings.texliveImageNameOveride? - tag = image.split(":")[1] - image = "#{Settings.texliveImageNameOveride}:#{tag}" + img = image.split("/") + image = "#{Settings.texliveImageNameOveride}/#{img[2]}" options = DockerRunner._getContainerOptions(command, image, volumes, timeout, environment) fingerprint = DockerRunner._fingerprintContainer(options) diff --git a/services/clsi/test/unit/coffee/DockerRunnerTests.coffee b/services/clsi/test/unit/coffee/DockerRunnerTests.coffee index 456f52b4b9..307ffde18a 100644 --- a/services/clsi/test/unit/coffee/DockerRunnerTests.coffee +++ b/services/clsi/test/unit/coffee/DockerRunnerTests.coffee @@ -138,13 +138,13 @@ describe "DockerRunner", -> describe "with image override", -> beforeEach -> - @Settings.texliveImageNameOveride = "overrideimage/here" + @Settings.texliveImageNameOveride = "overrideimage.com/something" @DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, @output = "mock-output") @DockerRunner.run @project_id, @command, @directory, @image, @timeout, @env, @callback it "should use the override and keep the tag", -> image = @DockerRunner._getContainerOptions.args[0][1] - image.should.equal "overrideimage/here:2016.2" + image.should.equal "overrideimage.com/something/image:2016.2" describe "_runAndWaitForContainer", -> beforeEach -> From d85980ad9dcdcee76b191a7eae6140157e2267b8 Mon Sep 17 00:00:00 2001 From: Brian Gough <bjg@network-theory.co.uk> Date: Tue, 17 Jul 2018 10:41:10 +0100 Subject: [PATCH 408/754] allow prune to fail to prevent build from terminating --- services/clsi/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index 0c289e15da..ce7367db96 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -54,7 +54,7 @@ pipeline { steps { sh 'mkdir -p compiles cache' // Not yet running, due to volumes/sibling containers - sh 'docker container prune -f' + sh 'docker container prune -f || true' sh 'docker pull $TEXLIVE_IMAGE' sh 'docker pull sharelatex/acceptance-test-runner:clsi-6.11.2' sh 'docker run --rm -e SIBLING_CONTAINER_USER=root -e SANDBOXED_COMPILES_HOST_DIR=$(pwd)/compiles -e SANDBOXED_COMPILES_SIBLING_CONTAINERS=true -e TEXLIVE_IMAGE=$TEXLIVE_IMAGE -v /var/run/docker.sock:/var/run/docker.sock -v $(pwd):/app sharelatex/acceptance-test-runner:clsi-6.11.2' From 28373d34f5dfbd7ae3aa2b5b32b52ddff940058a Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Tue, 17 Jul 2018 12:10:08 +0100 Subject: [PATCH 409/754] Bump build script to 1.1.8, drop csh-gcdm-test and csh-staging repos --- services/clsi/Jenkinsfile | 56 +++++++++++++++++++++++------ services/clsi/Makefile | 8 ++--- services/clsi/docker-compose.ci.yml | 2 +- services/clsi/docker-compose.yml | 2 +- services/clsi/package.json | 2 +- 5 files changed, 52 insertions(+), 18 deletions(-) diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index efe844839b..6db5e2982f 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -3,12 +3,33 @@ String cron_string = BRANCH_NAME == "master" ? "@daily" : "" pipeline { agent any + environment { + GIT_PROJECT = "clsi-sharelatex" + JENKINS_WORKFLOW = "clsi-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/sharelatex/${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' @@ -30,17 +51,11 @@ pipeline { stage('Package and publish build') { steps { - withCredentials([file(credentialsId: 'gcr.io_csh-gcdm-test', variable: 'DOCKER_REPO_KEY_PATH')]) { - sh 'docker login -u _json_key --password-stdin https://gcr.io/csh-gcdm-test < ${DOCKER_REPO_KEY_PATH}' + withCredentials([file(credentialsId: 'gcr.io_cr-test2', variable: 'DOCKER_REPO_KEY_PATH')]) { + sh 'docker login -u _json_key --password-stdin https://gcr.io/cr-test2 < ${DOCKER_REPO_KEY_PATH}' } - sh 'DOCKER_REPO=gcr.io/csh-gcdm-test make publish' - sh 'docker logout https://gcr.io/csh-gcdm-test' - - withCredentials([file(credentialsId: 'gcr.io_csh-staging', variable: 'DOCKER_REPO_KEY_PATH')]) { - sh 'docker login -u _json_key --password-stdin https://gcr.io/csh-staging < ${DOCKER_REPO_KEY_PATH}' - } - sh 'DOCKER_REPO=gcr.io/csh-staging make publish' - sh 'docker logout https://gcr.io/csh-staging' + sh 'DOCKER_REPO=gcr.io/cr-test2 make publish' + sh 'docker logout https://gcr.io/cr-test2' } } @@ -62,11 +77,32 @@ pipeline { 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" + } } } diff --git a/services/clsi/Makefile b/services/clsi/Makefile index b7c96a2b18..68e3c1cd27 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.7 +# Version: 1.1.8 BUILD_NUMBER ?= local BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) @@ -16,8 +16,7 @@ DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ clean: docker rmi ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) - docker rmi gcr.io/csh-gcdm-test/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) - docker rmi gcr.io/csh-staging/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) + docker rmi gcr.io/cr-test2/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) rm -f app.js rm -rf app/js rm -rf test/unit/js @@ -38,8 +37,7 @@ test_acceptance_pre_run: @[ ! -f test/acceptance/scripts/pre-run ] && echo "clsi has no pre acceptance tests task" || $(DOCKER_COMPOSE) run --rm test_acceptance test/acceptance/scripts/pre-run build: docker build --pull --tag ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ - --tag gcr.io/csh-gcdm-test/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ - --tag gcr.io/csh-staging/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ + --tag gcr.io/cr-test2/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ . publish: diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 9f3d54d1f4..e8850b9334 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.7 +# Version: 1.1.8 version: "2" diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 4c1cfc73f2..f47658d3f2 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.7 +# Version: 1.1.8 version: "2" diff --git a/services/clsi/package.json b/services/clsi/package.json index ab34cda854..bafda1950d 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -17,7 +17,7 @@ "compile:acceptance_tests": "[ ! -e test/acceptance/coffee ] && echo 'No acceptance tests to compile' || coffee -o test/acceptance/js -c test/acceptance/coffee", "compile:all": "npm run compile:app && npm run compile:unit_tests && npm run compile:acceptance_tests && npm run compile:smoke_tests", "nodemon": "nodemon --config nodemon.json", - "compile:smoke_tests": "[ ! -e test/smoke/coffee] && echo 'No smoke tests to compile' || coffee -o test/smoke/js -c test/smoke/coffee" + "compile:smoke_tests": "[ ! -e test/smoke/coffee ] && echo 'No smoke tests to compile' || coffee -o test/smoke/js -c test/smoke/coffee" }, "author": "James Allen <james@sharelatex.com>", "dependencies": { From d840246d29c840f22c9fd29cee2db2ea24ac1320 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 17 Jul 2018 12:50:33 +0100 Subject: [PATCH 410/754] add PRAGMA journal_mode=WAL; --- services/clsi/app/coffee/db.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/clsi/app/coffee/db.coffee b/services/clsi/app/coffee/db.coffee index f32cdf7aef..c21da6d5bf 100644 --- a/services/clsi/app/coffee/db.coffee +++ b/services/clsi/app/coffee/db.coffee @@ -14,6 +14,8 @@ sequelize = new Sequelize( options ) +sequelize.query("PRAGMA journal_mode=WAL;") + module.exports = UrlCache: sequelize.define("UrlCache", { url: Sequelize.STRING From e8c735e794fb37ac21cff935e234d606e90e92df Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 17 Jul 2018 12:53:07 +0100 Subject: [PATCH 411/754] only set wal for sqlite --- services/clsi/app/coffee/db.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/db.coffee b/services/clsi/app/coffee/db.coffee index c21da6d5bf..c8866e0c92 100644 --- a/services/clsi/app/coffee/db.coffee +++ b/services/clsi/app/coffee/db.coffee @@ -14,7 +14,8 @@ sequelize = new Sequelize( options ) -sequelize.query("PRAGMA journal_mode=WAL;") +if Settings.mysql.clsi.dialect == "sqlite" + sequelize.query("PRAGMA journal_mode=WAL;") module.exports = UrlCache: sequelize.define("UrlCache", { From 98108b87e7f47c84473bb00693fc16c4b07fcf72 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Wed, 18 Jul 2018 11:32:41 +0100 Subject: [PATCH 412/754] Push images to overleaf-ops --- services/clsi/Jenkinsfile | 8 ++++---- services/clsi/Makefile | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index 6db5e2982f..d82360d48d 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -51,11 +51,11 @@ pipeline { stage('Package and publish build') { steps { - withCredentials([file(credentialsId: 'gcr.io_cr-test2', variable: 'DOCKER_REPO_KEY_PATH')]) { - sh 'docker login -u _json_key --password-stdin https://gcr.io/cr-test2 < ${DOCKER_REPO_KEY_PATH}' + 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/cr-test2 make publish' - sh 'docker logout https://gcr.io/cr-test2' + sh 'DOCKER_REPO=gcr.io/overleaf-ops make publish' + sh 'docker logout https://gcr.io/overleaf-ops' } } diff --git a/services/clsi/Makefile b/services/clsi/Makefile index 68e3c1cd27..6daee1be7c 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -16,7 +16,7 @@ DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ clean: docker rmi ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) - docker rmi gcr.io/cr-test2/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) + docker rmi gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) rm -f app.js rm -rf app/js rm -rf test/unit/js @@ -37,7 +37,7 @@ test_acceptance_pre_run: @[ ! -f test/acceptance/scripts/pre-run ] && echo "clsi has no pre acceptance tests task" || $(DOCKER_COMPOSE) run --rm test_acceptance test/acceptance/scripts/pre-run build: docker build --pull --tag ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ - --tag gcr.io/cr-test2/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ + --tag gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ . publish: From 65679af0cb0c32db817caebbc3052fffc4fca126 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 26 Jul 2018 16:12:26 +0100 Subject: [PATCH 413/754] dd wal logging --- services/clsi/app/coffee/db.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/app/coffee/db.coffee b/services/clsi/app/coffee/db.coffee index c8866e0c92..c0bb1cac5e 100644 --- a/services/clsi/app/coffee/db.coffee +++ b/services/clsi/app/coffee/db.coffee @@ -15,6 +15,7 @@ sequelize = new Sequelize( ) if Settings.mysql.clsi.dialect == "sqlite" + logger.log "running PRAGMA journal_mode=WAL;" sequelize.query("PRAGMA journal_mode=WAL;") module.exports = From 3b068354997d76acd3ed714bc5d70776d32b3bed Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 30 Jul 2018 11:25:28 +0100 Subject: [PATCH 414/754] bump retried and package versions --- services/clsi/config/settings.defaults.coffee | 5 +++++ services/clsi/package.json | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index 1f3fe8be02..7a2e7cfe57 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -10,6 +10,11 @@ module.exports = password: null dialect: "sqlite" storage: process.env["SQLITE_PATH"] or Path.resolve(__dirname + "/../db.sqlite") + pool: + max: 1 + min: 0 + retry: + max: 10 path: compilesDir: Path.resolve(__dirname + "/../compiles") diff --git a/services/clsi/package.json b/services/clsi/package.json index bafda1950d..c34b93cb47 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -35,10 +35,10 @@ "mkdirp": "0.3.5", "mysql": "2.6.2", "request": "^2.21.0", - "sequelize": "^2.1.3", + "sequelize": "4.38.0", "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", - "sqlite3": "^3.1.13", + "sqlite3": "4.0.2", "underscore": "^1.8.2", "v8-profiler": "^5.2.4", "wrench": "~1.5.4" From 1ef8ea328cdeba1cdd44e0e223d4c679ddd99097 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 30 Jul 2018 14:04:33 +0100 Subject: [PATCH 415/754] remove password from clsi for sql sequalise fails when it is set to null --- services/clsi/.gitignore | 2 + services/clsi/app/coffee/db.coffee | 6 + services/clsi/config/settings.defaults.coffee | 3 +- services/clsi/package-lock.json | 2502 ++++++++++++++++- services/clsi/package.json | 4 +- 5 files changed, 2407 insertions(+), 110 deletions(-) diff --git a/services/clsi/.gitignore b/services/clsi/.gitignore index 21c1ecae1f..7fb78eefa0 100644 --- a/services/clsi/.gitignore +++ b/services/clsi/.gitignore @@ -12,5 +12,7 @@ app.js cache .vagrant db.sqlite +db.sqlite-wal +db.sqlite-shm config/* npm-debug.log diff --git a/services/clsi/app/coffee/db.coffee b/services/clsi/app/coffee/db.coffee index c0bb1cac5e..a24377e72c 100644 --- a/services/clsi/app/coffee/db.coffee +++ b/services/clsi/app/coffee/db.coffee @@ -42,4 +42,10 @@ module.exports = sync: () -> logger.log dbPath:Settings.mysql.clsi.storage, "syncing db schema" sequelize.sync() + .then(-> + logger.log "db sync complete" + ).catch((err)-> + console.log err, "error syncing" + ) + diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index 7a2e7cfe57..a2bf5930e8 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -7,12 +7,11 @@ module.exports = clsi: database: "clsi" username: "clsi" - password: null dialect: "sqlite" storage: process.env["SQLITE_PATH"] or Path.resolve(__dirname + "/../db.sqlite") pool: max: 1 - min: 0 + min: 1 retry: max: 10 diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index d00b345e5b..d58c1d73b9 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -4,11 +4,157 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@types/geojson": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.6.tgz", + "integrity": "sha512-Xqg/lIZMrUd0VRmSRbCAewtwGZiAk3mEUDvV4op1tGl+LvyPcb/MIOSxTl9z+9+J+R4/vpjiCAT4xeKzH9ji1w==" + }, + "@types/node": { + "version": "10.5.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.5.4.tgz", + "integrity": "sha512-8TqvB0ReZWwtcd3LXq3YSrBoLyXFgBX/sBZfGye9+YS8zH7/g+i6QRIuiDmwBoTzcQ/pk89nZYTYU4c5akKkzw==" + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg=" + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-0.2.0.tgz", + "integrity": "sha1-NZq0sV3NZLptdHNLcsNjYKmvLBk=", + "dev": true + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.6" + } + }, + "argparse": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", + "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", + "dev": true, + "requires": { + "underscore": "1.7.0", + "underscore.string": "2.4.0" + }, + "dependencies": { + "underscore": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", + "dev": true + }, + "underscore.string": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", + "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=", + "dev": true + } + } + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assertion-error": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", + "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", + "dev": true + }, + "async": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.9.tgz", + "integrity": "sha1-32MGD789Myhqdqr21Vophtn/hhk=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", + "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "optional": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "bignumber.js": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.7.tgz", + "integrity": "sha1-husHB89qURCQnSPm6nQ0wU9QDxw=" + }, + "bl": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", + "requires": { + "readable-stream": "2.3.6", + "safe-buffer": "5.1.2" + } + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "requires": { + "inherits": "2.0.3" + } + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" }, "body-parser": { "version": "1.18.2", @@ -156,21 +302,325 @@ } } }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "requires": { + "hoek": "2.16.3" + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", - "optional": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "requires": { + "buffer-alloc-unsafe": "1.1.0", + "buffer-fill": "1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" + }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" + }, + "bunyan": { + "version": "0.22.3", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-0.22.3.tgz", + "integrity": "sha1-ehncG0yMZF90AkGnQPIkUUfGfsI=", + "dev": true, + "requires": { + "mv": "2.1.1" + } + }, + "buster-core": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/buster-core/-/buster-core-0.6.4.tgz", + "integrity": "sha1-J79rrWdCROpyDzEdkAoMoct4YFA=", + "dev": true + }, + "buster-format": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/buster-format/-/buster-format-0.5.6.tgz", + "integrity": "sha1-K4bDIuz14bCubm55Bev884fSq5U=", + "dev": true, + "requires": { + "buster-core": "0.6.4" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chai": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-1.8.1.tgz", + "integrity": "sha1-zHeGbV5+vKK9dRRLHtw3Coh4X3I=", + "dev": true, + "requires": { + "assertion-error": "1.0.0", + "deep-eql": "0.1.3" + } + }, + "chalk": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.3.0.tgz", + "integrity": "sha1-HJhDdzfxGZ68wdTEj9Qbn5yOjyM=", + "dev": true, + "requires": { + "ansi-styles": "0.2.0", + "has-color": "0.1.7" + } + }, + "chownr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", + "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=" + }, + "cls-bluebird": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz", + "integrity": "sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4=", + "requires": { + "is-bluebird": "1.0.2", + "shimmer": "1.2.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "coffee-script": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", + "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=" + }, + "colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", + "dev": true + }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "requires": { + "delayed-stream": "1.0.0" + } + }, + "commander": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz", + "integrity": "sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg=" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "optional": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", + "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.0.6", + "typedarray": "0.0.6" + }, + "dependencies": { + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "requires": { + "boom": "2.10.1" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "dateformat": { + "version": "1.0.2-1.2.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", + "integrity": "sha1-sCIMAt6YYXQztyhRz0fePfLNvuk=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "requires": { + "ms": "2.0.0" + } + }, + "deep-eql": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "dev": true, + "requires": { + "type-detect": "0.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, + "diff": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", + "integrity": "sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ=" + }, + "docker-modem": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-1.0.6.tgz", + "integrity": "sha512-kDwWa5QaiVMB8Orbb7nXdGdwEZHKfEm7iPwglXe1KorImMpmGNlhC7A5LG0p8rrCcz1J4kJhq/o63lFjDdj8rQ==", + "requires": { + "debug": "3.1.0", + "JSONStream": "1.3.2", + "readable-stream": "1.0.34", + "split-ca": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "dockerode": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-2.5.5.tgz", + "integrity": "sha512-H3HX18xKmy51wqpPHvGDwPOotJMy9l/AWfiaVu4imrgBGr384rINEB2FwTwoYU++krkZjseVYyiVK8CnRz2tkw==", + "requires": { + "concat-stream": "1.5.2", + "docker-modem": "1.0.6", + "tar-fs": "1.12.0" + } + }, + "dottie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.0.tgz", + "integrity": "sha1-2hkZgci41xPKARXViYzzl8Lw3dA=" }, "dtrace-provider": { "version": "0.6.0", @@ -178,9 +628,51 @@ "integrity": "sha1-CweNVReTfYcxAUUtkUZzdVe3XlE=", "optional": true, "requires": { - "nan": "2.8.0" + "nan": "2.10.0" } }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "optional": true, + "requires": { + "jsbn": "0.1.1", + "safer-buffer": "2.1.2" + } + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "requires": { + "once": "1.4.0" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", + "dev": true + }, + "eventemitter2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", + "dev": true + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, "express": { "version": "4.16.2", "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", @@ -499,47 +991,609 @@ } } }, - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "optional": true, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "findup-sync": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", + "integrity": "sha1-fz56l7gjksZTvwZYm9hRkOk8NoM=", + "dev": true, "requires": { + "glob": "3.2.11", + "lodash": "2.4.2" + }, + "dependencies": { + "glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "minimatch": "0.3.0" + } + }, + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "dev": true, + "requires": { + "lru-cache": "2.7.3", + "sigmund": "1.0.1" + } + } + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.19" + } + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "fs-extra": { + "version": "0.16.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.16.5.tgz", + "integrity": "sha1-GtZh+myGyWCM0bSe/G/Og0k5p1A=", + "requires": { + "graceful-fs": "3.0.11", + "jsonfile": "2.4.0", + "rimraf": "2.6.2" + } + }, + "fs-minipass": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "integrity": "sha1-BsJ3IYRU7CiN93raVKA7hwKqy50=", + "requires": { + "minipass": "2.3.3" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.2" + }, + "dependencies": { + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + } + } + }, + "fstream-ignore": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", + "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" + } + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.3" + } + }, + "generic-pool": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.4.2.tgz", + "integrity": "sha512-H7cUpwCQSiJmAHM4c/aFu6fUfrhWXW1ncyh8ftxEPMu6AiYkHw9K8br720TGPZJbk5eOH2bynjZD1yPvdDAmag==" + }, + "getobject": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", + "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "requires": { + "fs.realpath": "1.0.0", "inflight": "1.0.6", "inherits": "2.0.3", "minimatch": "3.0.4", "once": "1.4.0", "path-is-absolute": "1.0.1" + } + }, + "graceful-fs": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", + "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", + "requires": { + "natives": "1.1.4" + } + }, + "growl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", + "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=" + }, + "grunt": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", + "integrity": "sha1-VpN81RlDJK3/bSB2MYMqnWuk5/A=", + "dev": true, + "requires": { + "async": "0.1.22", + "coffee-script": "1.3.3", + "colors": "0.6.2", + "dateformat": "1.0.2-1.2.3", + "eventemitter2": "0.4.14", + "exit": "0.1.2", + "findup-sync": "0.1.3", + "getobject": "0.1.0", + "glob": "3.1.21", + "grunt-legacy-log": "0.1.3", + "grunt-legacy-util": "0.2.0", + "hooker": "0.2.3", + "iconv-lite": "0.2.11", + "js-yaml": "2.0.5", + "lodash": "0.9.2", + "minimatch": "0.2.14", + "nopt": "1.0.10", + "rimraf": "2.2.8", + "underscore.string": "2.2.1", + "which": "1.0.9" }, "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "optional": true + "async": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", + "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=", + "dev": true }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "optional": true, + "coffee-script": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz", + "integrity": "sha1-FQ1rTLUiiUNp7+1qIQHCC8f0pPQ=", + "dev": true + }, + "glob": { + "version": "3.1.21", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", + "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", + "dev": true, "requires": { - "wrappy": "1.0.2" + "graceful-fs": "1.2.3", + "inherits": "1.0.2", + "minimatch": "0.2.14" } }, - "wrappy": { + "graceful-fs": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", + "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", + "dev": true + }, + "iconv-lite": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", + "integrity": "sha1-HOYKOleGSiktEyH/RgnKS7llrcg=", + "dev": true + }, + "inherits": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "optional": true + "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", + "dev": true + }, + "lodash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", + "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=", + "dev": true + }, + "minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "dev": true, + "requires": { + "lru-cache": "2.7.3", + "sigmund": "1.0.1" + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1.1.1" + } + }, + "rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true } } }, + "grunt-bunyan": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/grunt-bunyan/-/grunt-bunyan-0.5.0.tgz", + "integrity": "sha1-aCnXbgGZQ9owQTk2MaNuKsgpsWw=", + "dev": true, + "requires": { + "lodash": "2.4.2" + }, + "dependencies": { + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + } + } + }, + "grunt-contrib-clean": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-0.5.0.tgz", + "integrity": "sha1-9T397ghJsce0Dp67umn0jExgecU=", + "dev": true, + "requires": { + "rimraf": "2.2.8" + }, + "dependencies": { + "rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true + } + } + }, + "grunt-contrib-coffee": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-coffee/-/grunt-contrib-coffee-0.7.0.tgz", + "integrity": "sha1-ixIme3TnM4sfKcW4txj7n4mYLxM=", + "dev": true, + "requires": { + "coffee-script": "1.6.3" + }, + "dependencies": { + "coffee-script": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.3.tgz", + "integrity": "sha1-Y1XTLPGwTN/2tITl5xF4Ky8MOb4=", + "dev": true + } + } + }, + "grunt-execute": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/grunt-execute/-/grunt-execute-0.1.5.tgz", + "integrity": "sha1-yX64lDYS/vu3L749Mu+VIzxfouk=", + "dev": true + }, + "grunt-legacy-log": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", + "integrity": "sha1-7ClCboAwIa9ZAp+H0vnNczWgVTE=", + "dev": true, + "requires": { + "colors": "0.6.2", + "grunt-legacy-log-utils": "0.1.1", + "hooker": "0.2.3", + "lodash": "2.4.2", + "underscore.string": "2.3.3" + }, + "dependencies": { + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "underscore.string": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", + "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", + "dev": true + } + } + }, + "grunt-legacy-log-utils": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", + "integrity": "sha1-wHBrndkGThFvNvI/5OawSGcsD34=", + "dev": true, + "requires": { + "colors": "0.6.2", + "lodash": "2.4.2", + "underscore.string": "2.3.3" + }, + "dependencies": { + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "underscore.string": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", + "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", + "dev": true + } + } + }, + "grunt-legacy-util": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", + "integrity": "sha1-kzJIhNv343qf98Am3/RR2UqeVUs=", + "dev": true, + "requires": { + "async": "0.1.22", + "exit": "0.1.2", + "getobject": "0.1.0", + "hooker": "0.2.3", + "lodash": "0.9.2", + "underscore.string": "2.2.1", + "which": "1.0.9" + }, + "dependencies": { + "async": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", + "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=", + "dev": true + }, + "lodash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", + "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=", + "dev": true + } + } + }, + "grunt-mkdir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-mkdir/-/grunt-mkdir-1.0.0.tgz", + "integrity": "sha1-c+GiasJKCFljY/TdlUsNMkheWOk=" + }, + "grunt-mocha-test": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/grunt-mocha-test/-/grunt-mocha-test-0.8.2.tgz", + "integrity": "sha1-emGEuYhg0Phb3qrWvqob199bvus=", + "dev": true, + "requires": { + "mocha": "1.14.0" + }, + "dependencies": { + "glob": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", + "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", + "dev": true, + "requires": { + "graceful-fs": "2.0.3", + "inherits": "2.0.3", + "minimatch": "0.2.14" + } + }, + "graceful-fs": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", + "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=", + "dev": true + }, + "minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "dev": true, + "requires": { + "lru-cache": "2.7.3", + "sigmund": "1.0.1" + } + }, + "mocha": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.14.0.tgz", + "integrity": "sha1-cT223FAAGRqdA1gZXQkIeQ7LYVc=", + "dev": true, + "requires": { + "commander": "2.0.0", + "debug": "2.6.9", + "diff": "1.0.7", + "glob": "3.2.3", + "growl": "1.7.0", + "jade": "0.26.3", + "mkdirp": "0.3.5" + } + } + } + }, + "grunt-shell": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/grunt-shell/-/grunt-shell-0.6.4.tgz", + "integrity": "sha1-5KbRuSkSd2/ZOimcX2zGTpUlNlw=", + "dev": true, + "requires": { + "chalk": "0.3.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "requires": { + "ajv": "5.5.2", + "har-schema": "2.0.0" + } + }, + "has-color": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", + "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", + "dev": true + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "heapdump": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/heapdump/-/heapdump-0.3.9.tgz", + "integrity": "sha1-A8dOsN9dZ74Jgug0KbqcnSs7f3g=" + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + }, + "hooker": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", + "dev": true + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.14.2" + } + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": "2.1.2" + } + }, + "ignore-walk": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha1-qD5i59JyrA47VRqqgoMaGbafgvg=", + "requires": { + "minimatch": "3.0.4" + } + }, + "inflection": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", + "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "optional": true, "requires": { "once": "1.4.0", "wrappy": "1.0.2" @@ -549,7 +1603,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "optional": true, "requires": { "wrappy": "1.0.2" } @@ -561,12 +1614,169 @@ } } }, - "load-balancer-agent-sharelatex": { - "version": "git+https://github.com/sharelatex/load-balancer-agent-sharelatex.git#241a128c1e9fdec384186061b27704c8891d98ef", + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=" + }, + "is-bluebird": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", + "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { - "express": "4.16.2", - "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#405cf1350ca5ae5f7bb1e7091e28d5aa3aaaa72c", - "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#b4fb8404c5de571d029bf4c29e96a60b21206f94" + "number-is-nan": "1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "requires": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "dependencies": { + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=" + }, + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=" + } + } + }, + "js-yaml": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", + "integrity": "sha1-olrmUJmZ6X3yeMZxnaEb0Gh3Q6g=", + "dev": true, + "requires": { + "argparse": "0.1.16", + "esprima": "1.0.4" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "requires": { + "graceful-fs": "4.1.11" + }, + "dependencies": { + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "optional": true + } + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" + }, + "JSONStream": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz", + "integrity": "sha1-wQI3G27Dp887hHygDCC7D85Mbeo=", + "requires": { + "jsonparse": "1.3.1", + "through": "2.3.8" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "lockfile": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz", + "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==", + "requires": { + "signal-exit": "3.0.2" + } + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + }, + "logger-sharelatex": { + "version": "git+https://github.com/sharelatex/logger-sharelatex.git#9ee7b52eb2bbd8fcbb1e2c708587c1e93fd4c733", + "requires": { + "bunyan": "1.5.1", + "coffee-script": "1.4.0", + "raven": "1.2.1" }, "dependencies": { "bunyan": { @@ -576,72 +1786,186 @@ "requires": { "dtrace-provider": "0.6.0", "mv": "2.1.1", - "safe-json-stringify": "1.0.4" + "safe-json-stringify": "1.2.0" } }, "coffee-script": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.4.0.tgz", "integrity": "sha1-XjvIqsJsAajie/EHcixWVfWtfTY=" - }, - "cookie": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.0.tgz", - "integrity": "sha1-kOtGndzpBchm3mh+/EMTHYgB+dA=" - }, - "logger-sharelatex": { - "version": "git+https://github.com/sharelatex/logger-sharelatex.git#405cf1350ca5ae5f7bb1e7091e28d5aa3aaaa72c", - "requires": { - "bunyan": "1.5.1", - "coffee-script": "1.4.0", - "raven": "0.8.1" - } - }, - "lsmod": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-0.0.3.tgz", - "integrity": "sha1-F+E9ThrpF1DqVlNUjNiecUetAkQ=" - }, - "raven": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/raven/-/raven-0.8.1.tgz", - "integrity": "sha1-UVk7tlnHcnjc00gitlq+d7dRuvU=", - "requires": { - "cookie": "0.1.0", - "lsmod": "0.0.3", - "node-uuid": "1.4.8", - "stack-trace": "0.0.7" - } - }, - "settings-sharelatex": { - "version": "git+https://github.com/sharelatex/settings-sharelatex.git#b4fb8404c5de571d029bf4c29e96a60b21206f94", - "requires": { - "coffee-script": "1.6.0" - }, - "dependencies": { - "coffee-script": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", - "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=" - } - } - }, - "stack-trace": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.7.tgz", - "integrity": "sha1-xy4Il0T8Nln1CM3ONiGvVjTsD/8=" } } }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" + }, + "lsmod": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz", + "integrity": "sha1-mgD3bco26yP6BTUK/htYXUKZ5ks=" + }, + "lynx": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.0.11.tgz", + "integrity": "sha1-LPoU5EP9LZKlm3efQVZ84cxpZaM=", + "requires": { + "mersenne": "0.0.4", + "statsd-parser": "0.0.4" + } + }, + "mersenne": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz", + "integrity": "sha1-QB/ex+whzbngPNPTAhOY2iGycIU=" + }, + "metrics-sharelatex": { + "version": "git+https://github.com/sharelatex/metrics-sharelatex.git#e5356366b5b83997c8e1645b2e936af453381517", + "requires": { + "coffee-script": "1.6.0", + "lynx": "0.1.1", + "underscore": "1.6.0" + }, + "dependencies": { + "lynx": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.1.1.tgz", + "integrity": "sha1-Mxjc7xaQi4KG6Bisz9sxzXQkj50=", + "requires": { + "mersenne": "0.0.4", + "statsd-parser": "0.0.4" + } + }, + "underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" + } + } + }, + "mime-db": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", + "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" + }, + "mime-types": { + "version": "2.1.19", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", + "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", + "requires": { + "mime-db": "1.35.0" + } + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", - "optional": true, "requires": { "brace-expansion": "1.1.11" } }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "minipass": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.3.tgz", + "integrity": "sha1-p9zIt7gz9dNodZzOVE3MtV9Q8jM=", + "requires": { + "safe-buffer": "5.1.2", + "yallist": "3.0.2" + } + }, + "minizlib": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz", + "integrity": "sha1-EeE2WM5GvDpwomeqxYNZ0eDCnOs=", + "requires": { + "minipass": "2.3.3" + } + }, + "mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=" + }, + "mocha": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz", + "integrity": "sha512-0RVnjg1HJsXY2YFDoTNzcc1NKhYuXKRrBAG2gDygmJJA136Cs2QlRliZG1mA0ap7cuaT30mw16luAeln+4RiNA==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.11.0", + "debug": "3.1.0", + "diff": "3.3.1", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.3", + "he": "1.1.1", + "mkdirp": "0.5.1", + "supports-color": "4.4.0" + }, + "dependencies": { + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "diff": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", + "dev": true + }, + "growl": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + } + } + }, + "moment": { + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", + "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" + }, + "moment-timezone": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.21.tgz", + "integrity": "sha512-j96bAh4otsgj3lKydm3K7kdtA3iKf2m6MY2iSYCzCm5a1zmHo1g+aK3068dDEeocLZQIS9kU8bsdQHLqEvgW0A==", + "requires": { + "moment": "2.22.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, "mv": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", @@ -653,11 +1977,18 @@ "rimraf": "2.4.5" }, "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "optional": true + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "optional": true, + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } }, "mkdirp": { "version": "0.5.1", @@ -667,14 +1998,60 @@ "requires": { "minimist": "0.0.8" } + }, + "rimraf": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", + "optional": true, + "requires": { + "glob": "6.0.4" + } + } + } + }, + "mysql": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.6.2.tgz", + "integrity": "sha1-k3Gd0yT1fUHET7bX+GDPjmOFk7A=", + "requires": { + "bignumber.js": "2.0.7", + "readable-stream": "1.1.14", + "require-all": "1.0.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" } } }, "nan": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", - "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=", - "optional": true + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", + "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" + }, + "natives": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.4.tgz", + "integrity": "sha512-Q29yeg9aFKwhLVdkTAejM/HvYG0Y1Am1+HUkFQGn5k2j8GS+v60TVmZh6nujpEAj/qql+wGUrlryO8bF+b1jEg==" }, "ncp": { "version": "2.0.0", @@ -682,31 +2059,944 @@ "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", "optional": true }, - "node-uuid": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", - "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + "needle": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.1.tgz", + "integrity": "sha1-teMlvTqujCZ4kC+ilvcpRV0dOn0=", + "requires": { + "debug": "2.6.9", + "iconv-lite": "0.4.23", + "sax": "1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz", + "integrity": "sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==", + "requires": { + "detect-libc": "1.0.3", + "mkdirp": "0.5.1", + "needle": "2.2.1", + "nopt": "4.0.1", + "npm-packlist": "1.1.11", + "npmlog": "4.1.2", + "rc": "1.2.8", + "rimraf": "2.6.2", + "semver": "5.5.0", + "tar": "4.4.4" + }, + "dependencies": { + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "requires": { + "glob": "7.1.2" + } + } + } + }, + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "requires": { + "abbrev": "1.1.1", + "osenv": "0.1.5" + } + }, + "npm-bundled": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.3.tgz", + "integrity": "sha1-fnFwPZc68zcKlZG6/jpjrKC+Iwg=" + }, + "npm-packlist": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.11.tgz", + "integrity": "sha1-hOjGg8vnhn00sdNX2JPOKeKKAt4=", + "requires": { + "ignore-walk": "3.0.1", + "npm-bundled": "1.0.3" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=", + "requires": { + "are-we-there-yet": "1.1.5", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha1-hc36+uso6Gd/QW4odZK18/SepBA=", + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "optional": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, - "rimraf": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", - "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", - "optional": true, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "pump": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", + "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", "requires": { - "glob": "6.0.4" + "end-of-stream": "1.4.1", + "once": "1.4.0" } }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "raven": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/raven/-/raven-1.2.1.tgz", + "integrity": "sha1-lJwTTbAooZC3u/j3kKrlQbfAIL0=", + "requires": { + "cookie": "0.3.1", + "json-stringify-safe": "5.0.1", + "lsmod": "1.0.0", + "stack-trace": "0.0.9", + "uuid": "3.0.0" + }, + "dependencies": { + "uuid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz", + "integrity": "sha1-Zyj8BFnEUNeWqZwxg3VpvfZy1yg=" + } + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "0.6.0", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "request": { + "version": "2.87.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", + "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.7.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.0.3", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.19", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.6.0", + "uuid": "3.3.2" + } + }, + "require-all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/require-all/-/require-all-1.0.0.tgz", + "integrity": "sha1-hINwjnzkxt+tmItQgPl4KbktIic=" + }, + "require-like": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", + "integrity": "sha1-rW8wwTvs15cBDEaK+ndcDAprR/o=", + "dev": true + }, + "retry-as-promised": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-2.3.2.tgz", + "integrity": "sha1-zZdO5P2bX+A8vzGHHuSCIcB3N7c=", + "requires": { + "bluebird": "3.5.1", + "debug": "2.6.9" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "safe-json-stringify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz", - "integrity": "sha1-gaCY9Efku8P/MxKiQ1IbwGDvWRE=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", + "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sandboxed-module": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/sandboxed-module/-/sandboxed-module-0.3.0.tgz", + "integrity": "sha1-8fvvvYCaT2kHO9B8rm/H2y6vX2o=", + "dev": true, + "requires": { + "require-like": "0.1.2", + "stack-trace": "0.0.6" + }, + "dependencies": { + "stack-trace": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.6.tgz", + "integrity": "sha1-HnGb1qJin/CcGJ4Xqe+QKpT8XbA=", + "dev": true + } + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk=" + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha1-3Eu8emyp2Rbe5dQ1FvAJK1j3uKs=" + }, + "sequelize": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-4.38.0.tgz", + "integrity": "sha512-ZCcV2HuzU+03xunWgVeyXnPa/RYY5D2U/WUNpq+xF8VmDTLnSDsHl+pEwmiWrpZD7KdBqDczCeTgjToYyVzYQg==", + "requires": { + "bluebird": "3.5.1", + "cls-bluebird": "2.1.0", + "debug": "3.1.0", + "depd": "1.1.2", + "dottie": "2.0.0", + "generic-pool": "3.4.2", + "inflection": "1.12.0", + "lodash": "4.17.10", + "moment": "2.22.2", + "moment-timezone": "0.5.21", + "retry-as-promised": "2.3.2", + "semver": "5.5.0", + "terraformer-wkt-parser": "1.2.0", + "toposort-class": "1.0.1", + "uuid": "3.3.2", + "validator": "10.4.0", + "wkx": "0.4.5" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "settings-sharelatex": { + "version": "git+https://github.com/sharelatex/settings-sharelatex.git#cbc5e41c1dbe6789721a14b3fdae05bf22546559", + "requires": { + "coffee-script": "1.6.0" + } + }, + "shimmer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.0.tgz", + "integrity": "sha512-xTCx2vohXC2EWWDqY/zb4+5Mu28D+HYNSOuFzsyRDRvI/e1ICb69afwaUwfjr+25ZXldbOLyp+iDUZHq8UnTag==" + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "sinon": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.7.3.tgz", + "integrity": "sha1-emnWnNApRYbHQyVO7/G1g6UJl/I=", + "dev": true, + "requires": { + "buster-format": "0.5.6" + } + }, + "smoke-test-sharelatex": { + "version": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#bc3e93d18ccee219c0d99e8b02c984ccdd842e1c", + "requires": { + "mocha": "1.17.1" + }, + "dependencies": { + "glob": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", + "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", + "requires": { + "graceful-fs": "2.0.3", + "inherits": "2.0.3", + "minimatch": "0.2.14" + } + }, + "graceful-fs": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", + "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=" + }, + "minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "requires": { + "lru-cache": "2.7.3", + "sigmund": "1.0.1" + } + }, + "mocha": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.17.1.tgz", + "integrity": "sha1-f3Zx1oUm0HS3uuZgyQmfh+DqHMs=", + "requires": { + "commander": "2.0.0", + "debug": "2.6.9", + "diff": "1.0.7", + "glob": "3.2.3", + "growl": "1.7.0", + "jade": "0.26.3", + "mkdirp": "0.3.5" + } + } + } + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "requires": { + "hoek": "2.16.3" + } + }, + "split-ca": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", + "integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY=" + }, + "sqlite3": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.0.2.tgz", + "integrity": "sha512-51ferIRwYOhzUEtogqOa/y9supADlAht98bF/gbIi6WkzRJX6Yioldxbzj1MV4yV+LgdKD/kkHwFTeFXOG4htA==", + "requires": { + "nan": "2.10.0", + "node-pre-gyp": "0.10.3", + "request": "2.87.0" + }, + "dependencies": { + "nan": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", + "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" + }, + "request": { + "version": "2.87.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", + "integrity": "sha1-MvACNc0I1IK00NaNuTqCnA7VdW4=", + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.7.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.0.3", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.19", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.6.0", + "uuid": "3.3.2" + } + } + } + }, + "sshpk": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", + "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.2", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.2", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "safer-buffer": "2.1.2", + "tweetnacl": "0.14.5" + } + }, + "stack-trace": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", + "integrity": "sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU=" + }, + "statsd-parser": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/statsd-parser/-/statsd-parser-0.0.4.tgz", + "integrity": "sha1-y9JDlTzELv/VSLXSI4jtaJ7GOb0=" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "stringstream": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", + "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "supports-color": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + }, + "tar": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.4.tgz", + "integrity": "sha512-mq9ixIYfNF9SK0IS/h2HKMu8Q2iaCuhDDsZhdEag/FHv8fOaYld4vN7ouMgcSSt5WKZzPs8atclTcJm36OTh4w==", + "requires": { + "chownr": "1.0.1", + "fs-minipass": "1.2.5", + "minipass": "2.3.3", + "minizlib": "1.1.0", + "mkdirp": "0.5.1", + "safe-buffer": "5.1.2", + "yallist": "3.0.2" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + } + } + }, + "tar-fs": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.12.0.tgz", + "integrity": "sha1-pqgFU9ilTHPeHQrg553ncDVgXh0=", + "requires": { + "mkdirp": "0.5.1", + "pump": "1.0.3", + "tar-stream": "1.6.1" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + } + } + }, + "tar-pack": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.1.tgz", + "integrity": "sha512-PPRybI9+jM5tjtCbN2cxmmRU7YmqT3Zv/UDy48tAh2XRkLa9bAORtSWLkVc13+GJF+cdTh1yEnHEk3cpTaL5Kg==", + "requires": { + "debug": "2.6.9", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.4.0", + "readable-stream": "2.3.6", + "rimraf": "2.6.2", + "tar": "2.2.1", + "uid-number": "0.0.6" + }, + "dependencies": { + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + } + } + }, + "tar-stream": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.1.tgz", + "integrity": "sha512-IFLM5wp3QrJODQFPm6/to3LJZrONdBY/otxcvDIQzu217zKye6yVR3hhi9lAjrC2Z+m/j5oDxMPb1qcd8cIvpA==", + "requires": { + "bl": "1.2.2", + "buffer-alloc": "1.2.0", + "end-of-stream": "1.4.1", + "fs-constants": "1.0.0", + "readable-stream": "2.3.6", + "to-buffer": "1.1.1", + "xtend": "4.0.1" + } + }, + "terraformer": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/terraformer/-/terraformer-1.0.9.tgz", + "integrity": "sha512-YlmQ1fsMWTkKGDGibCRWgmLzrpDRUr63Q025LJ/taYQ6j1Yb8q9McKF7NBi6ACAyUXO6F/bl9w6v4MY307y5Ag==", + "requires": { + "@types/geojson": "1.0.6" + } + }, + "terraformer-wkt-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/terraformer-wkt-parser/-/terraformer-wkt-parser-1.2.0.tgz", + "integrity": "sha512-QU3iA54St5lF8Za1jg1oj4NYc8sn5tCZ08aNSWDeGzrsaV48eZk1iAVWasxhNspYBoCqdHuoot1pUTUrE1AJ4w==", + "requires": { + "@types/geojson": "1.0.6", + "terraformer": "1.0.9" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "timekeeper": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/timekeeper/-/timekeeper-0.0.4.tgz", + "integrity": "sha1-kNt58X2Ni1NiFUOJSSuXJ2LP0nY=", + "dev": true + }, + "to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" + }, + "toposort-class": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", + "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" + }, + "tough-cookie": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha1-7GDO44rGdQY//JelwYlwV47oNlU=", + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, + "type-detect": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "dev": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "uid-number": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", + "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" + }, + "underscore": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", + "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" + }, + "underscore.string": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", + "integrity": "sha1-18D6KvXVoaZ/QlPa7pgTLnM/Dxk=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "v8-profiler": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/v8-profiler/-/v8-profiler-5.7.0.tgz", + "integrity": "sha1-6DgcvrtbX9DKjSsJ9qAYGhWNs00=", + "requires": { + "nan": "2.10.0", + "node-pre-gyp": "0.6.39" + }, + "dependencies": { + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" + }, + "form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.19" + } + }, + "har-schema": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" + }, + "har-validator": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.1", + "sshpk": "1.14.2" + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "node-pre-gyp": { + "version": "0.6.39", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz", + "integrity": "sha512-OsJV74qxnvz/AMGgcfZoDaeDXKD3oY3QVIbBmwszTFkRisTSXbMQyn4UWzUMOtA5SVhrBZOTp0wcoSBgfMfMmQ==", + "requires": { + "detect-libc": "1.0.3", + "hawk": "3.1.3", + "mkdirp": "0.5.1", + "nopt": "4.0.1", + "npmlog": "4.1.2", + "rc": "1.2.8", + "request": "2.81.0", + "rimraf": "2.6.2", + "semver": "5.5.0", + "tar": "2.2.1", + "tar-pack": "3.4.1" + } + }, + "performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" + }, + "qs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" + }, + "request": { + "version": "2.81.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.7.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.19", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.1.2", + "stringstream": "0.0.6", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.6.0", + "uuid": "3.3.2" + } + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + } + } + }, + "validator": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.4.0.tgz", + "integrity": "sha512-Q/wBy3LB1uOyssgNlXSRmaf22NxjvDNZM2MtIQ4jaEOAB61xsh1TQxsq1CgzUMBV1lDrVMogIh8GjG1DYW0zLg==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + } + }, + "which": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", + "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "1.0.2" + } + }, + "wkx": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.5.tgz", + "integrity": "sha512-01dloEcJZAJabLO5XdcRgqdKpmnxS0zIT02LhkdWOZX2Zs2tPM6hlZ4XG9tWaWur1Qd1OO4kJxUbe2+5BofvnA==", + "requires": { + "@types/node": "10.5.4" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "wrench": { + "version": "1.5.9", + "resolved": "https://registry.npmjs.org/wrench/-/wrench-1.5.9.tgz", + "integrity": "sha1-QRaRxjqbJTGxcAJnJ5veyiOyFCo=" + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, + "yallist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", + "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" } } } diff --git a/services/clsi/package.json b/services/clsi/package.json index c34b93cb47..f2183ef9bc 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -35,10 +35,10 @@ "mkdirp": "0.3.5", "mysql": "2.6.2", "request": "^2.21.0", - "sequelize": "4.38.0", + "sequelize": "^4.38.0", "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", - "sqlite3": "4.0.2", + "sqlite3": "^4.0.2", "underscore": "^1.8.2", "v8-profiler": "^5.2.4", "wrench": "~1.5.4" From dc225f6aa3e83d733e4e23061220b010c9c09bb4 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 30 Jul 2018 15:11:41 +0100 Subject: [PATCH 416/754] remove some console.logs --- services/clsi/app.coffee | 1 - services/clsi/app/coffee/ResourceWriter.coffee | 2 -- services/clsi/app/coffee/UrlFetcher.coffee | 1 - 3 files changed, 4 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 7855e0924d..8b5c779d27 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -133,7 +133,6 @@ resCacher = if Settings.smokeTest do runSmokeTest = -> logger.log("running smoke tests") - console.log(__dirname, __filename) smokeTest.run(require.resolve(__dirname + "/test/smoke/js/SmokeTests.js"))({}, resCacher) setTimeout(runSmokeTest, 30 * 1000) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index 90ada043b8..0c9f7180f2 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -121,11 +121,9 @@ module.exports = ResourceWriter = callback() #try and continue compiling even if http resource can not be downloaded at this time else process = require("process") - console.log "writing file out", path, process.getuid() fs.writeFile path, resource.content, callback try result = fs.lstatSync(path) - console.log "path stats", result catch e checkPath: (basePath, resourcePath, callback) -> diff --git a/services/clsi/app/coffee/UrlFetcher.coffee b/services/clsi/app/coffee/UrlFetcher.coffee index 58645f0034..da10859f1e 100644 --- a/services/clsi/app/coffee/UrlFetcher.coffee +++ b/services/clsi/app/coffee/UrlFetcher.coffee @@ -36,7 +36,6 @@ module.exports = UrlFetcher = logger.log url:url, filePath: filePath, "finished downloading file into cache" urlStream.on "response", (res) -> - console.log if res.statusCode >= 200 and res.statusCode < 300 fileStream = fs.createWriteStream(filePath) From f2dfb718c6bd7a91aecd4cc2c395d7f41d2de2a0 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 30 Jul 2018 15:16:06 +0100 Subject: [PATCH 417/754] add sync= off and read_uncommited=true to improve perf --- services/clsi/app/coffee/db.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/clsi/app/coffee/db.coffee b/services/clsi/app/coffee/db.coffee index a24377e72c..764edebe8e 100644 --- a/services/clsi/app/coffee/db.coffee +++ b/services/clsi/app/coffee/db.coffee @@ -17,6 +17,8 @@ sequelize = new Sequelize( if Settings.mysql.clsi.dialect == "sqlite" logger.log "running PRAGMA journal_mode=WAL;" sequelize.query("PRAGMA journal_mode=WAL;") + sequelize.query("PRAGMA synchronous=OFF;") + sequelize.query("PRAGMA read_uncommitted = true;") module.exports = UrlCache: sequelize.define("UrlCache", { From daf40fc75723c2d9583b46af308d5451d08eade5 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 30 Jul 2018 15:18:25 +0100 Subject: [PATCH 418/754] added some debugging --- services/clsi/app/coffee/UrlCache.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/clsi/app/coffee/UrlCache.coffee b/services/clsi/app/coffee/UrlCache.coffee index b72b78ca0f..06e601282e 100644 --- a/services/clsi/app/coffee/UrlCache.coffee +++ b/services/clsi/app/coffee/UrlCache.coffee @@ -94,11 +94,13 @@ module.exports = UrlCache = return callback() _findUrlDetails: (project_id, url, callback = (error, urlDetails) ->) -> + console.log("_findUrlDetails") db.UrlCache.find(where: { url: url, project_id: project_id }) .then((urlDetails) -> callback null, urlDetails) .error callback _updateOrCreateUrlDetails: (project_id, url, lastModified, callback = (error) ->) -> + console.log("_updateOrCreateUrlDetails") db.UrlCache.findOrCreate(where: {url: url, project_id: project_id}) .spread( (urlDetails, created) -> @@ -109,11 +111,13 @@ module.exports = UrlCache = .error callback _clearUrlDetails: (project_id, url, callback = (error) ->) -> + console.log("_clearUrlDetails") db.UrlCache.destroy(where: {url: url, project_id: project_id}) .then(() -> callback null) .error callback _findAllUrlsInProject: (project_id, callback = (error, urls) ->) -> + console.log("_findAllUrlsInProject") db.UrlCache.findAll(where: { project_id: project_id }) .then( (urlEntries) -> From 1080c2c428d5e427a14434c6b5b976d4e06e4a58 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 30 Jul 2018 16:22:04 +0100 Subject: [PATCH 419/754] added a queue with 1 concurency to db queries --- .../coffee/ProjectPersistenceManager.coffee | 3 + services/clsi/app/coffee/UrlCache.coffee | 68 ++++++++++++------- 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.coffee b/services/clsi/app/coffee/ProjectPersistenceManager.coffee index 403043fac9..17bead29b4 100644 --- a/services/clsi/app/coffee/ProjectPersistenceManager.coffee +++ b/services/clsi/app/coffee/ProjectPersistenceManager.coffee @@ -11,6 +11,7 @@ module.exports = ProjectPersistenceManager = EXPIRY_TIMEOUT: Settings.project_cache_length_ms || oneDay * 2.5 markProjectAsJustAccessed: (project_id, callback = (error) ->) -> + console.log("markProjectAsJustAccessed") db.Project.findOrCreate(where: {project_id: project_id}) .spread( (project, created) -> @@ -53,11 +54,13 @@ module.exports = ProjectPersistenceManager = callback() _clearProjectFromDatabase: (project_id, callback = (error) ->) -> + console.log("_clearProjectFromDatabase") db.Project.destroy(where: {project_id: project_id}) .then(() -> callback()) .error callback _findExpiredProjectIds: (callback = (error, project_ids) ->) -> + console.log("_findExpiredProjectIds") db.Project.findAll(where: ["lastAccessed < ?", new Date(Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT)]) .then((projects) -> callback null, projects.map((project) -> project.project_id) diff --git a/services/clsi/app/coffee/UrlCache.coffee b/services/clsi/app/coffee/UrlCache.coffee index 06e601282e..e5fc81c796 100644 --- a/services/clsi/app/coffee/UrlCache.coffee +++ b/services/clsi/app/coffee/UrlCache.coffee @@ -6,6 +6,15 @@ fs = require("fs") logger = require "logger-sharelatex" async = require "async" +queue = async.queue((task, cb)-> + console.log("running task") + task(cb) +, 1) + +console.log("hi there queue") +queue.drain = ()-> + console.log('HI all items have been processed') + module.exports = UrlCache = downloadUrlToFile: (project_id, url, destPath, lastModified, callback = (error) ->) -> UrlCache._ensureUrlIsInCache project_id, url, lastModified, (error, pathToCachedUrl) => @@ -51,8 +60,9 @@ module.exports = UrlCache = _doesUrlNeedDownloading: (project_id, url, lastModified, callback = (error, needsDownloading) ->) -> if !lastModified? return callback null, true - + console.log "about to get _findUrlDetails" UrlCache._findUrlDetails project_id, url, (error, urlDetails) -> + console.log error, urlDetails, "_findUrlDetails result" return callback(error) if error? if !urlDetails? or !urlDetails.lastModified? or urlDetails.lastModified.getTime() < lastModified.getTime() return callback null, true @@ -94,36 +104,44 @@ module.exports = UrlCache = return callback() _findUrlDetails: (project_id, url, callback = (error, urlDetails) ->) -> - console.log("_findUrlDetails") - db.UrlCache.find(where: { url: url, project_id: project_id }) - .then((urlDetails) -> callback null, urlDetails) - .error callback + job = (cb)-> + db.UrlCache.find(where: { url: url, project_id: project_id }) + .then((urlDetails) -> cb null, urlDetails) + .error cb + queue.push job, callback _updateOrCreateUrlDetails: (project_id, url, lastModified, callback = (error) ->) -> - console.log("_updateOrCreateUrlDetails") - db.UrlCache.findOrCreate(where: {url: url, project_id: project_id}) - .spread( - (urlDetails, created) -> - urlDetails.updateAttributes(lastModified: lastModified) - .then(() -> callback()) - .error(callback) - ) - .error callback + job = (cb)-> + console.log("_updateOrCreateUrlDetails") + db.UrlCache.findOrCreate(where: {url: url, project_id: project_id}) + .spread( + (urlDetails, created) -> + urlDetails.updateAttributes(lastModified: lastModified) + .then(() -> cb()) + .error(cb) + ) + .error cb + queue.push(job, callback) _clearUrlDetails: (project_id, url, callback = (error) ->) -> - console.log("_clearUrlDetails") - db.UrlCache.destroy(where: {url: url, project_id: project_id}) - .then(() -> callback null) - .error callback + job = (cb)-> + console.log("_clearUrlDetails") + db.UrlCache.destroy(where: {url: url, project_id: project_id}) + .then(() -> cb null) + .error cb + queue.push(job, callback) + _findAllUrlsInProject: (project_id, callback = (error, urls) ->) -> - console.log("_findAllUrlsInProject") - db.UrlCache.findAll(where: { project_id: project_id }) - .then( - (urlEntries) -> - callback null, urlEntries.map((entry) -> entry.url) - ) - .error callback + job = (cb)-> + console.log("_findAllUrlsInProject") + db.UrlCache.findAll(where: { project_id: project_id }) + .then( + (urlEntries) -> + cb null, urlEntries.map((entry) -> entry.url) + ) + .error cb + queue.push(job, callback) From 759988401b7efbae795efedc0e54b746c06efe66 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 30 Jul 2018 16:46:47 +0100 Subject: [PATCH 420/754] add db queue file for global db query queues --- services/clsi/app/coffee/DbQueue.coffee | 13 ++++++ .../coffee/ProjectPersistenceManager.coffee | 46 +++++++++++-------- services/clsi/app/coffee/UrlCache.coffee | 18 ++------ 3 files changed, 46 insertions(+), 31 deletions(-) create mode 100644 services/clsi/app/coffee/DbQueue.coffee diff --git a/services/clsi/app/coffee/DbQueue.coffee b/services/clsi/app/coffee/DbQueue.coffee new file mode 100644 index 0000000000..6d12924c55 --- /dev/null +++ b/services/clsi/app/coffee/DbQueue.coffee @@ -0,0 +1,13 @@ +async = require "async" + +queue = async.queue((task, cb)-> + console.log("running task") + task(cb) + , 1) + +queue.drain = ()-> + console.log('HI all items have been processed') + +module.exports = + queue: queue + diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.coffee b/services/clsi/app/coffee/ProjectPersistenceManager.coffee index 17bead29b4..427b44b991 100644 --- a/services/clsi/app/coffee/ProjectPersistenceManager.coffee +++ b/services/clsi/app/coffee/ProjectPersistenceManager.coffee @@ -1,6 +1,7 @@ UrlCache = require "./UrlCache" CompileManager = require "./CompileManager" db = require "./db" +dbQueue = require "./DbQueue" async = require "async" logger = require "logger-sharelatex" oneDay = 24 * 60 * 60 * 1000 @@ -11,15 +12,18 @@ module.exports = ProjectPersistenceManager = EXPIRY_TIMEOUT: Settings.project_cache_length_ms || oneDay * 2.5 markProjectAsJustAccessed: (project_id, callback = (error) ->) -> - console.log("markProjectAsJustAccessed") - db.Project.findOrCreate(where: {project_id: project_id}) - .spread( - (project, created) -> - project.updateAttributes(lastAccessed: new Date()) - .then(() -> callback()) - .error callback - ) - .error callback + job = (cb)-> + console.log("markProjectAsJustAccessed") + db.Project.findOrCreate(where: {project_id: project_id}) + .spread( + (project, created) -> + project.updateAttributes(lastAccessed: new Date()) + .then(() -> cb()) + .error cb + ) + .error cb + dbQueue.queue.push(job, callback) + clearExpiredProjects: (callback = (error) ->) -> ProjectPersistenceManager._findExpiredProjectIds (error, project_ids) -> @@ -54,16 +58,22 @@ module.exports = ProjectPersistenceManager = callback() _clearProjectFromDatabase: (project_id, callback = (error) ->) -> - console.log("_clearProjectFromDatabase") - db.Project.destroy(where: {project_id: project_id}) - .then(() -> callback()) - .error callback + job = (cb)-> + console.log("_clearProjectFromDatabase") + db.Project.destroy(where: {project_id: project_id}) + .then(() -> callback()) + .error callback + dbQueue.queue.push(job, callback) + _findExpiredProjectIds: (callback = (error, project_ids) ->) -> - console.log("_findExpiredProjectIds") - db.Project.findAll(where: ["lastAccessed < ?", new Date(Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT)]) - .then((projects) -> - callback null, projects.map((project) -> project.project_id) - ).error callback + job = (cb)-> + console.log("_findExpiredProjectIds") + db.Project.findAll(where: ["lastAccessed < ?", new Date(Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT)]) + .then((projects) -> + callback null, projects.map((project) -> project.project_id) + ).error callback + dbQueue.queue.push(job, callback) + logger.log {EXPIRY_TIMEOUT: ProjectPersistenceManager.EXPIRY_TIMEOUT}, "project assets kept timeout" diff --git a/services/clsi/app/coffee/UrlCache.coffee b/services/clsi/app/coffee/UrlCache.coffee index e5fc81c796..73d9841131 100644 --- a/services/clsi/app/coffee/UrlCache.coffee +++ b/services/clsi/app/coffee/UrlCache.coffee @@ -1,4 +1,5 @@ db = require("./db") +dbQueue = require "./DbQueue" UrlFetcher = require("./UrlFetcher") Settings = require("settings-sharelatex") crypto = require("crypto") @@ -6,15 +7,6 @@ fs = require("fs") logger = require "logger-sharelatex" async = require "async" -queue = async.queue((task, cb)-> - console.log("running task") - task(cb) -, 1) - -console.log("hi there queue") -queue.drain = ()-> - console.log('HI all items have been processed') - module.exports = UrlCache = downloadUrlToFile: (project_id, url, destPath, lastModified, callback = (error) ->) -> UrlCache._ensureUrlIsInCache project_id, url, lastModified, (error, pathToCachedUrl) => @@ -108,7 +100,7 @@ module.exports = UrlCache = db.UrlCache.find(where: { url: url, project_id: project_id }) .then((urlDetails) -> cb null, urlDetails) .error cb - queue.push job, callback + dbQueue.queue.push job, callback _updateOrCreateUrlDetails: (project_id, url, lastModified, callback = (error) ->) -> job = (cb)-> @@ -121,7 +113,7 @@ module.exports = UrlCache = .error(cb) ) .error cb - queue.push(job, callback) + dbQueue.queue.push(job, callback) _clearUrlDetails: (project_id, url, callback = (error) ->) -> job = (cb)-> @@ -129,7 +121,7 @@ module.exports = UrlCache = db.UrlCache.destroy(where: {url: url, project_id: project_id}) .then(() -> cb null) .error cb - queue.push(job, callback) + dbQueue.queue.push(job, callback) _findAllUrlsInProject: (project_id, callback = (error, urls) ->) -> @@ -141,7 +133,7 @@ module.exports = UrlCache = cb null, urlEntries.map((entry) -> entry.url) ) .error cb - queue.push(job, callback) + dbQueue.queue.push(job, callback) From f9754c4b9507b4fa921dc6b4d0739f3df0217970 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 30 Jul 2018 17:01:59 +0100 Subject: [PATCH 421/754] =?UTF-8?q?fix=20missing=20cb=E2=80=99s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- services/clsi/app/coffee/ProjectPersistenceManager.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.coffee b/services/clsi/app/coffee/ProjectPersistenceManager.coffee index 427b44b991..f41c14cd1d 100644 --- a/services/clsi/app/coffee/ProjectPersistenceManager.coffee +++ b/services/clsi/app/coffee/ProjectPersistenceManager.coffee @@ -61,8 +61,8 @@ module.exports = ProjectPersistenceManager = job = (cb)-> console.log("_clearProjectFromDatabase") db.Project.destroy(where: {project_id: project_id}) - .then(() -> callback()) - .error callback + .then(() -> cb()) + .error cb dbQueue.queue.push(job, callback) @@ -71,8 +71,8 @@ module.exports = ProjectPersistenceManager = console.log("_findExpiredProjectIds") db.Project.findAll(where: ["lastAccessed < ?", new Date(Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT)]) .then((projects) -> - callback null, projects.map((project) -> project.project_id) - ).error callback + cb null, projects.map((project) -> project.project_id) + ).error cb dbQueue.queue.push(job, callback) From cd5bcdd7cafe4b35ca9e83f06eaa6424d2f68100 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 30 Jul 2018 17:37:30 +0100 Subject: [PATCH 422/754] fix expired projects command --- services/clsi/Makefile | 2 +- services/clsi/app/coffee/ProjectPersistenceManager.coffee | 8 ++++++-- services/clsi/app/coffee/db.coffee | 2 ++ services/clsi/docker-compose.ci.yml | 2 +- services/clsi/docker-compose.yml | 2 +- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/services/clsi/Makefile b/services/clsi/Makefile index 6daee1be7c..b1ce2934af 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.8 +# Version: 1.1.9 BUILD_NUMBER ?= local BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.coffee b/services/clsi/app/coffee/ProjectPersistenceManager.coffee index f41c14cd1d..2c73c614aa 100644 --- a/services/clsi/app/coffee/ProjectPersistenceManager.coffee +++ b/services/clsi/app/coffee/ProjectPersistenceManager.coffee @@ -68,11 +68,15 @@ module.exports = ProjectPersistenceManager = _findExpiredProjectIds: (callback = (error, project_ids) ->) -> job = (cb)-> - console.log("_findExpiredProjectIds") - db.Project.findAll(where: ["lastAccessed < ?", new Date(Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT)]) + keepProjectsFrom = new Date(Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT) + console.log("_findExpiredProjectIds", keepProjectsFrom) + q = {} + q[db.op.gt] = keepProjectsFrom + db.Project.findAll(where:{lastAccessed:q}) .then((projects) -> cb null, projects.map((project) -> project.project_id) ).error cb + dbQueue.queue.push(job, callback) diff --git a/services/clsi/app/coffee/db.coffee b/services/clsi/app/coffee/db.coffee index 764edebe8e..de48dfdf55 100644 --- a/services/clsi/app/coffee/db.coffee +++ b/services/clsi/app/coffee/db.coffee @@ -41,6 +41,8 @@ module.exports = ] }) + op: Sequelize.Op + sync: () -> logger.log dbPath:Settings.mysql.clsi.storage, "syncing db schema" sequelize.sync() diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index e8850b9334..f6c8a27fd0 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.8 +# Version: 1.1.9 version: "2" diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index f47658d3f2..371e6e768d 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.8 +# Version: 1.1.9 version: "2" From ac4d07352f388673eadc18fc374e6a9447b3442b Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 31 Jul 2018 14:38:24 +0100 Subject: [PATCH 423/754] make Settings.parallelSqlQueryLimit a config setting --- services/clsi/app/coffee/DbQueue.coffee | 5 +++-- services/clsi/config/settings.defaults.coffee | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/DbQueue.coffee b/services/clsi/app/coffee/DbQueue.coffee index 6d12924c55..79673e4d33 100644 --- a/services/clsi/app/coffee/DbQueue.coffee +++ b/services/clsi/app/coffee/DbQueue.coffee @@ -1,13 +1,14 @@ async = require "async" +Settings = require "settings-sharelatex" queue = async.queue((task, cb)-> console.log("running task") task(cb) - , 1) + , Settings.parallelSqlQueryLimit) queue.drain = ()-> console.log('HI all items have been processed') - + module.exports = queue: queue diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index a2bf5930e8..cfa6bda368 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -36,6 +36,7 @@ module.exports = smokeTest: process.env["SMOKE_TEST"] or false project_cache_length_ms: 1000 * 60 * 60 * 24 parallelFileDownloads:1 + parallelSqlQueryLimit:1 filestoreDomainOveride: process.env["FILESTORE_DOMAIN_OVERRIDE"] texliveImageNameOveride: process.env["TEX_LIVE_IMAGE_NAME_OVERRIDE"] From 44c0922a5bc7091fa812c58a9212e505229da087 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Wed, 1 Aug 2018 13:59:09 +0100 Subject: [PATCH 424/754] reduce logging --- services/clsi/app/coffee/DbQueue.coffee | 1 - services/clsi/app/coffee/DockerRunner.coffee | 12 +++++++----- .../clsi/app/coffee/ProjectPersistenceManager.coffee | 3 --- services/clsi/app/coffee/UrlCache.coffee | 5 ----- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/services/clsi/app/coffee/DbQueue.coffee b/services/clsi/app/coffee/DbQueue.coffee index 79673e4d33..0e025906b7 100644 --- a/services/clsi/app/coffee/DbQueue.coffee +++ b/services/clsi/app/coffee/DbQueue.coffee @@ -2,7 +2,6 @@ async = require "async" Settings = require "settings-sharelatex" queue = async.queue((task, cb)-> - console.log("running task") task(cb) , Settings.parallelSqlQueryLimit) diff --git a/services/clsi/app/coffee/DockerRunner.coffee b/services/clsi/app/coffee/DockerRunner.coffee index 69a3df97fa..589a2e03b1 100644 --- a/services/clsi/app/coffee/DockerRunner.coffee +++ b/services/clsi/app/coffee/DockerRunner.coffee @@ -46,7 +46,9 @@ module.exports = DockerRunner = fingerprint = DockerRunner._fingerprintContainer(options) options.name = name = "project-#{project_id}-#{fingerprint}" - logger.log project_id: project_id, options: options, "running docker container" + logOptions = _.clone(options) + logOptions.HostConfig.SecurityOpt = "secomp used, removed in logging" + logger.log project_id: project_id, options:logOptions, "running docker container" DockerRunner._runAndWaitForContainer options, volumes, timeout, (error, output) -> if error?.message?.match("HTTP code is 500") logger.log err: error, project_id: project_id, "error running container so destroying and retrying" @@ -144,14 +146,14 @@ module.exports = DockerRunner = "Ulimits": [{'Name': 'cpu', 'Soft': timeoutInSeconds+5, 'Hard': timeoutInSeconds+10}] "CapDrop": "ALL" "SecurityOpt": ["no-new-privileges"] - - if Settings.clsi.docker.seccomp_profile? - options.HostConfig.SecurityOpt.push "seccomp=#{Settings.clsi.docker.seccomp_profile}" + if Settings.path?.synctexBinHostPath? options["HostConfig"]["Binds"].push("#{Settings.path.synctexBinHostPath}:/opt/synctex:ro") - logger.log options:options, "options for running docker container" + if Settings.clsi.docker.seccomp_profile? + options.HostConfig.SecurityOpt.push "seccomp=#{Settings.clsi.docker.seccomp_profile}" + return options _fingerprintContainer: (containerOptions) -> diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.coffee b/services/clsi/app/coffee/ProjectPersistenceManager.coffee index 2c73c614aa..944ea70812 100644 --- a/services/clsi/app/coffee/ProjectPersistenceManager.coffee +++ b/services/clsi/app/coffee/ProjectPersistenceManager.coffee @@ -13,7 +13,6 @@ module.exports = ProjectPersistenceManager = markProjectAsJustAccessed: (project_id, callback = (error) ->) -> job = (cb)-> - console.log("markProjectAsJustAccessed") db.Project.findOrCreate(where: {project_id: project_id}) .spread( (project, created) -> @@ -59,7 +58,6 @@ module.exports = ProjectPersistenceManager = _clearProjectFromDatabase: (project_id, callback = (error) ->) -> job = (cb)-> - console.log("_clearProjectFromDatabase") db.Project.destroy(where: {project_id: project_id}) .then(() -> cb()) .error cb @@ -69,7 +67,6 @@ module.exports = ProjectPersistenceManager = _findExpiredProjectIds: (callback = (error, project_ids) ->) -> job = (cb)-> keepProjectsFrom = new Date(Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT) - console.log("_findExpiredProjectIds", keepProjectsFrom) q = {} q[db.op.gt] = keepProjectsFrom db.Project.findAll(where:{lastAccessed:q}) diff --git a/services/clsi/app/coffee/UrlCache.coffee b/services/clsi/app/coffee/UrlCache.coffee index 73d9841131..d44479a10a 100644 --- a/services/clsi/app/coffee/UrlCache.coffee +++ b/services/clsi/app/coffee/UrlCache.coffee @@ -52,9 +52,7 @@ module.exports = UrlCache = _doesUrlNeedDownloading: (project_id, url, lastModified, callback = (error, needsDownloading) ->) -> if !lastModified? return callback null, true - console.log "about to get _findUrlDetails" UrlCache._findUrlDetails project_id, url, (error, urlDetails) -> - console.log error, urlDetails, "_findUrlDetails result" return callback(error) if error? if !urlDetails? or !urlDetails.lastModified? or urlDetails.lastModified.getTime() < lastModified.getTime() return callback null, true @@ -104,7 +102,6 @@ module.exports = UrlCache = _updateOrCreateUrlDetails: (project_id, url, lastModified, callback = (error) ->) -> job = (cb)-> - console.log("_updateOrCreateUrlDetails") db.UrlCache.findOrCreate(where: {url: url, project_id: project_id}) .spread( (urlDetails, created) -> @@ -117,7 +114,6 @@ module.exports = UrlCache = _clearUrlDetails: (project_id, url, callback = (error) ->) -> job = (cb)-> - console.log("_clearUrlDetails") db.UrlCache.destroy(where: {url: url, project_id: project_id}) .then(() -> cb null) .error cb @@ -126,7 +122,6 @@ module.exports = UrlCache = _findAllUrlsInProject: (project_id, callback = (error, urls) ->) -> job = (cb)-> - console.log("_findAllUrlsInProject") db.UrlCache.findAll(where: { project_id: project_id }) .then( (urlEntries) -> From 6f926007d29aed5f13bae959be2c3c671d66f124 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Wed, 1 Aug 2018 14:10:22 +0100 Subject: [PATCH 425/754] null check host options --- services/clsi/app/coffee/DockerRunner.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/DockerRunner.coffee b/services/clsi/app/coffee/DockerRunner.coffee index 589a2e03b1..1e2612cc41 100644 --- a/services/clsi/app/coffee/DockerRunner.coffee +++ b/services/clsi/app/coffee/DockerRunner.coffee @@ -47,7 +47,7 @@ module.exports = DockerRunner = options.name = name = "project-#{project_id}-#{fingerprint}" logOptions = _.clone(options) - logOptions.HostConfig.SecurityOpt = "secomp used, removed in logging" + logOptions?.HostConfig?.SecurityOpt = "secomp used, removed in logging" logger.log project_id: project_id, options:logOptions, "running docker container" DockerRunner._runAndWaitForContainer options, volumes, timeout, (error, output) -> if error?.message?.match("HTTP code is 500") From 361eaf9217717b2785fb4c524f796cc7ea0a1a41 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Wed, 1 Aug 2018 14:32:17 +0100 Subject: [PATCH 426/754] comment out erroring log for moment --- services/clsi/app/coffee/DockerRunner.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/clsi/app/coffee/DockerRunner.coffee b/services/clsi/app/coffee/DockerRunner.coffee index 1e2612cc41..d529b2ded3 100644 --- a/services/clsi/app/coffee/DockerRunner.coffee +++ b/services/clsi/app/coffee/DockerRunner.coffee @@ -46,9 +46,9 @@ module.exports = DockerRunner = fingerprint = DockerRunner._fingerprintContainer(options) options.name = name = "project-#{project_id}-#{fingerprint}" - logOptions = _.clone(options) - logOptions?.HostConfig?.SecurityOpt = "secomp used, removed in logging" - logger.log project_id: project_id, options:logOptions, "running docker container" + # logOptions = _.clone(options) + # logOptions?.HostConfig?.SecurityOpt = "secomp used, removed in logging" + logger.log project_id: project_id, "running docker container" DockerRunner._runAndWaitForContainer options, volumes, timeout, (error, output) -> if error?.message?.match("HTTP code is 500") logger.log err: error, project_id: project_id, "error running container so destroying and retrying" From 2aac47256fae2dcfdbbddcc5b57c78edd9560d07 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Fri, 3 Aug 2018 15:33:53 +0100 Subject: [PATCH 427/754] Read sentry dsn from env --- services/clsi/config/settings.defaults.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index cfa6bda368..6f4f160eae 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -39,6 +39,8 @@ module.exports = parallelSqlQueryLimit:1 filestoreDomainOveride: process.env["FILESTORE_DOMAIN_OVERRIDE"] texliveImageNameOveride: process.env["TEX_LIVE_IMAGE_NAME_OVERRIDE"] + sentry: + dsn: process.env['SENTRY_DSN'] if process.env["DOCKER_RUNNER"] From f878ba0f200f55ba2d748aeb7a5b5d7b618aaa4b Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Mon, 13 Aug 2018 12:27:13 +0100 Subject: [PATCH 428/754] Put a guard on sentry dsn --- services/clsi/config/settings.defaults.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index 6f4f160eae..19fcb3269c 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -39,6 +39,7 @@ module.exports = parallelSqlQueryLimit:1 filestoreDomainOveride: process.env["FILESTORE_DOMAIN_OVERRIDE"] texliveImageNameOveride: process.env["TEX_LIVE_IMAGE_NAME_OVERRIDE"] +if process.env['SENTRY_DSN'] sentry: dsn: process.env['SENTRY_DSN'] From 1aee86553277e141eca330a51c569b99253a4ed4 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Mon, 13 Aug 2018 17:36:53 +0100 Subject: [PATCH 429/754] Revert "Put a guard on sentry dsn" This reverts commit 95e052d05910680aeeca2521b65b089122c4e249. --- services/clsi/config/settings.defaults.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index 19fcb3269c..6f4f160eae 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -39,7 +39,6 @@ module.exports = parallelSqlQueryLimit:1 filestoreDomainOveride: process.env["FILESTORE_DOMAIN_OVERRIDE"] texliveImageNameOveride: process.env["TEX_LIVE_IMAGE_NAME_OVERRIDE"] -if process.env['SENTRY_DSN'] sentry: dsn: process.env['SENTRY_DSN'] From 3dfc03fb0a378d7cf8be76bc3447172b75fd5757 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 14 Aug 2018 15:17:56 +0100 Subject: [PATCH 430/754] put FILESTORE_PARALLEL_FILE_DOWNLOADS and FILESTORE_PARALLEL_SQL_QUERY_LIMIT into env vars --- services/clsi/config/settings.defaults.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index cfa6bda368..6dae3fec97 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -35,8 +35,8 @@ module.exports = smokeTest: process.env["SMOKE_TEST"] or false project_cache_length_ms: 1000 * 60 * 60 * 24 - parallelFileDownloads:1 - parallelSqlQueryLimit:1 + parallelFileDownloads: process.env["FILESTORE_PARALLEL_FILE_DOWNLOADS"] or 1 + parallelSqlQueryLimit: process.env["FILESTORE_PARALLEL_SQL_QUERY_LIMIT"] or 1 filestoreDomainOveride: process.env["FILESTORE_DOMAIN_OVERRIDE"] texliveImageNameOveride: process.env["TEX_LIVE_IMAGE_NAME_OVERRIDE"] From e61907bf485704cea27459b4c88e5b9c4ad133c7 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Sun, 19 Aug 2018 11:38:27 +0100 Subject: [PATCH 431/754] added loads of debugging --- services/clsi/app/coffee/LockManager.coffee | 7 +++++-- .../clsi/app/coffee/ProjectPersistenceManager.coffee | 10 +++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/services/clsi/app/coffee/LockManager.coffee b/services/clsi/app/coffee/LockManager.coffee index 5d6fa4626d..2877d5f97d 100644 --- a/services/clsi/app/coffee/LockManager.coffee +++ b/services/clsi/app/coffee/LockManager.coffee @@ -2,7 +2,8 @@ Settings = require('settings-sharelatex') logger = require "logger-sharelatex" Lockfile = require('lockfile') # from https://github.com/npm/lockfile Errors = require "./Errors" - +fs = require("fs") +Path = require("path") module.exports = LockManager = LOCK_TEST_INTERVAL: 1000 # 50ms between each test of the lock MAX_LOCK_WAIT_TIME: 15000 # 10s maximum time to spend trying to get the lock @@ -15,7 +16,9 @@ module.exports = LockManager = stale: @LOCK_STALE Lockfile.lock path, lockOpts, (error) -> return callback new Errors.AlreadyCompilingError("compile in progress") if error?.code is 'EEXIST' - return callback(error) if error? + if error? + logger.err error:error, path:path, statLock:fs.lstatSync(path), statDir: fs.lstatSync(path.dirname(path)), "unable to get lock" + return callback(error) runner (error1, args...) -> Lockfile.unlock path, (error2) -> error = error1 or error2 diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.coffee b/services/clsi/app/coffee/ProjectPersistenceManager.coffee index 944ea70812..2565af399d 100644 --- a/services/clsi/app/coffee/ProjectPersistenceManager.coffee +++ b/services/clsi/app/coffee/ProjectPersistenceManager.coffee @@ -51,12 +51,16 @@ module.exports = ProjectPersistenceManager = clearProjectFromCache: (project_id, callback = (error) ->) -> logger.log project_id: project_id, "clearing project from cache" UrlCache.clearProject project_id, (error) -> - return callback(error) if error? + if error? + logger.err error:error, project_id: project_id, "error clearing project from cache" + return callback(error) ProjectPersistenceManager._clearProjectFromDatabase project_id, (error) -> - return callback(error) if error? - callback() + if error? + logger.err error:error, project_id:project_id, "error clearing project from database" + callback(error) _clearProjectFromDatabase: (project_id, callback = (error) ->) -> + logger.log project_id:project_id, "clearing project from database" job = (cb)-> db.Project.destroy(where: {project_id: project_id}) .then(() -> cb()) From f0b927e8e2d1f6c31cbf961bfde00551f5ee49dc Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 20 Aug 2018 10:12:32 +0100 Subject: [PATCH 432/754] improve error reporting --- services/clsi/app/coffee/CompileManager.coffee | 3 ++- services/clsi/app/coffee/DockerRunner.coffee | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 807b8c442f..daa9021745 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -290,7 +290,8 @@ module.exports = CompileManager = return callback(error) if error? fs.readFile directory + "/" + file_name + ".wc", "utf-8", (err, stdout) -> if err? - logger.err err:err, command:command, directory:directory, project_id:project_id, user_id:user_id, "error reading word count output" + #call it node_err so sentry doesn't use random path error as unique id so it can't be ignored + logger.err node_err:err, command:command, directory:directory, project_id:project_id, user_id:user_id, "error reading word count output" return callback(err) results = CompileManager._parseWordcountFromOutput(stdout) logger.log project_id:project_id, user_id:user_id, wordcount: results, "word count results" diff --git a/services/clsi/app/coffee/DockerRunner.coffee b/services/clsi/app/coffee/DockerRunner.coffee index d529b2ded3..42c9f9a8c6 100644 --- a/services/clsi/app/coffee/DockerRunner.coffee +++ b/services/clsi/app/coffee/DockerRunner.coffee @@ -214,7 +214,7 @@ module.exports = DockerRunner = if error?.statusCode == 404 createAndStartContainer() else if error? - logger.err {container_name: name}, "unable to inspect container to start" + logger.err {container_name: name, error:error}, "unable to inspect container to start" return callback(error) else startExistingContainer() From 9f5fbefb8a9eded59470516389a5589ad99f9d8c Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 21 Aug 2018 12:02:12 +0100 Subject: [PATCH 433/754] add log on exited error code --- services/clsi/app/coffee/DockerRunner.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/app/coffee/DockerRunner.coffee b/services/clsi/app/coffee/DockerRunner.coffee index 42c9f9a8c6..d1337d644b 100644 --- a/services/clsi/app/coffee/DockerRunner.coffee +++ b/services/clsi/app/coffee/DockerRunner.coffee @@ -107,6 +107,7 @@ module.exports = DockerRunner = if exitCode is 1 # exit status from chktex err = DockerRunner.ERR_EXITED err.code = exitCode + logger.err err:err, exitCode:exitCode, options:options, "docker container has exited" return callback(err) containerReturned = true callbackIfFinished() From ae84777c941f2d987b7d0b647386b2b478b47c18 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 21 Aug 2018 18:56:53 +0100 Subject: [PATCH 434/754] add time secomp --- services/clsi/seccomp/clsi-profile.json | 40 +++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/services/clsi/seccomp/clsi-profile.json b/services/clsi/seccomp/clsi-profile.json index cab7a445a4..34fd2520ad 100644 --- a/services/clsi/seccomp/clsi-profile.json +++ b/services/clsi/seccomp/clsi-profile.json @@ -31,6 +31,21 @@ "action": "SCMP_ACT_ALLOW", "args": [] }, + { + "name": "clock_getres", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "clock_gettime", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "clock_nanosleep", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, { "name": "clone", "action": "SCMP_ACT_ALLOW", @@ -668,6 +683,31 @@ "action": "SCMP_ACT_ALLOW", "args": [] }, + { + "name": "timer_create", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "timer_delete", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "timer_getoverrun", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "timer_gettime", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "timer_settime", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, { "name": "times", "action": "SCMP_ACT_ALLOW", From 40f4357cd68ca52e12abbfb0da01063d52c3985c Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Wed, 22 Aug 2018 18:21:15 +0100 Subject: [PATCH 435/754] fix sql query checking last access time --- services/clsi/app/coffee/ProjectPersistenceManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.coffee b/services/clsi/app/coffee/ProjectPersistenceManager.coffee index 2565af399d..4ea02bf728 100644 --- a/services/clsi/app/coffee/ProjectPersistenceManager.coffee +++ b/services/clsi/app/coffee/ProjectPersistenceManager.coffee @@ -72,7 +72,7 @@ module.exports = ProjectPersistenceManager = job = (cb)-> keepProjectsFrom = new Date(Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT) q = {} - q[db.op.gt] = keepProjectsFrom + q[db.op.lt] = keepProjectsFrom db.Project.findAll(where:{lastAccessed:q}) .then((projects) -> cb null, projects.map((project) -> project.project_id) From 6c0665bb49f623cb4eaafcaed1709f65dc5fc105 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Wed, 22 Aug 2018 21:32:19 +0100 Subject: [PATCH 436/754] change sync to async for lockfile debugging --- services/clsi/app/coffee/LockManager.coffee | 18 ++++++++++-------- .../test/unit/coffee/LockManagerTests.coffee | 2 ++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/services/clsi/app/coffee/LockManager.coffee b/services/clsi/app/coffee/LockManager.coffee index 2877d5f97d..069e26b2ad 100644 --- a/services/clsi/app/coffee/LockManager.coffee +++ b/services/clsi/app/coffee/LockManager.coffee @@ -16,11 +16,13 @@ module.exports = LockManager = stale: @LOCK_STALE Lockfile.lock path, lockOpts, (error) -> return callback new Errors.AlreadyCompilingError("compile in progress") if error?.code is 'EEXIST' - if error? - logger.err error:error, path:path, statLock:fs.lstatSync(path), statDir: fs.lstatSync(path.dirname(path)), "unable to get lock" - return callback(error) - runner (error1, args...) -> - Lockfile.unlock path, (error2) -> - error = error1 or error2 - return callback(error) if error? - callback(null, args...) + fs.lstat path, (err, statLock)-> + fs.lstat Path.dirname(path), (err, statDir)-> + if error? + logger.err error:error, path:path, statLock:statLock, statDir: statDir, "unable to get lock" + return callback(error) + runner (error1, args...) -> + Lockfile.unlock path, (error2) -> + error = error1 or error2 + return callback(error) if error? + callback(null, args...) diff --git a/services/clsi/test/unit/coffee/LockManagerTests.coffee b/services/clsi/test/unit/coffee/LockManagerTests.coffee index 2d0c95afa1..ab9ce9385c 100644 --- a/services/clsi/test/unit/coffee/LockManagerTests.coffee +++ b/services/clsi/test/unit/coffee/LockManagerTests.coffee @@ -10,6 +10,8 @@ describe "DockerLockManager", -> @LockManager = SandboxedModule.require modulePath, requires: "settings-sharelatex": {} "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } + "fs": + lstat:sinon.stub().callsArgWith(1) "lockfile": @Lockfile = {} @lockFile = "/local/compile/directory/.project-lock" From a80700f3e429fb14f7a051864c021b1e60fc187e Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Wed, 22 Aug 2018 23:54:40 +0100 Subject: [PATCH 437/754] improve error reporting --- services/clsi/app/coffee/LockManager.coffee | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/services/clsi/app/coffee/LockManager.coffee b/services/clsi/app/coffee/LockManager.coffee index 069e26b2ad..2a8777fd30 100644 --- a/services/clsi/app/coffee/LockManager.coffee +++ b/services/clsi/app/coffee/LockManager.coffee @@ -16,11 +16,13 @@ module.exports = LockManager = stale: @LOCK_STALE Lockfile.lock path, lockOpts, (error) -> return callback new Errors.AlreadyCompilingError("compile in progress") if error?.code is 'EEXIST' - fs.lstat path, (err, statLock)-> - fs.lstat Path.dirname(path), (err, statDir)-> - if error? - logger.err error:error, path:path, statLock:statLock, statDir: statDir, "unable to get lock" - return callback(error) + if error? + fs.lstat path, (statLockErr, statLock)-> + fs.lstat Path.dirname(path), (statDirErr, statDir)-> + fs.readdir Path.dirname(path), (readdirErr, readdirDir)-> + logger.err error:error, path:path, statLock:statLock, statLockErr:statLockErr, statDir:statDir, statDirErr: statDirErr, readdirErr:readdirErr, readdirDir:readdirDir, "unable to get lock" + return callback(error) + else runner (error1, args...) -> Lockfile.unlock path, (error2) -> error = error1 or error2 From b28d2103578687d32e467e684a9e37c0d0e135f9 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 23 Aug 2018 00:10:06 +0100 Subject: [PATCH 438/754] try changing bin to be owned by node --- services/clsi/entrypoint.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh index 32776fa553..cb2580cbbf 100755 --- a/services/clsi/entrypoint.sh +++ b/services/clsi/entrypoint.sh @@ -17,6 +17,7 @@ chown -R node:node /app/bin/synctex mkdir -p /app/test/acceptance/fixtures/tmp/ chown -R node:node /app +chown -R node:node /app/bin ./bin/install_texlive_gce.sh exec runuser -u node "$@" \ No newline at end of file From 2aaadc61248b808d1aad19df4122edae88e0bc70 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 23 Aug 2018 00:12:05 +0100 Subject: [PATCH 439/754] fix unit tests --- services/clsi/app/coffee/LockManager.coffee | 23 ++++++++++--------- .../test/unit/coffee/LockManagerTests.coffee | 3 ++- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/services/clsi/app/coffee/LockManager.coffee b/services/clsi/app/coffee/LockManager.coffee index 2a8777fd30..afa3cca971 100644 --- a/services/clsi/app/coffee/LockManager.coffee +++ b/services/clsi/app/coffee/LockManager.coffee @@ -15,16 +15,17 @@ module.exports = LockManager = pollPeriod: @LOCK_TEST_INTERVAL stale: @LOCK_STALE Lockfile.lock path, lockOpts, (error) -> - return callback new Errors.AlreadyCompilingError("compile in progress") if error?.code is 'EEXIST' - if error? - fs.lstat path, (statLockErr, statLock)-> - fs.lstat Path.dirname(path), (statDirErr, statDir)-> - fs.readdir Path.dirname(path), (readdirErr, readdirDir)-> + if error?.code is 'EEXIST' + return callback new Errors.AlreadyCompilingError("compile in progress") + else if error? + fs.lstat path, (statLockErr, statLock)-> + fs.lstat Path.dirname(path), (statDirErr, statDir)-> + fs.readdir Path.dirname(path), (readdirErr, readdirDir)-> logger.err error:error, path:path, statLock:statLock, statLockErr:statLockErr, statDir:statDir, statDirErr: statDirErr, readdirErr:readdirErr, readdirDir:readdirDir, "unable to get lock" return callback(error) - else - runner (error1, args...) -> - Lockfile.unlock path, (error2) -> - error = error1 or error2 - return callback(error) if error? - callback(null, args...) + else + runner (error1, args...) -> + Lockfile.unlock path, (error2) -> + error = error1 or error2 + return callback(error) if error? + callback(null, args...) diff --git a/services/clsi/test/unit/coffee/LockManagerTests.coffee b/services/clsi/test/unit/coffee/LockManagerTests.coffee index ab9ce9385c..9dd1d46cbb 100644 --- a/services/clsi/test/unit/coffee/LockManagerTests.coffee +++ b/services/clsi/test/unit/coffee/LockManagerTests.coffee @@ -9,9 +9,10 @@ describe "DockerLockManager", -> beforeEach -> @LockManager = SandboxedModule.require modulePath, requires: "settings-sharelatex": {} - "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } + "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub(), err:-> } "fs": lstat:sinon.stub().callsArgWith(1) + readdir: sinon.stub().callsArgWith(1) "lockfile": @Lockfile = {} @lockFile = "/local/compile/directory/.project-lock" From 59638c261db6357d7c21c01523cdad9a22ac9e4e Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 23 Aug 2018 08:34:18 +0100 Subject: [PATCH 440/754] fse.ensureDir when running synctex and wordcount --- .../clsi/app/coffee/CompileController.coffee | 2 - .../clsi/app/coffee/CompileManager.coffee | 59 +++++++++++-------- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 91137f6d13..5d1ee2e764 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -82,7 +82,6 @@ module.exports = CompileController = column = parseInt(req.query.column, 10) project_id = req.params.project_id user_id = req.params.user_id - CompileManager.syncFromCode project_id, user_id, file, line, column, (error, pdfPositions) -> return next(error) if error? res.send JSON.stringify { @@ -95,7 +94,6 @@ module.exports = CompileController = v = parseFloat(req.query.v) project_id = req.params.project_id user_id = req.params.user_id - CompileManager.syncFromPdf project_id, user_id, page, h, v, (error, codePositions) -> return next(error) if error? res.send JSON.stringify { diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index daa9021745..f40a98b1ce 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -203,14 +203,18 @@ module.exports = CompileManager = compileDir = getCompileDir(project_id, user_id) synctex_path = "#{base_dir}/output.pdf" command = ["code", synctex_path, file_path, line, column] - CompileManager._runSynctex project_id, user_id, command, (error, stdout) -> - return callback(error) if error? - if stdout.toLowerCase().indexOf("warning") == -1 - logType = "log" - else - logType = "err" - logger[logType] project_id: project_id, user_id:user_id, file_name: file_name, line: line, column: column, command:command, stdout: stdout, "synctex code output" - callback null, CompileManager._parseSynctexFromCodeOutput(stdout) + fse.ensureDir compileDir, (error) -> + if error? + logger.err {error, project_id, user_id, file_name}, "error ensuring dir for sync from code" + return callback(error) + CompileManager._runSynctex project_id, user_id, command, (error, stdout) -> + return callback(error) if error? + if stdout.toLowerCase().indexOf("warning") == -1 + logType = "log" + else + logType = "err" + logger[logType] project_id: project_id, user_id:user_id, file_name: file_name, line: line, column: column, command:command, stdout: stdout, "synctex code output" + callback null, CompileManager._parseSynctexFromCodeOutput(stdout) syncFromPdf: (project_id, user_id, page, h, v, callback = (error, filePositions) ->) -> compileName = getCompileName(project_id, user_id) @@ -218,10 +222,14 @@ module.exports = CompileManager = base_dir = Settings.path.synctexBaseDir(compileName) synctex_path = "#{base_dir}/output.pdf" command = ["pdf", synctex_path, page, h, v] - CompileManager._runSynctex project_id, user_id, command, (error, stdout) -> - return callback(error) if error? - logger.log project_id: project_id, user_id:user_id, page: page, h: h, v:v, stdout: stdout, "synctex pdf output" - callback null, CompileManager._parseSynctexFromPdfOutput(stdout, base_dir) + fse.ensureDir compileDir, (error) -> + if error? + logger.err {error, project_id, user_id, file_name}, "error ensuring dir for sync to code" + return callback(error) + CompileManager._runSynctex project_id, user_id, command, (error, stdout) -> + return callback(error) if error? + logger.log project_id: project_id, user_id:user_id, page: page, h: h, v:v, stdout: stdout, "synctex pdf output" + callback null, CompileManager._parseSynctexFromPdfOutput(stdout, base_dir) _checkFileExists: (path, callback = (error) ->) -> synctexDir = Path.dirname(path) @@ -282,20 +290,23 @@ module.exports = CompileManager = logger.log project_id:project_id, user_id:user_id, file_name:file_name, image:image, "running wordcount" file_path = "$COMPILE_DIR/" + file_name command = [ "texcount", '-nocol', '-inc', file_path, "-out=" + file_path + ".wc"] - directory = getCompileDir(project_id, user_id) + compileDir = getCompileDir(project_id, user_id) timeout = 10 * 1000 compileName = getCompileName(project_id, user_id) - - CommandRunner.run compileName, command, directory, image, timeout, {}, (error) -> - return callback(error) if error? - fs.readFile directory + "/" + file_name + ".wc", "utf-8", (err, stdout) -> - if err? - #call it node_err so sentry doesn't use random path error as unique id so it can't be ignored - logger.err node_err:err, command:command, directory:directory, project_id:project_id, user_id:user_id, "error reading word count output" - return callback(err) - results = CompileManager._parseWordcountFromOutput(stdout) - logger.log project_id:project_id, user_id:user_id, wordcount: results, "word count results" - callback null, results + fse.ensureDir compileDir, (error) -> + if error? + logger.err {error, project_id, user_id, file_name}, "error ensuring dir for sync from code" + return callback(error) + CommandRunner.run compileName, command, compileDir, image, timeout, {}, (error) -> + return callback(error) if error? + fs.readFile compileDir + "/" + file_name + ".wc", "utf-8", (err, stdout) -> + if err? + #call it node_err so sentry doesn't use random path error as unique id so it can't be ignored + logger.err node_err:err, command:command, compileDir:compileDir, project_id:project_id, user_id:user_id, "error reading word count output" + return callback(err) + results = CompileManager._parseWordcountFromOutput(stdout) + logger.log project_id:project_id, user_id:user_id, wordcount: results, "word count results" + callback null, results _parseWordcountFromOutput: (output) -> results = { From 250f4a42f0a54fe526c904f71ae09a0330a2bd27 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 23 Aug 2018 11:16:28 +0100 Subject: [PATCH 441/754] reduce log level --- services/clsi/app/coffee/DockerRunner.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/DockerRunner.coffee b/services/clsi/app/coffee/DockerRunner.coffee index d1337d644b..e71a93330c 100644 --- a/services/clsi/app/coffee/DockerRunner.coffee +++ b/services/clsi/app/coffee/DockerRunner.coffee @@ -107,9 +107,10 @@ module.exports = DockerRunner = if exitCode is 1 # exit status from chktex err = DockerRunner.ERR_EXITED err.code = exitCode - logger.err err:err, exitCode:exitCode, options:options, "docker container has exited" return callback(err) containerReturned = true + options.SecurityOpt = null #small log line + logger.log err:err, exitCode:exitCode, options:options, "docker container has exited" callbackIfFinished() _getContainerOptions: (command, image, volumes, timeout, environment) -> From 905a7615443968cc6b69d318dabbd5abc72a0b76 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 23 Aug 2018 11:18:05 +0100 Subject: [PATCH 442/754] don't error on a bad synctex call --- services/clsi/app/coffee/CompileManager.coffee | 6 +----- services/clsi/app/coffee/DockerRunner.coffee | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index f40a98b1ce..0436a8983e 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -209,11 +209,7 @@ module.exports = CompileManager = return callback(error) CompileManager._runSynctex project_id, user_id, command, (error, stdout) -> return callback(error) if error? - if stdout.toLowerCase().indexOf("warning") == -1 - logType = "log" - else - logType = "err" - logger[logType] project_id: project_id, user_id:user_id, file_name: file_name, line: line, column: column, command:command, stdout: stdout, "synctex code output" + logger.log project_id: project_id, user_id:user_id, file_name: file_name, line: line, column: column, command:command, stdout: stdout, "synctex code output" callback null, CompileManager._parseSynctexFromCodeOutput(stdout) syncFromPdf: (project_id, user_id, page, h, v, callback = (error, filePositions) ->) -> diff --git a/services/clsi/app/coffee/DockerRunner.coffee b/services/clsi/app/coffee/DockerRunner.coffee index e71a93330c..3c2ed9c581 100644 --- a/services/clsi/app/coffee/DockerRunner.coffee +++ b/services/clsi/app/coffee/DockerRunner.coffee @@ -109,7 +109,7 @@ module.exports = DockerRunner = err.code = exitCode return callback(err) containerReturned = true - options.SecurityOpt = null #small log line + options?.HostConfig?.SecurityOpt = null #small log line logger.log err:err, exitCode:exitCode, options:options, "docker container has exited" callbackIfFinished() From 5a635e5465633f8bc20f499b735acf324c16616f Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Tue, 11 Sep 2018 09:44:22 +0100 Subject: [PATCH 443/754] cache pdf files generated by epstopdf --- services/clsi/app/coffee/ResourceWriter.coffee | 2 ++ services/clsi/test/unit/coffee/ResourceWriterTests.coffee | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index 0b6aef5b72..85df35da96 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -80,6 +80,8 @@ module.exports = ResourceWriter = should_delete = false if path.match(/^output-.*/) # Tikz cached figures should_delete = false + if path.match(/-eps-converted-to\.pdf$/) # Epstopdf generated files + should_delete = false if path == "output.pdf" or path == "output.dvi" or path == "output.log" or path == "output.xdv" should_delete = true if path == "output.tex" # created by TikzManager if present in output files diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee index fbc8916c31..66a8a36d96 100644 --- a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee +++ b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee @@ -134,6 +134,9 @@ describe "ResourceWriter", -> type: "aux" }, { path: "cache/_chunk1" + },{ + path: "figures/image-eps-converted-to.pdf" + type: "pdf" }] @resources = "mock-resources" @OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, @output_files) @@ -165,6 +168,11 @@ describe "ResourceWriter", -> .calledWith(path.join(@basePath, "cache/_chunk1")) .should.equal false + it "should not delete the epstopdf converted files", -> + @ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(@basePath, "figures/image-eps-converted-to.pdf")) + .should.equal false + it "should call the callback", -> @callback.called.should.equal true From bec46504eb70a8ddf5f7f9e8c3fd2bb6e0f26a77 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 11 Sep 2018 09:51:20 +0100 Subject: [PATCH 444/754] improve synctex logging --- services/clsi/app/coffee/CompileManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 0436a8983e..64f2665cd5 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -251,7 +251,7 @@ module.exports = CompileManager = compileName = getCompileName(project_id, user_id) CommandRunner.run compileName, command, directory, Settings.clsi.docker.image, timeout, {}, (error, output) -> if error? - logger.err err:error, command:command, "error running synctex" + logger.err err:error, command:command, project_id:project_id, user_id:user_id, "error running synctex" return callback(error) callback(null, output.stdout) From ce44fa3390632ee0bf5fa3b3f35eaa909935e8a7 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 11 Sep 2018 09:54:44 +0100 Subject: [PATCH 445/754] bump wordcount timeouts, taken from 82b996b145196711e439d7d7045f53498c1afa1a --- services/clsi/app/coffee/CompileManager.coffee | 2 +- services/clsi/test/unit/coffee/CompileManagerTests.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 64f2665cd5..893d461150 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -247,7 +247,7 @@ module.exports = CompileManager = command.unshift("/opt/synctex") directory = getCompileDir(project_id, user_id) - timeout = 10 * 1000 + timeout = 60 * 1000 # increased to allow for large projects compileName = getCompileName(project_id, user_id) CommandRunner.run compileName, command, directory, Settings.clsi.docker.image, timeout, {}, (error, output) -> if error? diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 93b7f11b59..26524fb75d 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -319,7 +319,7 @@ describe "CompileManager", -> @callback = sinon.stub() @project_id - @timeout = 10 * 1000 + @timeout = 60 * 1000 @file_name = "main.tex" @Settings.path.compilesDir = "/local/compile/directory" @image = "example.com/image" From 3fc3cd11f431aad567488e80ea0379dcba03a19e Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 11 Sep 2018 10:21:37 +0100 Subject: [PATCH 446/754] fix unit tests --- services/clsi/app/coffee/CompileManager.coffee | 5 +++-- services/clsi/test/unit/coffee/CompileManagerTests.coffee | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 9c28535bd2..eff20ebd2c 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -282,12 +282,13 @@ module.exports = CompileManager = } return results + wordcount: (project_id, user_id, file_name, image, callback = (error, pdfPositions) ->) -> logger.log project_id:project_id, user_id:user_id, file_name:file_name, image:image, "running wordcount" file_path = "$COMPILE_DIR/" + file_name command = [ "texcount", '-nocol', '-inc', file_path, "-out=" + file_path + ".wc"] - directory = getCompileDir(project_id, user_id) - timeout = 60 * 1000 # increased to allow for large projects + compileDir = getCompileDir(project_id, user_id) + timeout = 60 * 1000 compileName = getCompileName(project_id, user_id) fse.ensureDir compileDir, (error) -> if error? diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 26524fb75d..608a3e55a4 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -269,7 +269,7 @@ describe "CompileManager", -> ['/opt/synctex', 'code', synctex_path, file_path, @line, @column], "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}", @Settings.clsi.docker.image, - 10000, + 60000, {} ).should.equal true @@ -300,7 +300,7 @@ describe "CompileManager", -> ['/opt/synctex', "pdf", synctex_path, @page, @h, @v], "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}", @Settings.clsi.docker.image, - 10000, + 60000, {}).should.equal true it "should call the callback with the parsed output", -> From 3c12f60eebc2c39345330a843ff7bcbd1f3de4a1 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 11 Sep 2018 11:34:25 +0100 Subject: [PATCH 447/754] change timeout test latex code --- services/clsi/test/acceptance/coffee/TimeoutTests.coffee | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/clsi/test/acceptance/coffee/TimeoutTests.coffee b/services/clsi/test/acceptance/coffee/TimeoutTests.coffee index 877223a961..c3acd8f9f8 100644 --- a/services/clsi/test/acceptance/coffee/TimeoutTests.coffee +++ b/services/clsi/test/acceptance/coffee/TimeoutTests.coffee @@ -8,13 +8,14 @@ describe "Timed out compile", -> before (done) -> @request = options: - timeout: 1 #seconds + timeout: 10 #seconds resources: [ path: "main.tex" content: ''' \\documentclass{article} \\begin{document} - \\input{|"/bin/bash -c ':(){ :|:& };:'"} + \\def\\x{Hello!\\par\\x} + \\x \\end{document} ''' ] From 1b3e2678bfb0ce11c9e7522bb929fc2279b7b0e3 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 14 Sep 2018 10:26:40 +0100 Subject: [PATCH 448/754] remove debugging get settings function --- services/clsi/app.coffee | 5 ----- 1 file changed, 5 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 8b5c779d27..367174f387 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -143,11 +143,6 @@ app.get "/health_check", (req, res)-> app.get "/smoke_test_force", (req, res)-> smokeTest.run(require.resolve(__dirname + "/test/smoke/js/SmokeTests.js"))(req, res) - -#TODO delete this -app.get "/settings", (req, res)-> - res.json(Settings) - profiler = require "v8-profiler" app.get "/profile", (req, res) -> time = parseInt(req.query.time || "1000") From 74dd560979934af134687f1b9227b3000be800f1 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Thu, 4 Oct 2018 16:38:41 +0100 Subject: [PATCH 449/754] extend caching for tikz, minted and markdown files --- .../clsi/app/coffee/ResourceWriter.coffee | 8 ++- .../unit/coffee/ResourceWriterTests.coffee | 56 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index 1285572304..f3a4bd0735 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -78,7 +78,13 @@ module.exports = ResourceWriter = should_delete = true if path.match(/^output\./) or path.match(/\.aux$/) or path.match(/^cache\//) # knitr cache should_delete = false - if path.match(/^output-.*/) # Tikz cached figures + if path.match(/^output-.*/) # Tikz cached figures (default case) + should_delete = false + if path.match(/\.(pdf|dpth|md5)$/) # Tikz cached figures (by extension) + should_delete = false + if path.match(/\.(pygtex|pygstyle)$/) or path.match(/(^|\/)_minted-[^\/]+\//) # minted files/directory + should_delete = false + if path.match(/\.md\.tex$/) or path.match(/(^|\/)_markdown_[^\/]+\//) # markdown files/directory should_delete = false if path.match(/-eps-converted-to\.pdf$/) # Epstopdf generated files should_delete = false diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee index 66a8a36d96..4a88226f20 100644 --- a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee +++ b/services/clsi/test/unit/coffee/ResourceWriterTests.coffee @@ -137,6 +137,27 @@ describe "ResourceWriter", -> },{ path: "figures/image-eps-converted-to.pdf" type: "pdf" + },{ + path: "foo/main-figure0.md5" + type: "md5" + }, { + path: "foo/main-figure0.dpth" + type: "dpth" + }, { + path: "foo/main-figure0.pdf" + type: "pdf" + }, { + path: "_minted-main/default-pyg-prefix.pygstyle" + type: "pygstyle" + }, { + path: "_minted-main/default.pygstyle" + type: "pygstyle" + }, { + path: "_minted-main/35E248B60965545BD232AE9F0FE9750D504A7AF0CD3BAA7542030FC560DFCC45.pygtex" + type: "pygtex" + }, { + path: "_markdown_main/30893013dec5d869a415610079774c2f.md.tex" + type: "tex" }] @resources = "mock-resources" @OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, @output_files) @@ -173,6 +194,41 @@ describe "ResourceWriter", -> .calledWith(path.join(@basePath, "figures/image-eps-converted-to.pdf")) .should.equal false + it "should not delete the tikz md5 files", -> + @ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(@basePath, "foo/main-figure0.md5")) + .should.equal false + + it "should not delete the tikz dpth files", -> + @ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(@basePath, "foo/main-figure0.dpth")) + .should.equal false + + it "should not delete the tikz pdf files", -> + @ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(@basePath, "foo/main-figure0.pdf")) + .should.equal false + + it "should not delete the minted pygstyle files", -> + @ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(@basePath, "_minted-main/default-pyg-prefix.pygstyle")) + .should.equal false + + it "should not delete the minted default pygstyle files", -> + @ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(@basePath, "_minted-main/default.pygstyle")) + .should.equal false + + it "should not delete the minted default pygtex files", -> + @ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(@basePath, "_minted-main/35E248B60965545BD232AE9F0FE9750D504A7AF0CD3BAA7542030FC560DFCC45.pygtex")) + .should.equal false + + it "should not delete the markdown md.tex files", -> + @ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(@basePath, "_markdown_main/30893013dec5d869a415610079774c2f.md.tex")) + .should.equal false + it "should call the callback", -> @callback.called.should.equal true From 34acce8bdacaeef82878df1f130a693b6b52b4bd Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Wed, 10 Oct 2018 16:13:20 +0100 Subject: [PATCH 450/754] use TikzManager to create main file for pstool package --- services/clsi/app/coffee/CompileManager.coffee | 4 ++-- services/clsi/app/coffee/TikzManager.coffee | 12 +++++++----- .../clsi/test/unit/coffee/TikzManager.coffee | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index eff20ebd2c..0a98e87db4 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -58,9 +58,9 @@ module.exports = CompileManager = callback() createTikzFileIfRequired = (callback) -> - TikzManager.checkMainFile compileDir, request.rootResourcePath, resourceList, (error, usesTikzExternalize) -> + TikzManager.checkMainFile compileDir, request.rootResourcePath, resourceList, (error, needsMainFile) -> return callback(error) if error? - if usesTikzExternalize + if needsMainFile TikzManager.injectOutputFile compileDir, request.rootResourcePath, callback else callback() diff --git a/services/clsi/app/coffee/TikzManager.coffee b/services/clsi/app/coffee/TikzManager.coffee index 7605b0d277..60cb1e3496 100644 --- a/services/clsi/app/coffee/TikzManager.coffee +++ b/services/clsi/app/coffee/TikzManager.coffee @@ -4,26 +4,28 @@ ResourceWriter = require "./ResourceWriter" SafeReader = require "./SafeReader" logger = require "logger-sharelatex" -# for \tikzexternalize to work the main file needs to match the +# for \tikzexternalize or pstool to work the main file needs to match the # jobname. Since we set the -jobname to output, we have to create a # copy of the main file as 'output.tex'. module.exports = TikzManager = - checkMainFile: (compileDir, mainFile, resources, callback = (error, usesTikzExternalize) ->) -> + checkMainFile: (compileDir, mainFile, resources, callback = (error, needsMainFile) ->) -> # if there's already an output.tex file, we don't want to touch it for resource in resources if resource.path is "output.tex" logger.log compileDir: compileDir, mainFile: mainFile, "output.tex already in resources" return callback(null, false) - # if there's no output.tex, see if we are using tikz/pgf in the main file + # if there's no output.tex, see if we are using tikz/pgf or pstool in the main file ResourceWriter.checkPath compileDir, mainFile, (error, path) -> return callback(error) if error? SafeReader.readFile path, 65536, "utf8", (error, content) -> return callback(error) if error? usesTikzExternalize = content?.indexOf("\\tikzexternalize") >= 0 - logger.log compileDir: compileDir, mainFile: mainFile, usesTikzExternalize:usesTikzExternalize, "checked for tikzexternalize" - callback null, usesTikzExternalize + usesPsTool = content.indexOf("{pstool}") >= 0 + logger.log compileDir: compileDir, mainFile: mainFile, usesTikzExternalize:usesTikzExternalize, usesPsTool: usesPsTool, "checked for packages needing main file as output.tex" + needsMainFile = (usesTikzExternalize || usesPsTool) + callback null, needsMainFile injectOutputFile: (compileDir, mainFile, callback = (error) ->) -> ResourceWriter.checkPath compileDir, mainFile, (error, path) -> diff --git a/services/clsi/test/unit/coffee/TikzManager.coffee b/services/clsi/test/unit/coffee/TikzManager.coffee index 5a3ec5ce5a..69968aa5ea 100644 --- a/services/clsi/test/unit/coffee/TikzManager.coffee +++ b/services/clsi/test/unit/coffee/TikzManager.coffee @@ -65,6 +65,22 @@ describe 'TikzManager', -> @callback.calledWithExactly(null, false) .should.equal true + describe "and the main file contains \\usepackage{pstool}", -> + beforeEach -> + @SafeReader.readFile = sinon.stub() + .withArgs("#{@compileDir}/#{@mainFile}") + .callsArgWith(3, null, "hello \\usepackage[random-options]{pstool}") + @TikzManager.checkMainFile @compileDir, @mainFile, @resources, @callback + + it "should look at the file on disk", -> + @SafeReader.readFile + .calledWith("#{@compileDir}/#{@mainFile}") + .should.equal true + + it "should call the callback with true ", -> + @callback.calledWithExactly(null, true) + .should.equal true + describe "injectOutputFile", -> beforeEach -> @rootDir = "/mock" From 187786b4e4cfa8c8015184459fba2e7e7984e26a Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 12 Oct 2018 10:49:54 +0100 Subject: [PATCH 451/754] improve log message --- services/clsi/app/coffee/TikzManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/TikzManager.coffee b/services/clsi/app/coffee/TikzManager.coffee index 60cb1e3496..5b80e6c7c4 100644 --- a/services/clsi/app/coffee/TikzManager.coffee +++ b/services/clsi/app/coffee/TikzManager.coffee @@ -32,6 +32,6 @@ module.exports = TikzManager = return callback(error) if error? fs.readFile path, "utf8", (error, content) -> return callback(error) if error? - logger.log compileDir: compileDir, mainFile: mainFile, "copied file to output.tex for tikz" + logger.log compileDir: compileDir, mainFile: mainFile, "copied file to output.tex as project uses packages which require it" # use wx flag to ensure that output file does not already exist fs.writeFile Path.join(compileDir, "output.tex"), content, {flag:'wx'}, callback From 55fa22caa9fd17a94604c95634774a79da883cca Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Mon, 15 Oct 2018 10:01:52 +0100 Subject: [PATCH 452/754] fix exception when content undefined in TikzManager --- services/clsi/app/coffee/TikzManager.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/TikzManager.coffee b/services/clsi/app/coffee/TikzManager.coffee index 5b80e6c7c4..22def278b1 100644 --- a/services/clsi/app/coffee/TikzManager.coffee +++ b/services/clsi/app/coffee/TikzManager.coffee @@ -22,7 +22,7 @@ module.exports = TikzManager = SafeReader.readFile path, 65536, "utf8", (error, content) -> return callback(error) if error? usesTikzExternalize = content?.indexOf("\\tikzexternalize") >= 0 - usesPsTool = content.indexOf("{pstool}") >= 0 + usesPsTool = content?.indexOf("{pstool}") >= 0 logger.log compileDir: compileDir, mainFile: mainFile, usesTikzExternalize:usesTikzExternalize, usesPsTool: usesPsTool, "checked for packages needing main file as output.tex" needsMainFile = (usesTikzExternalize || usesPsTool) callback null, needsMainFile From 82bbf0e33604b3060a707eef552c9af2c2681053 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Mon, 22 Oct 2018 16:01:17 +0100 Subject: [PATCH 453/754] Add some notes on the CLSIs --- services/clsi/README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/services/clsi/README.md b/services/clsi/README.md index 270c06ac5d..9048afa7d4 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -5,6 +5,30 @@ A web api for compiling LaTeX documents in the cloud [![Build Status](https://travis-ci.org/sharelatex/clsi-sharelatex.png?branch=master)](https://travis-ci.org/sharelatex/clsi-sharelatex) +The CLSI provide a RESTful interface to traditional LaTeX tools (or, more generally, any command line tool for composing marked-up documents into a display format such as PDF or HTML). The CLSI listens on the following ports by default: + +* TCP/3009 - the RESTful interface +* TCP/3048 - reports load information to the `CLSI-lb` +* TCP/3049 - HTTP interface to control the CLSI service + +These defaults can be modified in `config/settings.defaults.coffee`. + +The provided `Dockerfile` builds a docker image which has the docker command line tools installed. The configuration in `docker-compose-config.yml` mounts the docker socket, in order that the CLSI container can talk to the docker host it is running in. This allows it to spin up `sibling containers` running our TEXLIVE image on the same docker host to perform the actual compiles. + +The CLSI can be configured through the following environment variables: + + * `DOCKER_RUNNER` - Set to true to use sibling containers + * `SYNCTEX_BIN_HOST_PATH` - Path to SyncTeX binary + * `COMPILES_HOST_DIR` - Working directory for LaTeX compiles + * `SQLITE_PATH` - Path to SQLite database + * `TEXLIVE_IMAGE` - The TEXLIVE docker image to use for sibling containers, e.g. `gcr.io/overleaf-ops/texlive-full:2017.1` + * `TEXLIVE_IMAGE_USER` - When using sibling containers, the user to run as in the TEXLIVE image. Defaults to `tex` + * `TEX_LIVE_IMAGE_NAME_OVERRIDE` - The name of the registry for the docker image e.g. `gcr.io/overleaf-ops` + * `FILESTORE_DOMAIN_OVERRIDE` - The url for the filestore service e.g.`http://$FILESTORE_HOST:3009` + * `STATSD_HOST` - The address of the Statsd service (used by the metrics module) + * `LISTEN_ADDRESS` - The address for the RESTful service to listen on. Set to `0.0.0.0` to listen on all network interfaces + * `SMOKE_TEST` - Whether to run smoke tests + Installation ------------ From 03ff1c310a2ba100c52acdfc528ad9221aa64f55 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <mans0954@users.noreply.github.com> Date: Mon, 22 Oct 2018 16:03:50 +0100 Subject: [PATCH 454/754] Fix indenting --- services/clsi/README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/services/clsi/README.md b/services/clsi/README.md index 9048afa7d4..9d7b08a0b0 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -17,17 +17,17 @@ The provided `Dockerfile` builds a docker image which has the docker command lin The CLSI can be configured through the following environment variables: - * `DOCKER_RUNNER` - Set to true to use sibling containers - * `SYNCTEX_BIN_HOST_PATH` - Path to SyncTeX binary - * `COMPILES_HOST_DIR` - Working directory for LaTeX compiles - * `SQLITE_PATH` - Path to SQLite database - * `TEXLIVE_IMAGE` - The TEXLIVE docker image to use for sibling containers, e.g. `gcr.io/overleaf-ops/texlive-full:2017.1` - * `TEXLIVE_IMAGE_USER` - When using sibling containers, the user to run as in the TEXLIVE image. Defaults to `tex` - * `TEX_LIVE_IMAGE_NAME_OVERRIDE` - The name of the registry for the docker image e.g. `gcr.io/overleaf-ops` - * `FILESTORE_DOMAIN_OVERRIDE` - The url for the filestore service e.g.`http://$FILESTORE_HOST:3009` - * `STATSD_HOST` - The address of the Statsd service (used by the metrics module) - * `LISTEN_ADDRESS` - The address for the RESTful service to listen on. Set to `0.0.0.0` to listen on all network interfaces - * `SMOKE_TEST` - Whether to run smoke tests + * `DOCKER_RUNNER` - Set to true to use sibling containers + * `SYNCTEX_BIN_HOST_PATH` - Path to SyncTeX binary + * `COMPILES_HOST_DIR` - Working directory for LaTeX compiles + * `SQLITE_PATH` - Path to SQLite database + * `TEXLIVE_IMAGE` - The TEXLIVE docker image to use for sibling containers, e.g. `gcr.io/overleaf-ops/texlive-full:2017.1` + * `TEXLIVE_IMAGE_USER` - When using sibling containers, the user to run as in the TEXLIVE image. Defaults to `tex` + * `TEX_LIVE_IMAGE_NAME_OVERRIDE` - The name of the registry for the docker image e.g. `gcr.io/overleaf-ops` + * `FILESTORE_DOMAIN_OVERRIDE` - The url for the filestore service e.g.`http://$FILESTORE_HOST:3009` + * `STATSD_HOST` - The address of the Statsd service (used by the metrics module) + * `LISTEN_ADDRESS` - The address for the RESTful service to listen on. Set to `0.0.0.0` to listen on all network interfaces + * `SMOKE_TEST` - Whether to run smoke tests Installation ------------ From 7bd718462364280111ffd27d371238be6dbe66ab Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Mon, 22 Oct 2018 17:52:38 +0100 Subject: [PATCH 455/754] Make REAME more generic --- services/clsi/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/clsi/README.md b/services/clsi/README.md index 9048afa7d4..7b81e4dc1a 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -8,12 +8,12 @@ A web api for compiling LaTeX documents in the cloud The CLSI provide a RESTful interface to traditional LaTeX tools (or, more generally, any command line tool for composing marked-up documents into a display format such as PDF or HTML). The CLSI listens on the following ports by default: * TCP/3009 - the RESTful interface -* TCP/3048 - reports load information to the `CLSI-lb` +* TCP/3048 - reports load information * TCP/3049 - HTTP interface to control the CLSI service These defaults can be modified in `config/settings.defaults.coffee`. -The provided `Dockerfile` builds a docker image which has the docker command line tools installed. The configuration in `docker-compose-config.yml` mounts the docker socket, in order that the CLSI container can talk to the docker host it is running in. This allows it to spin up `sibling containers` running our TEXLIVE image on the same docker host to perform the actual compiles. +The provided `Dockerfile` builds a docker image which has the docker command line tools installed. The configuration in `docker-compose-config.yml` mounts the docker socket, in order that the CLSI container can talk to the docker host it is running in. This allows it to spin up `sibling containers` running an image with a TeX distribution installed to perform the actual compiles. The CLSI can be configured through the following environment variables: @@ -21,9 +21,9 @@ The CLSI can be configured through the following environment variables: * `SYNCTEX_BIN_HOST_PATH` - Path to SyncTeX binary * `COMPILES_HOST_DIR` - Working directory for LaTeX compiles * `SQLITE_PATH` - Path to SQLite database - * `TEXLIVE_IMAGE` - The TEXLIVE docker image to use for sibling containers, e.g. `gcr.io/overleaf-ops/texlive-full:2017.1` + * `TEXLIVE_IMAGE` - The docker image with a TeX distribution to use for sibling containers * `TEXLIVE_IMAGE_USER` - When using sibling containers, the user to run as in the TEXLIVE image. Defaults to `tex` - * `TEX_LIVE_IMAGE_NAME_OVERRIDE` - The name of the registry for the docker image e.g. `gcr.io/overleaf-ops` + * `TEX_LIVE_IMAGE_NAME_OVERRIDE` - The name of the registry for the docker image * `FILESTORE_DOMAIN_OVERRIDE` - The url for the filestore service e.g.`http://$FILESTORE_HOST:3009` * `STATSD_HOST` - The address of the Statsd service (used by the metrics module) * `LISTEN_ADDRESS` - The address for the RESTful service to listen on. Set to `0.0.0.0` to listen on all network interfaces From a78a2b89999a62abb342871e89aa9fc49921e13d Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Wed, 7 Nov 2018 08:29:34 +0000 Subject: [PATCH 456/754] First attempt to use my stackdriver branch --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index f2183ef9bc..b646f7be03 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -31,7 +31,7 @@ "lockfile": "^1.0.3", "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", "lynx": "0.0.11", - "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.8.1", + "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#csh-stackdriver", "mkdirp": "0.3.5", "mysql": "2.6.2", "request": "^2.21.0", From 887a19ca80ba5f9f77eb1385446e5e1c686237e9 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Thu, 22 Nov 2018 09:13:23 +0000 Subject: [PATCH 457/754] Expand CLSI to Common LaTeX Service Interface on first use --- services/clsi/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/README.md b/services/clsi/README.md index e074204c36..ba0a63e4ef 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -5,7 +5,7 @@ A web api for compiling LaTeX documents in the cloud [![Build Status](https://travis-ci.org/sharelatex/clsi-sharelatex.png?branch=master)](https://travis-ci.org/sharelatex/clsi-sharelatex) -The CLSI provide a RESTful interface to traditional LaTeX tools (or, more generally, any command line tool for composing marked-up documents into a display format such as PDF or HTML). The CLSI listens on the following ports by default: +The Common LaTeX Service Interface (CLSI) provides a RESTful interface to traditional LaTeX tools (or, more generally, any command line tool for composing marked-up documents into a display format such as PDF or HTML). The CLSI listens on the following ports by default: * TCP/3009 - the RESTful interface * TCP/3048 - reports load information From c67e88fcb3939a1237207603bcad95fda22c2d41 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 23 Nov 2018 14:52:13 +0000 Subject: [PATCH 458/754] have failed compiles warn rather than be an error --- services/clsi/app/coffee/CompileController.coffee | 4 ++-- services/clsi/test/unit/coffee/CompileControllerTests.coffee | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 5d1ee2e764..0a51c94759 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -33,7 +33,7 @@ module.exports = CompileController = else status = "error" code = 500 - logger.error err: error, project_id: request.project_id, "error running compile" + logger.warn err: error, project_id: request.project_id, "error running compile" else status = "failure" @@ -42,7 +42,7 @@ module.exports = CompileController = status = "success" if status == "failure" - logger.err project_id: request.project_id, outputFiles:outputFiles, "project failed to compile successfully, no output.pdf generated" + logger.warn project_id: request.project_id, outputFiles:outputFiles, "project failed to compile successfully, no output.pdf generated" # log an error if any core files are found for file in outputFiles diff --git a/services/clsi/test/unit/coffee/CompileControllerTests.coffee b/services/clsi/test/unit/coffee/CompileControllerTests.coffee index f0269ee3cb..529c732231 100644 --- a/services/clsi/test/unit/coffee/CompileControllerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileControllerTests.coffee @@ -14,7 +14,7 @@ describe "CompileController", -> clsi: url: "http://clsi.example.com" "./ProjectPersistenceManager": @ProjectPersistenceManager = {} - "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub(), err:sinon.stub() } + "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub(), err:sinon.stub(), warn: sinon.stub()} @Settings.externalUrl = "http://www.example.com" @req = {} @res = {} From 12192464cbd841fafffad5ba778669797afcccc8 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Thu, 29 Nov 2018 10:10:48 +0000 Subject: [PATCH 459/754] Use v1.9.0 of metrics to get Prometheus support --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index b646f7be03..1bacde3da8 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -31,7 +31,7 @@ "lockfile": "^1.0.3", "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", "lynx": "0.0.11", - "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#csh-stackdriver", + "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#1.9.0", "mkdirp": "0.3.5", "mysql": "2.6.2", "request": "^2.21.0", From 8d1d479980c1abba5f25eab52810864915f6a734 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Thu, 29 Nov 2018 10:24:25 +0000 Subject: [PATCH 460/754] Bump metrics to v2.0.3 - specify tag correctly this time --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index 1bacde3da8..3de282ab97 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -31,7 +31,7 @@ "lockfile": "^1.0.3", "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", "lynx": "0.0.11", - "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#1.9.0", + "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v2.0.3", "mkdirp": "0.3.5", "mysql": "2.6.2", "request": "^2.21.0", From ae92438cc47aec9a2a8fb0f863eefec29e46386d Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Thu, 29 Nov 2018 14:30:00 +0000 Subject: [PATCH 461/754] Inject metrics --- services/clsi/app.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 367174f387..0db79dd563 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -16,6 +16,7 @@ Metrics = require "metrics-sharelatex" Metrics.initialize("clsi") Metrics.open_sockets.monitor(logger) Metrics.memory.monitor(logger) +Metrics.injectMetricsRoute(app) ProjectPersistenceManager = require "./app/js/ProjectPersistenceManager" OutputCacheManager = require "./app/js/OutputCacheManager" From 1783d9d74b1e38a60a35a84f0273531b3702addd Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Thu, 29 Nov 2018 15:49:12 +0000 Subject: [PATCH 462/754] Inject routes after app defined --- services/clsi/app.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 0db79dd563..10bceee5d5 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -16,7 +16,6 @@ Metrics = require "metrics-sharelatex" Metrics.initialize("clsi") Metrics.open_sockets.monitor(logger) Metrics.memory.monitor(logger) -Metrics.injectMetricsRoute(app) ProjectPersistenceManager = require "./app/js/ProjectPersistenceManager" OutputCacheManager = require "./app/js/OutputCacheManager" @@ -27,6 +26,7 @@ express = require "express" bodyParser = require "body-parser" app = express() +Metrics.injectMetricsRoute(app) app.use Metrics.http.monitor(logger) # Compile requests can take longer than the default two From 46af2a2a129229e49948ac2021cd485539d9d4e8 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Mon, 3 Dec 2018 15:10:39 +0000 Subject: [PATCH 463/754] Bump metrics to 2.0.4 --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index 3de282ab97..e565b38bee 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -31,7 +31,7 @@ "lockfile": "^1.0.3", "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", "lynx": "0.0.11", - "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v2.0.3", + "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v2.0.4", "mkdirp": "0.3.5", "mysql": "2.6.2", "request": "^2.21.0", From 7c577eb6ebe6b57f4f3ea3b6b45464ef34d5acc5 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Tue, 11 Dec 2018 12:11:53 +0000 Subject: [PATCH 464/754] Use metrics which labels host in timing --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index e565b38bee..f0589b8c90 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -31,7 +31,7 @@ "lockfile": "^1.0.3", "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", "lynx": "0.0.11", - "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v2.0.4", + "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#d96892cc1741dd6d59e83c924a1458e4929d39b7", "mkdirp": "0.3.5", "mysql": "2.6.2", "request": "^2.21.0", From 2559b63f04fcb1a1ec026efec29ca6410ee5cdf3 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Thu, 13 Dec 2018 14:15:19 +0000 Subject: [PATCH 465/754] Bump metrics-sharelatex.git to v2.0.11 --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index f0589b8c90..84a3153a80 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -31,7 +31,7 @@ "lockfile": "^1.0.3", "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", "lynx": "0.0.11", - "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#d96892cc1741dd6d59e83c924a1458e4929d39b7", + "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v2.0.11", "mkdirp": "0.3.5", "mysql": "2.6.2", "request": "^2.21.0", From 533ec9128e9388790ceee8107f908ca10efc319d Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Thu, 13 Dec 2018 14:21:32 +0000 Subject: [PATCH 466/754] Bump metrics-sharelatex to v2.0.12 --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index 84a3153a80..49f5873ed7 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -31,7 +31,7 @@ "lockfile": "^1.0.3", "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", "lynx": "0.0.11", - "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v2.0.11", + "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v2.0.12", "mkdirp": "0.3.5", "mysql": "2.6.2", "request": "^2.21.0", From f9b0ac0e80eb50efc16b66c880ce277c9cd42fd5 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Thu, 13 Dec 2018 14:24:44 +0000 Subject: [PATCH 467/754] Initialise metrics at begining of app --- services/clsi/app.coffee | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 10bceee5d5..d9faf9e379 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -1,3 +1,8 @@ +Metrics = require "metrics-sharelatex" +Metrics.initialize("clsi") +Metrics.open_sockets.monitor(logger) +Metrics.memory.monitor(logger) + CompileController = require "./app/js/CompileController" Settings = require "settings-sharelatex" logger = require "logger-sharelatex" @@ -12,11 +17,6 @@ Errors = require './app/js/Errors' Path = require "path" fs = require "fs" -Metrics = require "metrics-sharelatex" -Metrics.initialize("clsi") -Metrics.open_sockets.monitor(logger) -Metrics.memory.monitor(logger) - ProjectPersistenceManager = require "./app/js/ProjectPersistenceManager" OutputCacheManager = require "./app/js/OutputCacheManager" From b49de8e57c43c33205809f6a7ffc06fc4dfc7320 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Thu, 13 Dec 2018 14:45:40 +0000 Subject: [PATCH 468/754] Bump buildscript to 1.1.10 --- services/clsi/.github/ISSUE_TEMPLATE.md | 38 ++++++++++++++++ .../clsi/.github/PULL_REQUEST_TEMPLATE.md | 45 +++++++++++++++++++ services/clsi/Dockerfile | 2 +- services/clsi/Makefile | 2 +- services/clsi/buildscript.txt | 9 ++++ services/clsi/docker-compose.ci.yml | 2 +- services/clsi/docker-compose.yml | 2 +- services/clsi/package.json | 2 +- 8 files changed, 97 insertions(+), 5 deletions(-) create mode 100644 services/clsi/.github/ISSUE_TEMPLATE.md create mode 100644 services/clsi/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 services/clsi/buildscript.txt diff --git a/services/clsi/.github/ISSUE_TEMPLATE.md b/services/clsi/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..e0093aa90c --- /dev/null +++ b/services/clsi/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,38 @@ +<!-- BUG REPORT TEMPLATE --> + +## Steps to Reproduce +<!-- Describe the steps leading up to when / where you found the bug. --> +<!-- Screenshots may be helpful here. --> + +1. +2. +3. + +## Expected Behaviour +<!-- What should have happened when you completed the steps above? --> + +## Observed Behaviour +<!-- What actually happened when you completed the steps above? --> +<!-- Screenshots may be helpful here. --> + +## Context +<!-- How has this issue affected you? What were you trying to accomplish? --> + +## Technical Info +<!-- Provide any technical details that may be applicable (or N/A if not applicable). --> + +* URL: +* Browser Name and version: +* Operating System and version (desktop or mobile): +* Signed in as: +* Project and/or file: + +## Analysis +<!--- Optionally, document investigation of / suggest a fix for the bug, e.g. 'comes from this line / commit' --> + +## Who Needs to Know? +<!-- If you want to bring this to the attention of particular people, @-mention them below. --> +<!-- If a user reported this bug and should be notified when it is fixed, provide the Front conversation link. --> + +- +- diff --git a/services/clsi/.github/PULL_REQUEST_TEMPLATE.md b/services/clsi/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..ed25ee83c1 --- /dev/null +++ b/services/clsi/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,45 @@ +<!-- Please review https://github.com/overleaf/write_latex/blob/master/.github/CONTRIBUTING.md for guidance on what is expected in each section. --> + +### Description + + + +#### Screenshots + + + +#### Related Issues / PRs + + + +### Review + + + +#### Potential Impact + + + +#### Manual Testing Performed + +- [ ] +- [ ] + +#### Accessibility + + + +### Deployment + + + +#### Deployment Checklist + +- [ ] Update documentation not included in the PR (if any) +- [ ] + +#### Metrics and Monitoring + + + +#### Who Needs to Know? diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 1ccc689d42..53f4b944d3 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -20,4 +20,4 @@ WORKDIR /app RUN chmod 0755 ./install_deps.sh && ./install_deps.sh ENTRYPOINT ["/bin/sh", "entrypoint.sh"] -CMD ["node","app.js"] +CMD ["node", "--expose-gc", "app.js"] diff --git a/services/clsi/Makefile b/services/clsi/Makefile index b1ce2934af..7558b6e5e6 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.9 +# Version: 1.1.10 BUILD_NUMBER ?= local BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt new file mode 100644 index 0000000000..e4a8e46fc1 --- /dev/null +++ b/services/clsi/buildscript.txt @@ -0,0 +1,9 @@ +--script-version=1.1.10 +clsi +--node-version=6.14.1 +--acceptance-creds=None +--language=coffeescript +--dependencies=mongo,redis +--docker-repos=gcr.io/overleaf-ops +--kube=false +--build-target=docker diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index f6c8a27fd0..57108f5070 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.9 +# Version: 1.1.10 version: "2" diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 371e6e768d..61bb84209f 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.9 +# Version: 1.1.10 version: "2" diff --git a/services/clsi/package.json b/services/clsi/package.json index 49f5873ed7..0bc083c540 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -7,7 +7,7 @@ "url": "https://github.com/sharelatex/clsi-sharelatex.git" }, "scripts": { - "compile:app": "([ -e app/coffee ] && coffee $COFFEE_OPTIONS -o app/js -c app/coffee || echo 'No CoffeeScript folder to compile') && ( [ -e app.coffee ] && coffee $COFFEE_OPTIONS -c app.coffee || echo 'No CoffeeScript app to compile')", + "compile:app": "([ -e app/coffee ] && coffee -m $COFFEE_OPTIONS -o app/js -c app/coffee || echo 'No CoffeeScript folder to compile') && ( [ -e app.coffee ] && coffee -m $COFFEE_OPTIONS -c app.coffee || echo 'No CoffeeScript app to compile')", "start": "npm run compile:app && node $NODE_APP_OPTIONS app.js", "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 30000 --exit $@ test/acceptance/js", "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- --grep=$MOCHA_GREP", From 45a7adab6e7de9523469e074564368fd06e2a22a Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Thu, 13 Dec 2018 17:33:45 +0000 Subject: [PATCH 469/754] Revert "Initialise metrics at begining of app" This reverts commit 855f26c5205039e96cfea8d224eff5ebb743e046. --- services/clsi/app.coffee | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index d9faf9e379..10bceee5d5 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -1,8 +1,3 @@ -Metrics = require "metrics-sharelatex" -Metrics.initialize("clsi") -Metrics.open_sockets.monitor(logger) -Metrics.memory.monitor(logger) - CompileController = require "./app/js/CompileController" Settings = require "settings-sharelatex" logger = require "logger-sharelatex" @@ -17,6 +12,11 @@ Errors = require './app/js/Errors' Path = require "path" fs = require "fs" +Metrics = require "metrics-sharelatex" +Metrics.initialize("clsi") +Metrics.open_sockets.monitor(logger) +Metrics.memory.monitor(logger) + ProjectPersistenceManager = require "./app/js/ProjectPersistenceManager" OutputCacheManager = require "./app/js/OutputCacheManager" From 392884356e6eeae06b02925c2782f225c6b1e957 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Thu, 13 Dec 2018 17:37:16 +0000 Subject: [PATCH 470/754] Revert "Bump buildscript to 1.1.10" This reverts commit 38874f9169abfb1bcc7297988abe22c012a4c107. --- services/clsi/.github/ISSUE_TEMPLATE.md | 38 ---------------- .../clsi/.github/PULL_REQUEST_TEMPLATE.md | 45 ------------------- services/clsi/Dockerfile | 2 +- services/clsi/Makefile | 2 +- services/clsi/buildscript.txt | 9 ---- services/clsi/docker-compose.ci.yml | 2 +- services/clsi/docker-compose.yml | 2 +- services/clsi/package.json | 2 +- 8 files changed, 5 insertions(+), 97 deletions(-) delete mode 100644 services/clsi/.github/ISSUE_TEMPLATE.md delete mode 100644 services/clsi/.github/PULL_REQUEST_TEMPLATE.md delete mode 100644 services/clsi/buildscript.txt diff --git a/services/clsi/.github/ISSUE_TEMPLATE.md b/services/clsi/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index e0093aa90c..0000000000 --- a/services/clsi/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,38 +0,0 @@ -<!-- BUG REPORT TEMPLATE --> - -## Steps to Reproduce -<!-- Describe the steps leading up to when / where you found the bug. --> -<!-- Screenshots may be helpful here. --> - -1. -2. -3. - -## Expected Behaviour -<!-- What should have happened when you completed the steps above? --> - -## Observed Behaviour -<!-- What actually happened when you completed the steps above? --> -<!-- Screenshots may be helpful here. --> - -## Context -<!-- How has this issue affected you? What were you trying to accomplish? --> - -## Technical Info -<!-- Provide any technical details that may be applicable (or N/A if not applicable). --> - -* URL: -* Browser Name and version: -* Operating System and version (desktop or mobile): -* Signed in as: -* Project and/or file: - -## Analysis -<!--- Optionally, document investigation of / suggest a fix for the bug, e.g. 'comes from this line / commit' --> - -## Who Needs to Know? -<!-- If you want to bring this to the attention of particular people, @-mention them below. --> -<!-- If a user reported this bug and should be notified when it is fixed, provide the Front conversation link. --> - -- -- diff --git a/services/clsi/.github/PULL_REQUEST_TEMPLATE.md b/services/clsi/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index ed25ee83c1..0000000000 --- a/services/clsi/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,45 +0,0 @@ -<!-- Please review https://github.com/overleaf/write_latex/blob/master/.github/CONTRIBUTING.md for guidance on what is expected in each section. --> - -### Description - - - -#### Screenshots - - - -#### Related Issues / PRs - - - -### Review - - - -#### Potential Impact - - - -#### Manual Testing Performed - -- [ ] -- [ ] - -#### Accessibility - - - -### Deployment - - - -#### Deployment Checklist - -- [ ] Update documentation not included in the PR (if any) -- [ ] - -#### Metrics and Monitoring - - - -#### Who Needs to Know? diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 53f4b944d3..1ccc689d42 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -20,4 +20,4 @@ WORKDIR /app RUN chmod 0755 ./install_deps.sh && ./install_deps.sh ENTRYPOINT ["/bin/sh", "entrypoint.sh"] -CMD ["node", "--expose-gc", "app.js"] +CMD ["node","app.js"] diff --git a/services/clsi/Makefile b/services/clsi/Makefile index 7558b6e5e6..b1ce2934af 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.10 +# Version: 1.1.9 BUILD_NUMBER ?= local BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt deleted file mode 100644 index e4a8e46fc1..0000000000 --- a/services/clsi/buildscript.txt +++ /dev/null @@ -1,9 +0,0 @@ ---script-version=1.1.10 -clsi ---node-version=6.14.1 ---acceptance-creds=None ---language=coffeescript ---dependencies=mongo,redis ---docker-repos=gcr.io/overleaf-ops ---kube=false ---build-target=docker diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 57108f5070..f6c8a27fd0 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.10 +# Version: 1.1.9 version: "2" diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 61bb84209f..371e6e768d 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.10 +# Version: 1.1.9 version: "2" diff --git a/services/clsi/package.json b/services/clsi/package.json index 0bc083c540..49f5873ed7 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -7,7 +7,7 @@ "url": "https://github.com/sharelatex/clsi-sharelatex.git" }, "scripts": { - "compile:app": "([ -e app/coffee ] && coffee -m $COFFEE_OPTIONS -o app/js -c app/coffee || echo 'No CoffeeScript folder to compile') && ( [ -e app.coffee ] && coffee -m $COFFEE_OPTIONS -c app.coffee || echo 'No CoffeeScript app to compile')", + "compile:app": "([ -e app/coffee ] && coffee $COFFEE_OPTIONS -o app/js -c app/coffee || echo 'No CoffeeScript folder to compile') && ( [ -e app.coffee ] && coffee $COFFEE_OPTIONS -c app.coffee || echo 'No CoffeeScript app to compile')", "start": "npm run compile:app && node $NODE_APP_OPTIONS app.js", "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 30000 --exit $@ test/acceptance/js", "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- --grep=$MOCHA_GREP", From dd3774b7a9496f30ac5de749c6488970cab7fa7d Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Mon, 17 Dec 2018 15:29:56 +0000 Subject: [PATCH 471/754] Bump node to 6.15.1 --- services/clsi/.nvmrc | 2 +- services/clsi/Dockerfile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/services/clsi/.nvmrc b/services/clsi/.nvmrc index bbf0c5a541..d36e8d82f3 100644 --- a/services/clsi/.nvmrc +++ b/services/clsi/.nvmrc @@ -1 +1 @@ -6.14.1 +6.15.1 diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 1ccc689d42..ea550b2892 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -1,4 +1,4 @@ -FROM node:6.14.1 as app +FROM node:6.15.1 as app WORKDIR /app @@ -12,7 +12,7 @@ COPY . /app RUN npm run compile:all -FROM node:6.14.1 +FROM node:6.15.1 COPY --from=app /app /app From b57342128de86c4b20bb772df2228b68e81e7cb2 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Mon, 17 Dec 2018 15:31:45 +0000 Subject: [PATCH 472/754] package-lock not supported until npm 5 --- services/clsi/package-lock.json | 3002 ------------------------------- 1 file changed, 3002 deletions(-) delete mode 100644 services/clsi/package-lock.json diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json deleted file mode 100644 index d58c1d73b9..0000000000 --- a/services/clsi/package-lock.json +++ /dev/null @@ -1,3002 +0,0 @@ -{ - "name": "node-clsi", - "version": "0.1.4", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@types/geojson": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.6.tgz", - "integrity": "sha512-Xqg/lIZMrUd0VRmSRbCAewtwGZiAk3mEUDvV4op1tGl+LvyPcb/MIOSxTl9z+9+J+R4/vpjiCAT4xeKzH9ji1w==" - }, - "@types/node": { - "version": "10.5.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.5.4.tgz", - "integrity": "sha512-8TqvB0ReZWwtcd3LXq3YSrBoLyXFgBX/sBZfGye9+YS8zH7/g+i6QRIuiDmwBoTzcQ/pk89nZYTYU4c5akKkzw==" - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg=" - }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" - } - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-0.2.0.tgz", - "integrity": "sha1-NZq0sV3NZLptdHNLcsNjYKmvLBk=", - "dev": true - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=" - }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.6" - } - }, - "argparse": { - "version": "0.1.16", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", - "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", - "dev": true, - "requires": { - "underscore": "1.7.0", - "underscore.string": "2.4.0" - }, - "dependencies": { - "underscore": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", - "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", - "dev": true - }, - "underscore.string": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", - "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=", - "dev": true - } - } - }, - "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "assertion-error": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", - "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", - "dev": true - }, - "async": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/async/-/async-0.2.9.tgz", - "integrity": "sha1-32MGD789Myhqdqr21Vophtn/hhk=" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - } - }, - "bignumber.js": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.7.tgz", - "integrity": "sha1-husHB89qURCQnSPm6nQ0wU9QDxw=" - }, - "bl": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", - "requires": { - "readable-stream": "2.3.6", - "safe-buffer": "5.1.2" - } - }, - "block-stream": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", - "requires": { - "inherits": "2.0.3" - } - }, - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" - }, - "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", - "requires": { - "bytes": "3.0.0", - "content-type": "1.0.4", - "debug": "2.6.9", - "depd": "1.1.2", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "on-finished": "2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "1.6.15" - }, - "dependencies": { - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": "1.4.0" - }, - "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" - } - } - }, - "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "mime-db": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" - }, - "mime-types": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", - "requires": { - "mime-db": "1.30.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, - "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" - }, - "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "unpipe": "1.0.0" - } - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" - }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" - }, - "type-is": { - "version": "1.6.15", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", - "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", - "requires": { - "media-typer": "0.3.0", - "mime-types": "2.1.17" - } - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - } - } - }, - "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "requires": { - "hoek": "2.16.3" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "browser-stdout": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", - "dev": true - }, - "buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "requires": { - "buffer-alloc-unsafe": "1.1.0", - "buffer-fill": "1.0.0" - } - }, - "buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" - }, - "buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" - }, - "bunyan": { - "version": "0.22.3", - "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-0.22.3.tgz", - "integrity": "sha1-ehncG0yMZF90AkGnQPIkUUfGfsI=", - "dev": true, - "requires": { - "mv": "2.1.1" - } - }, - "buster-core": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/buster-core/-/buster-core-0.6.4.tgz", - "integrity": "sha1-J79rrWdCROpyDzEdkAoMoct4YFA=", - "dev": true - }, - "buster-format": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/buster-format/-/buster-format-0.5.6.tgz", - "integrity": "sha1-K4bDIuz14bCubm55Bev884fSq5U=", - "dev": true, - "requires": { - "buster-core": "0.6.4" - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "chai": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-1.8.1.tgz", - "integrity": "sha1-zHeGbV5+vKK9dRRLHtw3Coh4X3I=", - "dev": true, - "requires": { - "assertion-error": "1.0.0", - "deep-eql": "0.1.3" - } - }, - "chalk": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.3.0.tgz", - "integrity": "sha1-HJhDdzfxGZ68wdTEj9Qbn5yOjyM=", - "dev": true, - "requires": { - "ansi-styles": "0.2.0", - "has-color": "0.1.7" - } - }, - "chownr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", - "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=" - }, - "cls-bluebird": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz", - "integrity": "sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4=", - "requires": { - "is-bluebird": "1.0.2", - "shimmer": "1.2.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, - "coffee-script": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", - "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=" - }, - "colors": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", - "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", - "dev": true - }, - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "requires": { - "delayed-stream": "1.0.0" - } - }, - "commander": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz", - "integrity": "sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg=" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "concat-stream": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", - "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.0.6", - "typedarray": "0.0.6" - }, - "dependencies": { - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" - }, - "readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "0.10.31", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - } - } - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "cryptiles": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", - "requires": { - "boom": "2.10.1" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "1.0.0" - } - }, - "dateformat": { - "version": "1.0.2-1.2.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", - "integrity": "sha1-sCIMAt6YYXQztyhRz0fePfLNvuk=", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", - "requires": { - "ms": "2.0.0" - } - }, - "deep-eql": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", - "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", - "dev": true, - "requires": { - "type-detect": "0.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" - }, - "diff": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", - "integrity": "sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ=" - }, - "docker-modem": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-1.0.6.tgz", - "integrity": "sha512-kDwWa5QaiVMB8Orbb7nXdGdwEZHKfEm7iPwglXe1KorImMpmGNlhC7A5LG0p8rrCcz1J4kJhq/o63lFjDdj8rQ==", - "requires": { - "debug": "3.1.0", - "JSONStream": "1.3.2", - "readable-stream": "1.0.34", - "split-ca": "1.0.1" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - } - } - }, - "dockerode": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-2.5.5.tgz", - "integrity": "sha512-H3HX18xKmy51wqpPHvGDwPOotJMy9l/AWfiaVu4imrgBGr384rINEB2FwTwoYU++krkZjseVYyiVK8CnRz2tkw==", - "requires": { - "concat-stream": "1.5.2", - "docker-modem": "1.0.6", - "tar-fs": "1.12.0" - } - }, - "dottie": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.0.tgz", - "integrity": "sha1-2hkZgci41xPKARXViYzzl8Lw3dA=" - }, - "dtrace-provider": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.6.0.tgz", - "integrity": "sha1-CweNVReTfYcxAUUtkUZzdVe3XlE=", - "optional": true, - "requires": { - "nan": "2.10.0" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "optional": true, - "requires": { - "jsbn": "0.1.1", - "safer-buffer": "2.1.2" - } - }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "requires": { - "once": "1.4.0" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "esprima": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", - "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", - "dev": true - }, - "eventemitter2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", - "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", - "dev": true - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "express": { - "version": "4.16.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", - "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", - "requires": { - "accepts": "1.3.4", - "array-flatten": "1.1.1", - "body-parser": "1.18.2", - "content-disposition": "0.5.2", - "content-type": "1.0.4", - "cookie": "0.3.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "1.1.2", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", - "finalhandler": "1.1.0", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "1.1.2", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "path-to-regexp": "0.1.7", - "proxy-addr": "2.0.2", - "qs": "6.5.1", - "range-parser": "1.2.0", - "safe-buffer": "5.1.1", - "send": "0.16.1", - "serve-static": "1.13.1", - "setprototypeof": "1.1.0", - "statuses": "1.3.1", - "type-is": "1.6.15", - "utils-merge": "1.0.1", - "vary": "1.1.2" - }, - "dependencies": { - "accepts": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", - "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", - "requires": { - "mime-types": "2.1.17", - "negotiator": "0.6.1" - } - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "finalhandler": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", - "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", - "requires": { - "debug": "2.6.9", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "statuses": "1.3.1", - "unpipe": "1.0.0" - } - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": "1.3.1" - }, - "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" - } - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ipaddr.js": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz", - "integrity": "sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A=" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" - }, - "mime-db": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" - }, - "mime-types": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", - "requires": { - "mime-db": "1.30.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "proxy-addr": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", - "integrity": "sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=", - "requires": { - "forwarded": "0.1.2", - "ipaddr.js": "1.5.2" - } - }, - "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" - }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" - }, - "send": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", - "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", - "requires": { - "debug": "2.6.9", - "depd": "1.1.2", - "destroy": "1.0.4", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", - "fresh": "0.5.2", - "http-errors": "1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "2.3.0", - "range-parser": "1.2.0", - "statuses": "1.3.1" - } - }, - "serve-static": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", - "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", - "requires": { - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "parseurl": "1.3.2", - "send": "0.16.1" - } - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=" - }, - "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" - }, - "type-is": { - "version": "1.6.15", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", - "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", - "requires": { - "media-typer": "0.3.0", - "mime-types": "2.1.17" - } - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - } - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" - }, - "findup-sync": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", - "integrity": "sha1-fz56l7gjksZTvwZYm9hRkOk8NoM=", - "dev": true, - "requires": { - "glob": "3.2.11", - "lodash": "2.4.2" - }, - "dependencies": { - "glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", - "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "minimatch": "0.3.0" - } - }, - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", - "dev": true - }, - "minimatch": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", - "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", - "dev": true, - "requires": { - "lru-cache": "2.7.3", - "sigmund": "1.0.1" - } - } - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.19" - } - }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" - }, - "fs-extra": { - "version": "0.16.5", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.16.5.tgz", - "integrity": "sha1-GtZh+myGyWCM0bSe/G/Og0k5p1A=", - "requires": { - "graceful-fs": "3.0.11", - "jsonfile": "2.4.0", - "rimraf": "2.6.2" - } - }, - "fs-minipass": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", - "integrity": "sha1-BsJ3IYRU7CiN93raVKA7hwKqy50=", - "requires": { - "minipass": "2.3.3" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "fstream": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", - "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", - "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.2" - }, - "dependencies": { - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - } - } - }, - "fstream-ignore": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", - "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", - "requires": { - "fstream": "1.0.11", - "inherits": "2.0.3", - "minimatch": "3.0.4" - } - }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.3" - } - }, - "generic-pool": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.4.2.tgz", - "integrity": "sha512-H7cUpwCQSiJmAHM4c/aFu6fUfrhWXW1ncyh8ftxEPMu6AiYkHw9K8br720TGPZJbk5eOH2bynjZD1yPvdDAmag==" - }, - "getobject": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", - "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "1.0.0" - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "graceful-fs": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", - "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", - "requires": { - "natives": "1.1.4" - } - }, - "growl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", - "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=" - }, - "grunt": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", - "integrity": "sha1-VpN81RlDJK3/bSB2MYMqnWuk5/A=", - "dev": true, - "requires": { - "async": "0.1.22", - "coffee-script": "1.3.3", - "colors": "0.6.2", - "dateformat": "1.0.2-1.2.3", - "eventemitter2": "0.4.14", - "exit": "0.1.2", - "findup-sync": "0.1.3", - "getobject": "0.1.0", - "glob": "3.1.21", - "grunt-legacy-log": "0.1.3", - "grunt-legacy-util": "0.2.0", - "hooker": "0.2.3", - "iconv-lite": "0.2.11", - "js-yaml": "2.0.5", - "lodash": "0.9.2", - "minimatch": "0.2.14", - "nopt": "1.0.10", - "rimraf": "2.2.8", - "underscore.string": "2.2.1", - "which": "1.0.9" - }, - "dependencies": { - "async": { - "version": "0.1.22", - "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", - "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=", - "dev": true - }, - "coffee-script": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz", - "integrity": "sha1-FQ1rTLUiiUNp7+1qIQHCC8f0pPQ=", - "dev": true - }, - "glob": { - "version": "3.1.21", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", - "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", - "dev": true, - "requires": { - "graceful-fs": "1.2.3", - "inherits": "1.0.2", - "minimatch": "0.2.14" - } - }, - "graceful-fs": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", - "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", - "dev": true - }, - "iconv-lite": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", - "integrity": "sha1-HOYKOleGSiktEyH/RgnKS7llrcg=", - "dev": true - }, - "inherits": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", - "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", - "dev": true - }, - "lodash": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", - "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=", - "dev": true - }, - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "dev": true, - "requires": { - "lru-cache": "2.7.3", - "sigmund": "1.0.1" - } - }, - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "dev": true, - "requires": { - "abbrev": "1.1.1" - } - }, - "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", - "dev": true - } - } - }, - "grunt-bunyan": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/grunt-bunyan/-/grunt-bunyan-0.5.0.tgz", - "integrity": "sha1-aCnXbgGZQ9owQTk2MaNuKsgpsWw=", - "dev": true, - "requires": { - "lodash": "2.4.2" - }, - "dependencies": { - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", - "dev": true - } - } - }, - "grunt-contrib-clean": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-0.5.0.tgz", - "integrity": "sha1-9T397ghJsce0Dp67umn0jExgecU=", - "dev": true, - "requires": { - "rimraf": "2.2.8" - }, - "dependencies": { - "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", - "dev": true - } - } - }, - "grunt-contrib-coffee": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-coffee/-/grunt-contrib-coffee-0.7.0.tgz", - "integrity": "sha1-ixIme3TnM4sfKcW4txj7n4mYLxM=", - "dev": true, - "requires": { - "coffee-script": "1.6.3" - }, - "dependencies": { - "coffee-script": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.3.tgz", - "integrity": "sha1-Y1XTLPGwTN/2tITl5xF4Ky8MOb4=", - "dev": true - } - } - }, - "grunt-execute": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/grunt-execute/-/grunt-execute-0.1.5.tgz", - "integrity": "sha1-yX64lDYS/vu3L749Mu+VIzxfouk=", - "dev": true - }, - "grunt-legacy-log": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", - "integrity": "sha1-7ClCboAwIa9ZAp+H0vnNczWgVTE=", - "dev": true, - "requires": { - "colors": "0.6.2", - "grunt-legacy-log-utils": "0.1.1", - "hooker": "0.2.3", - "lodash": "2.4.2", - "underscore.string": "2.3.3" - }, - "dependencies": { - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", - "dev": true - }, - "underscore.string": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", - "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", - "dev": true - } - } - }, - "grunt-legacy-log-utils": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", - "integrity": "sha1-wHBrndkGThFvNvI/5OawSGcsD34=", - "dev": true, - "requires": { - "colors": "0.6.2", - "lodash": "2.4.2", - "underscore.string": "2.3.3" - }, - "dependencies": { - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", - "dev": true - }, - "underscore.string": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", - "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", - "dev": true - } - } - }, - "grunt-legacy-util": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", - "integrity": "sha1-kzJIhNv343qf98Am3/RR2UqeVUs=", - "dev": true, - "requires": { - "async": "0.1.22", - "exit": "0.1.2", - "getobject": "0.1.0", - "hooker": "0.2.3", - "lodash": "0.9.2", - "underscore.string": "2.2.1", - "which": "1.0.9" - }, - "dependencies": { - "async": { - "version": "0.1.22", - "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", - "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=", - "dev": true - }, - "lodash": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", - "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=", - "dev": true - } - } - }, - "grunt-mkdir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-mkdir/-/grunt-mkdir-1.0.0.tgz", - "integrity": "sha1-c+GiasJKCFljY/TdlUsNMkheWOk=" - }, - "grunt-mocha-test": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/grunt-mocha-test/-/grunt-mocha-test-0.8.2.tgz", - "integrity": "sha1-emGEuYhg0Phb3qrWvqob199bvus=", - "dev": true, - "requires": { - "mocha": "1.14.0" - }, - "dependencies": { - "glob": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", - "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", - "dev": true, - "requires": { - "graceful-fs": "2.0.3", - "inherits": "2.0.3", - "minimatch": "0.2.14" - } - }, - "graceful-fs": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", - "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=", - "dev": true - }, - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "dev": true, - "requires": { - "lru-cache": "2.7.3", - "sigmund": "1.0.1" - } - }, - "mocha": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.14.0.tgz", - "integrity": "sha1-cT223FAAGRqdA1gZXQkIeQ7LYVc=", - "dev": true, - "requires": { - "commander": "2.0.0", - "debug": "2.6.9", - "diff": "1.0.7", - "glob": "3.2.3", - "growl": "1.7.0", - "jade": "0.26.3", - "mkdirp": "0.3.5" - } - } - } - }, - "grunt-shell": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/grunt-shell/-/grunt-shell-0.6.4.tgz", - "integrity": "sha1-5KbRuSkSd2/ZOimcX2zGTpUlNlw=", - "dev": true, - "requires": { - "chalk": "0.3.0" - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", - "requires": { - "ajv": "5.5.2", - "har-schema": "2.0.0" - } - }, - "has-color": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", - "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", - "dev": true - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" - }, - "hawk": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", - "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" - } - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, - "heapdump": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/heapdump/-/heapdump-0.3.9.tgz", - "integrity": "sha1-A8dOsN9dZ74Jgug0KbqcnSs7f3g=" - }, - "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" - }, - "hooker": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", - "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", - "dev": true - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.1", - "sshpk": "1.14.2" - } - }, - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "requires": { - "safer-buffer": "2.1.2" - } - }, - "ignore-walk": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", - "integrity": "sha1-qD5i59JyrA47VRqqgoMaGbafgvg=", - "requires": { - "minimatch": "3.0.4" - } - }, - "inflection": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", - "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - }, - "dependencies": { - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - } - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=" - }, - "is-bluebird": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", - "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "jade": { - "version": "0.26.3", - "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", - "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", - "requires": { - "commander": "0.6.1", - "mkdirp": "0.3.0" - }, - "dependencies": { - "commander": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", - "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=" - }, - "mkdirp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", - "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=" - } - } - }, - "js-yaml": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", - "integrity": "sha1-olrmUJmZ6X3yeMZxnaEb0Gh3Q6g=", - "dev": true, - "requires": { - "argparse": "0.1.16", - "esprima": "1.0.4" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" - }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "requires": { - "jsonify": "0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "requires": { - "graceful-fs": "4.1.11" - }, - "dependencies": { - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "optional": true - } - } - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" - }, - "JSONStream": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz", - "integrity": "sha1-wQI3G27Dp887hHygDCC7D85Mbeo=", - "requires": { - "jsonparse": "1.3.1", - "through": "2.3.8" - } - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "lockfile": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz", - "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==", - "requires": { - "signal-exit": "3.0.2" - } - }, - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" - }, - "logger-sharelatex": { - "version": "git+https://github.com/sharelatex/logger-sharelatex.git#9ee7b52eb2bbd8fcbb1e2c708587c1e93fd4c733", - "requires": { - "bunyan": "1.5.1", - "coffee-script": "1.4.0", - "raven": "1.2.1" - }, - "dependencies": { - "bunyan": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.5.1.tgz", - "integrity": "sha1-X259RMQ7lS9WsPQTCeOrEjkbTi0=", - "requires": { - "dtrace-provider": "0.6.0", - "mv": "2.1.1", - "safe-json-stringify": "1.2.0" - } - }, - "coffee-script": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.4.0.tgz", - "integrity": "sha1-XjvIqsJsAajie/EHcixWVfWtfTY=" - } - } - }, - "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" - }, - "lsmod": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz", - "integrity": "sha1-mgD3bco26yP6BTUK/htYXUKZ5ks=" - }, - "lynx": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.0.11.tgz", - "integrity": "sha1-LPoU5EP9LZKlm3efQVZ84cxpZaM=", - "requires": { - "mersenne": "0.0.4", - "statsd-parser": "0.0.4" - } - }, - "mersenne": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz", - "integrity": "sha1-QB/ex+whzbngPNPTAhOY2iGycIU=" - }, - "metrics-sharelatex": { - "version": "git+https://github.com/sharelatex/metrics-sharelatex.git#e5356366b5b83997c8e1645b2e936af453381517", - "requires": { - "coffee-script": "1.6.0", - "lynx": "0.1.1", - "underscore": "1.6.0" - }, - "dependencies": { - "lynx": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.1.1.tgz", - "integrity": "sha1-Mxjc7xaQi4KG6Bisz9sxzXQkj50=", - "requires": { - "mersenne": "0.0.4", - "statsd-parser": "0.0.4" - } - }, - "underscore": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", - "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" - } - } - }, - "mime-db": { - "version": "1.35.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", - "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" - }, - "mime-types": { - "version": "2.1.19", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", - "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", - "requires": { - "mime-db": "1.35.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - }, - "minipass": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.3.tgz", - "integrity": "sha1-p9zIt7gz9dNodZzOVE3MtV9Q8jM=", - "requires": { - "safe-buffer": "5.1.2", - "yallist": "3.0.2" - } - }, - "minizlib": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz", - "integrity": "sha1-EeE2WM5GvDpwomeqxYNZ0eDCnOs=", - "requires": { - "minipass": "2.3.3" - } - }, - "mkdirp": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", - "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=" - }, - "mocha": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz", - "integrity": "sha512-0RVnjg1HJsXY2YFDoTNzcc1NKhYuXKRrBAG2gDygmJJA136Cs2QlRliZG1mA0ap7cuaT30mw16luAeln+4RiNA==", - "dev": true, - "requires": { - "browser-stdout": "1.3.0", - "commander": "2.11.0", - "debug": "3.1.0", - "diff": "3.3.1", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.3", - "he": "1.1.1", - "mkdirp": "0.5.1", - "supports-color": "4.4.0" - }, - "dependencies": { - "commander": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "diff": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", - "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", - "dev": true - }, - "growl": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", - "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - } - } - }, - "moment": { - "version": "2.22.2", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", - "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" - }, - "moment-timezone": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.21.tgz", - "integrity": "sha512-j96bAh4otsgj3lKydm3K7kdtA3iKf2m6MY2iSYCzCm5a1zmHo1g+aK3068dDEeocLZQIS9kU8bsdQHLqEvgW0A==", - "requires": { - "moment": "2.22.2" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "mv": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", - "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", - "optional": true, - "requires": { - "mkdirp": "0.5.1", - "ncp": "2.0.0", - "rimraf": "2.4.5" - }, - "dependencies": { - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "optional": true, - "requires": { - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "rimraf": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", - "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", - "optional": true, - "requires": { - "glob": "6.0.4" - } - } - } - }, - "mysql": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.6.2.tgz", - "integrity": "sha1-k3Gd0yT1fUHET7bX+GDPjmOFk7A=", - "requires": { - "bignumber.js": "2.0.7", - "readable-stream": "1.1.14", - "require-all": "1.0.0" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - } - } - }, - "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" - }, - "natives": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.4.tgz", - "integrity": "sha512-Q29yeg9aFKwhLVdkTAejM/HvYG0Y1Am1+HUkFQGn5k2j8GS+v60TVmZh6nujpEAj/qql+wGUrlryO8bF+b1jEg==" - }, - "ncp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", - "optional": true - }, - "needle": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.1.tgz", - "integrity": "sha1-teMlvTqujCZ4kC+ilvcpRV0dOn0=", - "requires": { - "debug": "2.6.9", - "iconv-lite": "0.4.23", - "sax": "1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz", - "integrity": "sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==", - "requires": { - "detect-libc": "1.0.3", - "mkdirp": "0.5.1", - "needle": "2.2.1", - "nopt": "4.0.1", - "npm-packlist": "1.1.11", - "npmlog": "4.1.2", - "rc": "1.2.8", - "rimraf": "2.6.2", - "semver": "5.5.0", - "tar": "4.4.4" - }, - "dependencies": { - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "requires": { - "glob": "7.1.2" - } - } - } - }, - "nopt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", - "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.5" - } - }, - "npm-bundled": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.3.tgz", - "integrity": "sha1-fnFwPZc68zcKlZG6/jpjrKC+Iwg=" - }, - "npm-packlist": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.11.tgz", - "integrity": "sha1-hOjGg8vnhn00sdNX2JPOKeKKAt4=", - "requires": { - "ignore-walk": "3.0.1", - "npm-bundled": "1.0.3" - } - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=", - "requires": { - "are-we-there-yet": "1.1.5", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1.0.2" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha1-hc36+uso6Gd/QW4odZK18/SepBA=", - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "pump": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", - "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", - "requires": { - "end-of-stream": "1.4.1", - "once": "1.4.0" - } - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "raven": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/raven/-/raven-1.2.1.tgz", - "integrity": "sha1-lJwTTbAooZC3u/j3kKrlQbfAIL0=", - "requires": { - "cookie": "0.3.1", - "json-stringify-safe": "5.0.1", - "lsmod": "1.0.0", - "stack-trace": "0.0.9", - "uuid": "3.0.0" - }, - "dependencies": { - "uuid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz", - "integrity": "sha1-Zyj8BFnEUNeWqZwxg3VpvfZy1yg=" - } - } - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { - "deep-extend": "0.6.0", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - } - } - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" - } - }, - "request": { - "version": "2.87.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", - "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", - "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.7.0", - "caseless": "0.12.0", - "combined-stream": "1.0.6", - "extend": "3.0.2", - "forever-agent": "0.6.1", - "form-data": "2.3.2", - "har-validator": "5.0.3", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.19", - "oauth-sign": "0.8.2", - "performance-now": "2.1.0", - "qs": "6.5.2", - "safe-buffer": "5.1.2", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.6.0", - "uuid": "3.3.2" - } - }, - "require-all": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/require-all/-/require-all-1.0.0.tgz", - "integrity": "sha1-hINwjnzkxt+tmItQgPl4KbktIic=" - }, - "require-like": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", - "integrity": "sha1-rW8wwTvs15cBDEaK+ndcDAprR/o=", - "dev": true - }, - "retry-as-promised": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-2.3.2.tgz", - "integrity": "sha1-zZdO5P2bX+A8vzGHHuSCIcB3N7c=", - "requires": { - "bluebird": "3.5.1", - "debug": "2.6.9" - } - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "requires": { - "glob": "7.1.2" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-json-stringify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", - "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sandboxed-module": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/sandboxed-module/-/sandboxed-module-0.3.0.tgz", - "integrity": "sha1-8fvvvYCaT2kHO9B8rm/H2y6vX2o=", - "dev": true, - "requires": { - "require-like": "0.1.2", - "stack-trace": "0.0.6" - }, - "dependencies": { - "stack-trace": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.6.tgz", - "integrity": "sha1-HnGb1qJin/CcGJ4Xqe+QKpT8XbA=", - "dev": true - } - } - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk=" - }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha1-3Eu8emyp2Rbe5dQ1FvAJK1j3uKs=" - }, - "sequelize": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-4.38.0.tgz", - "integrity": "sha512-ZCcV2HuzU+03xunWgVeyXnPa/RYY5D2U/WUNpq+xF8VmDTLnSDsHl+pEwmiWrpZD7KdBqDczCeTgjToYyVzYQg==", - "requires": { - "bluebird": "3.5.1", - "cls-bluebird": "2.1.0", - "debug": "3.1.0", - "depd": "1.1.2", - "dottie": "2.0.0", - "generic-pool": "3.4.2", - "inflection": "1.12.0", - "lodash": "4.17.10", - "moment": "2.22.2", - "moment-timezone": "0.5.21", - "retry-as-promised": "2.3.2", - "semver": "5.5.0", - "terraformer-wkt-parser": "1.2.0", - "toposort-class": "1.0.1", - "uuid": "3.3.2", - "validator": "10.4.0", - "wkx": "0.4.5" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - } - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "settings-sharelatex": { - "version": "git+https://github.com/sharelatex/settings-sharelatex.git#cbc5e41c1dbe6789721a14b3fdae05bf22546559", - "requires": { - "coffee-script": "1.6.0" - } - }, - "shimmer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.0.tgz", - "integrity": "sha512-xTCx2vohXC2EWWDqY/zb4+5Mu28D+HYNSOuFzsyRDRvI/e1ICb69afwaUwfjr+25ZXldbOLyp+iDUZHq8UnTag==" - }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "sinon": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.7.3.tgz", - "integrity": "sha1-emnWnNApRYbHQyVO7/G1g6UJl/I=", - "dev": true, - "requires": { - "buster-format": "0.5.6" - } - }, - "smoke-test-sharelatex": { - "version": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#bc3e93d18ccee219c0d99e8b02c984ccdd842e1c", - "requires": { - "mocha": "1.17.1" - }, - "dependencies": { - "glob": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", - "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", - "requires": { - "graceful-fs": "2.0.3", - "inherits": "2.0.3", - "minimatch": "0.2.14" - } - }, - "graceful-fs": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", - "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=" - }, - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "requires": { - "lru-cache": "2.7.3", - "sigmund": "1.0.1" - } - }, - "mocha": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.17.1.tgz", - "integrity": "sha1-f3Zx1oUm0HS3uuZgyQmfh+DqHMs=", - "requires": { - "commander": "2.0.0", - "debug": "2.6.9", - "diff": "1.0.7", - "glob": "3.2.3", - "growl": "1.7.0", - "jade": "0.26.3", - "mkdirp": "0.3.5" - } - } - } - }, - "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", - "requires": { - "hoek": "2.16.3" - } - }, - "split-ca": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", - "integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY=" - }, - "sqlite3": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.0.2.tgz", - "integrity": "sha512-51ferIRwYOhzUEtogqOa/y9supADlAht98bF/gbIi6WkzRJX6Yioldxbzj1MV4yV+LgdKD/kkHwFTeFXOG4htA==", - "requires": { - "nan": "2.10.0", - "node-pre-gyp": "0.10.3", - "request": "2.87.0" - }, - "dependencies": { - "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" - }, - "request": { - "version": "2.87.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", - "integrity": "sha1-MvACNc0I1IK00NaNuTqCnA7VdW4=", - "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.7.0", - "caseless": "0.12.0", - "combined-stream": "1.0.6", - "extend": "3.0.2", - "forever-agent": "0.6.1", - "form-data": "2.3.2", - "har-validator": "5.0.3", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.19", - "oauth-sign": "0.8.2", - "performance-now": "2.1.0", - "qs": "6.5.2", - "safe-buffer": "5.1.2", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.6.0", - "uuid": "3.3.2" - } - } - } - }, - "sshpk": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.2", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.2", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "safer-buffer": "2.1.2", - "tweetnacl": "0.14.5" - } - }, - "stack-trace": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", - "integrity": "sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU=" - }, - "statsd-parser": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/statsd-parser/-/statsd-parser-0.0.4.tgz", - "integrity": "sha1-y9JDlTzELv/VSLXSI4jtaJ7GOb0=" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "5.1.2" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "stringstream": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", - "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==" - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, - "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - }, - "tar": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.4.tgz", - "integrity": "sha512-mq9ixIYfNF9SK0IS/h2HKMu8Q2iaCuhDDsZhdEag/FHv8fOaYld4vN7ouMgcSSt5WKZzPs8atclTcJm36OTh4w==", - "requires": { - "chownr": "1.0.1", - "fs-minipass": "1.2.5", - "minipass": "2.3.3", - "minizlib": "1.1.0", - "mkdirp": "0.5.1", - "safe-buffer": "5.1.2", - "yallist": "3.0.2" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - } - } - }, - "tar-fs": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.12.0.tgz", - "integrity": "sha1-pqgFU9ilTHPeHQrg553ncDVgXh0=", - "requires": { - "mkdirp": "0.5.1", - "pump": "1.0.3", - "tar-stream": "1.6.1" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - } - } - }, - "tar-pack": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.1.tgz", - "integrity": "sha512-PPRybI9+jM5tjtCbN2cxmmRU7YmqT3Zv/UDy48tAh2XRkLa9bAORtSWLkVc13+GJF+cdTh1yEnHEk3cpTaL5Kg==", - "requires": { - "debug": "2.6.9", - "fstream": "1.0.11", - "fstream-ignore": "1.0.5", - "once": "1.4.0", - "readable-stream": "2.3.6", - "rimraf": "2.6.2", - "tar": "2.2.1", - "uid-number": "0.0.6" - }, - "dependencies": { - "tar": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", - "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" - } - } - } - }, - "tar-stream": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.1.tgz", - "integrity": "sha512-IFLM5wp3QrJODQFPm6/to3LJZrONdBY/otxcvDIQzu217zKye6yVR3hhi9lAjrC2Z+m/j5oDxMPb1qcd8cIvpA==", - "requires": { - "bl": "1.2.2", - "buffer-alloc": "1.2.0", - "end-of-stream": "1.4.1", - "fs-constants": "1.0.0", - "readable-stream": "2.3.6", - "to-buffer": "1.1.1", - "xtend": "4.0.1" - } - }, - "terraformer": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/terraformer/-/terraformer-1.0.9.tgz", - "integrity": "sha512-YlmQ1fsMWTkKGDGibCRWgmLzrpDRUr63Q025LJ/taYQ6j1Yb8q9McKF7NBi6ACAyUXO6F/bl9w6v4MY307y5Ag==", - "requires": { - "@types/geojson": "1.0.6" - } - }, - "terraformer-wkt-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/terraformer-wkt-parser/-/terraformer-wkt-parser-1.2.0.tgz", - "integrity": "sha512-QU3iA54St5lF8Za1jg1oj4NYc8sn5tCZ08aNSWDeGzrsaV48eZk1iAVWasxhNspYBoCqdHuoot1pUTUrE1AJ4w==", - "requires": { - "@types/geojson": "1.0.6", - "terraformer": "1.0.9" - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "timekeeper": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/timekeeper/-/timekeeper-0.0.4.tgz", - "integrity": "sha1-kNt58X2Ni1NiFUOJSSuXJ2LP0nY=", - "dev": true - }, - "to-buffer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" - }, - "toposort-class": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", - "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" - }, - "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha1-7GDO44rGdQY//JelwYlwV47oNlU=", - "requires": { - "punycode": "1.4.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "5.1.2" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true - }, - "type-detect": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", - "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", - "dev": true - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, - "uid-number": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", - "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" - }, - "underscore": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", - "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" - }, - "underscore.string": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", - "integrity": "sha1-18D6KvXVoaZ/QlPa7pgTLnM/Dxk=", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - }, - "v8-profiler": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/v8-profiler/-/v8-profiler-5.7.0.tgz", - "integrity": "sha1-6DgcvrtbX9DKjSsJ9qAYGhWNs00=", - "requires": { - "nan": "2.10.0", - "node-pre-gyp": "0.6.39" - }, - "dependencies": { - "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" - } - }, - "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" - }, - "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" - }, - "form-data": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.19" - } - }, - "har-schema": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", - "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" - }, - "har-validator": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", - "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", - "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" - } - }, - "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", - "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.1", - "sshpk": "1.14.2" - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - }, - "node-pre-gyp": { - "version": "0.6.39", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz", - "integrity": "sha512-OsJV74qxnvz/AMGgcfZoDaeDXKD3oY3QVIbBmwszTFkRisTSXbMQyn4UWzUMOtA5SVhrBZOTp0wcoSBgfMfMmQ==", - "requires": { - "detect-libc": "1.0.3", - "hawk": "3.1.3", - "mkdirp": "0.5.1", - "nopt": "4.0.1", - "npmlog": "4.1.2", - "rc": "1.2.8", - "request": "2.81.0", - "rimraf": "2.6.2", - "semver": "5.5.0", - "tar": "2.2.1", - "tar-pack": "3.4.1" - } - }, - "performance-now": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" - }, - "qs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" - }, - "request": { - "version": "2.81.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", - "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.7.0", - "caseless": "0.12.0", - "combined-stream": "1.0.6", - "extend": "3.0.2", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.19", - "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.1.2", - "stringstream": "0.0.6", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.6.0", - "uuid": "3.3.2" - } - }, - "tar": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", - "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" - } - } - } - }, - "validator": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-10.4.0.tgz", - "integrity": "sha512-Q/wBy3LB1uOyssgNlXSRmaf22NxjvDNZM2MtIQ4jaEOAB61xsh1TQxsq1CgzUMBV1lDrVMogIh8GjG1DYW0zLg==" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "1.3.0" - } - }, - "which": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", - "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=", - "dev": true - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "requires": { - "string-width": "1.0.2" - } - }, - "wkx": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.5.tgz", - "integrity": "sha512-01dloEcJZAJabLO5XdcRgqdKpmnxS0zIT02LhkdWOZX2Zs2tPM6hlZ4XG9tWaWur1Qd1OO4kJxUbe2+5BofvnA==", - "requires": { - "@types/node": "10.5.4" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "wrench": { - "version": "1.5.9", - "resolved": "https://registry.npmjs.org/wrench/-/wrench-1.5.9.tgz", - "integrity": "sha1-QRaRxjqbJTGxcAJnJ5veyiOyFCo=" - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" - }, - "yallist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", - "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" - } - } -} From 19bd4791dd065b6959168ea76e23146bf638d2dd Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Tue, 18 Dec 2018 11:03:06 +0000 Subject: [PATCH 473/754] Add npm-shrinkwrap.json --- services/clsi/npm-shrinkwrap.json | 2499 +++++++++++++++++++++++++++++ 1 file changed, 2499 insertions(+) create mode 100644 services/clsi/npm-shrinkwrap.json diff --git a/services/clsi/npm-shrinkwrap.json b/services/clsi/npm-shrinkwrap.json new file mode 100644 index 0000000000..4853442cd9 --- /dev/null +++ b/services/clsi/npm-shrinkwrap.json @@ -0,0 +1,2499 @@ +{ + "name": "node-clsi", + "version": "0.1.4", + "dependencies": { + "@google-cloud/common": { + "version": "0.27.0", + "from": "@google-cloud/common@>=0.27.0 <0.28.0", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.27.0.tgz" + }, + "@google-cloud/debug-agent": { + "version": "3.0.1", + "from": "@google-cloud/debug-agent@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/debug-agent/-/debug-agent-3.0.1.tgz", + "dependencies": { + "coffeescript": { + "version": "2.3.2", + "from": "coffeescript@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.3.2.tgz" + } + } + }, + "@google-cloud/profiler": { + "version": "0.2.3", + "from": "@google-cloud/profiler@>=0.2.3 <0.3.0", + "resolved": "https://registry.npmjs.org/@google-cloud/profiler/-/profiler-0.2.3.tgz", + "dependencies": { + "@google-cloud/common": { + "version": "0.26.2", + "from": "@google-cloud/common@>=0.26.0 <0.27.0", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.26.2.tgz" + }, + "through2": { + "version": "3.0.0", + "from": "through2@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.0.tgz" + } + } + }, + "@google-cloud/projectify": { + "version": "0.3.2", + "from": "@google-cloud/projectify@>=0.3.2 <0.4.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-0.3.2.tgz" + }, + "@google-cloud/promisify": { + "version": "0.3.1", + "from": "@google-cloud/promisify@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-0.3.1.tgz" + }, + "@google-cloud/trace-agent": { + "version": "3.5.0", + "from": "@google-cloud/trace-agent@>=3.2.0 <4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/trace-agent/-/trace-agent-3.5.0.tgz", + "dependencies": { + "@google-cloud/common": { + "version": "0.28.0", + "from": "@google-cloud/common@>=0.28.0 <0.29.0", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.28.0.tgz" + }, + "uuid": { + "version": "3.3.2", + "from": "uuid@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz" + } + } + }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "from": "@protobufjs/aspromise@>=1.1.2 <2.0.0", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "from": "@protobufjs/base64@>=1.1.2 <2.0.0", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "from": "@protobufjs/codegen@>=2.0.4 <3.0.0", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "from": "@protobufjs/eventemitter@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "from": "@protobufjs/fetch@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz" + }, + "@protobufjs/float": { + "version": "1.0.2", + "from": "@protobufjs/float@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "from": "@protobufjs/inquire@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz" + }, + "@protobufjs/path": { + "version": "1.1.2", + "from": "@protobufjs/path@>=1.1.2 <2.0.0", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "from": "@protobufjs/pool@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "from": "@protobufjs/utf8@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz" + }, + "@sindresorhus/is": { + "version": "0.13.0", + "from": "@sindresorhus/is@>=0.13.0 <0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.13.0.tgz" + }, + "@types/caseless": { + "version": "0.12.1", + "from": "@types/caseless@*", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz" + }, + "@types/console-log-level": { + "version": "1.4.0", + "from": "@types/console-log-level@>=1.4.0 <2.0.0", + "resolved": "https://registry.npmjs.org/@types/console-log-level/-/console-log-level-1.4.0.tgz" + }, + "@types/duplexify": { + "version": "3.6.0", + "from": "@types/duplexify@>=3.5.0 <4.0.0", + "resolved": "https://registry.npmjs.org/@types/duplexify/-/duplexify-3.6.0.tgz" + }, + "@types/form-data": { + "version": "2.2.1", + "from": "@types/form-data@*", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz" + }, + "@types/geojson": { + "version": "1.0.6", + "from": "@types/geojson@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.6.tgz" + }, + "@types/long": { + "version": "4.0.0", + "from": "@types/long@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz" + }, + "@types/node": { + "version": "10.12.15", + "from": "@types/node@*", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.15.tgz" + }, + "@types/request": { + "version": "2.48.1", + "from": "@types/request@>=2.47.0 <3.0.0", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz" + }, + "@types/semver": { + "version": "5.5.0", + "from": "@types/semver@>=5.5.0 <6.0.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz" + }, + "@types/tough-cookie": { + "version": "2.3.4", + "from": "@types/tough-cookie@*", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.4.tgz" + }, + "abbrev": { + "version": "1.1.1", + "from": "abbrev@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" + }, + "accepts": { + "version": "1.3.5", + "from": "accepts@>=1.3.5 <1.4.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz" + }, + "acorn": { + "version": "5.7.3", + "from": "acorn@>=5.0.3 <6.0.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz" + }, + "agent-base": { + "version": "4.2.1", + "from": "agent-base@>=4.1.0 <5.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz" + }, + "ajv": { + "version": "6.6.2", + "from": "ajv@>=6.5.5 <7.0.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz" + }, + "ansi-regex": { + "version": "2.1.1", + "from": "ansi-regex@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" + }, + "ansi-styles": { + "version": "0.2.0", + "from": "ansi-styles@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-0.2.0.tgz", + "dev": true + }, + "aproba": { + "version": "1.2.0", + "from": "aproba@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz" + }, + "are-we-there-yet": { + "version": "1.1.5", + "from": "are-we-there-yet@>=1.1.2 <1.2.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz" + }, + "argparse": { + "version": "0.1.16", + "from": "argparse@>=0.1.11 <0.2.0", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", + "dev": true, + "dependencies": { + "underscore": { + "version": "1.7.0", + "from": "underscore@>=1.7.0 <1.8.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "dev": true + }, + "underscore.string": { + "version": "2.4.0", + "from": "underscore.string@>=2.4.0 <2.5.0", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", + "dev": true + } + } + }, + "array-flatten": { + "version": "1.1.1", + "from": "array-flatten@1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" + }, + "arrify": { + "version": "1.0.1", + "from": "arrify@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz" + }, + "asn1": { + "version": "0.2.4", + "from": "asn1@>=0.2.3 <0.3.0", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz" + }, + "assert-plus": { + "version": "1.0.0", + "from": "assert-plus@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + }, + "assertion-error": { + "version": "1.0.0", + "from": "assertion-error@1.0.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", + "dev": true + }, + "async": { + "version": "0.2.9", + "from": "async@0.2.9", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.9.tgz" + }, + "async-listener": { + "version": "0.6.10", + "from": "async-listener@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.10.tgz" + }, + "asynckit": { + "version": "0.4.0", + "from": "asynckit@>=0.4.0 <0.5.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + }, + "aws-sign2": { + "version": "0.7.0", + "from": "aws-sign2@>=0.7.0 <0.8.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz" + }, + "aws4": { + "version": "1.8.0", + "from": "aws4@>=1.8.0 <2.0.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz" + }, + "axios": { + "version": "0.18.0", + "from": "axios@>=0.18.0 <0.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz" + }, + "balanced-match": { + "version": "1.0.0", + "from": "balanced-match@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "from": "bcrypt-pbkdf@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz" + }, + "bignumber.js": { + "version": "7.2.1", + "from": "bignumber.js@>=7.0.0 <8.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz" + }, + "bindings": { + "version": "1.3.1", + "from": "bindings@>=1.2.1 <2.0.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.1.tgz" + }, + "bintrees": { + "version": "1.0.1", + "from": "bintrees@1.0.1", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.1.tgz" + }, + "bl": { + "version": "1.2.2", + "from": "bl@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz" + }, + "block-stream": { + "version": "0.0.9", + "from": "block-stream@*", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz" + }, + "bluebird": { + "version": "3.5.3", + "from": "bluebird@>=3.5.0 <4.0.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz" + }, + "body-parser": { + "version": "1.18.3", + "from": "body-parser@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz" + }, + "boom": { + "version": "2.10.1", + "from": "boom@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz" + }, + "brace-expansion": { + "version": "1.1.11", + "from": "brace-expansion@>=1.1.7 <2.0.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + }, + "browser-stdout": { + "version": "1.3.0", + "from": "browser-stdout@1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "dev": true + }, + "buffer-alloc": { + "version": "1.2.0", + "from": "buffer-alloc@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz" + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "from": "buffer-alloc-unsafe@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz" + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "from": "buffer-equal-constant-time@1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz" + }, + "buffer-fill": { + "version": "1.0.0", + "from": "buffer-fill@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz" + }, + "buffer-from": { + "version": "1.1.1", + "from": "buffer-from@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz" + }, + "builtin-modules": { + "version": "3.0.0", + "from": "builtin-modules@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.0.0.tgz" + }, + "bunyan": { + "version": "0.22.3", + "from": "bunyan@>=0.22.1 <0.23.0", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-0.22.3.tgz", + "dev": true + }, + "buster-core": { + "version": "0.6.4", + "from": "buster-core@0.6.4", + "resolved": "https://registry.npmjs.org/buster-core/-/buster-core-0.6.4.tgz", + "dev": true + }, + "buster-format": { + "version": "0.5.6", + "from": "buster-format@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/buster-format/-/buster-format-0.5.6.tgz", + "dev": true + }, + "bytes": { + "version": "3.0.0", + "from": "bytes@3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz" + }, + "caseless": { + "version": "0.12.0", + "from": "caseless@>=0.12.0 <0.13.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz" + }, + "chai": { + "version": "1.8.1", + "from": "chai@>=1.8.1 <1.9.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-1.8.1.tgz", + "dev": true + }, + "chalk": { + "version": "0.3.0", + "from": "chalk@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.3.0.tgz", + "dev": true + }, + "chownr": { + "version": "1.1.1", + "from": "chownr@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz" + }, + "cls-bluebird": { + "version": "2.1.0", + "from": "cls-bluebird@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz" + }, + "co": { + "version": "4.6.0", + "from": "co@>=4.6.0 <5.0.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz" + }, + "code-point-at": { + "version": "1.1.0", + "from": "code-point-at@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz" + }, + "coffee-script": { + "version": "1.6.0", + "from": "coffee-script@1.6.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", + "dev": true + }, + "colors": { + "version": "0.6.2", + "from": "colors@>=0.6.2 <0.7.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "dev": true + }, + "combined-stream": { + "version": "1.0.7", + "from": "combined-stream@>=1.0.6 <1.1.0", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz" + }, + "commander": { + "version": "2.0.0", + "from": "commander@2.0.0", + "resolved": "http://registry.npmjs.org/commander/-/commander-2.0.0.tgz", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "from": "concat-map@0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + }, + "concat-stream": { + "version": "1.6.2", + "from": "concat-stream@>=1.6.2 <1.7.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz" + }, + "console-control-strings": { + "version": "1.1.0", + "from": "console-control-strings@>=1.1.0 <1.2.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" + }, + "console-log-level": { + "version": "1.4.0", + "from": "console-log-level@>=1.4.0 <2.0.0", + "resolved": "https://registry.npmjs.org/console-log-level/-/console-log-level-1.4.0.tgz" + }, + "content-disposition": { + "version": "0.5.2", + "from": "content-disposition@0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz" + }, + "content-type": { + "version": "1.0.4", + "from": "content-type@>=1.0.4 <1.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz" + }, + "continuation-local-storage": { + "version": "3.2.1", + "from": "continuation-local-storage@>=3.2.1 <4.0.0", + "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz" + }, + "cookie": { + "version": "0.3.1", + "from": "cookie@0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz" + }, + "cookie-signature": { + "version": "1.0.6", + "from": "cookie-signature@1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" + }, + "core-util-is": { + "version": "1.0.2", + "from": "core-util-is@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" + }, + "cryptiles": { + "version": "2.0.5", + "from": "cryptiles@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz" + }, + "dashdash": { + "version": "1.14.1", + "from": "dashdash@>=1.12.0 <2.0.0", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz" + }, + "dateformat": { + "version": "1.0.2-1.2.3", + "from": "dateformat@1.0.2-1.2.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", + "dev": true + }, + "debug": { + "version": "2.6.9", + "from": "debug@2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" + }, + "deep-eql": { + "version": "0.1.3", + "from": "deep-eql@0.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "dev": true + }, + "deep-extend": { + "version": "0.6.0", + "from": "deep-extend@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz" + }, + "delay": { + "version": "4.1.0", + "from": "delay@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-4.1.0.tgz" + }, + "delayed-stream": { + "version": "1.0.0", + "from": "delayed-stream@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + }, + "delegates": { + "version": "1.0.0", + "from": "delegates@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" + }, + "depd": { + "version": "1.1.2", + "from": "depd@>=1.1.2 <1.2.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" + }, + "destroy": { + "version": "1.0.4", + "from": "destroy@>=1.0.4 <1.1.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz" + }, + "detect-libc": { + "version": "1.0.3", + "from": "detect-libc@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz" + }, + "diff": { + "version": "1.0.7", + "from": "diff@1.0.7", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", + "dev": true + }, + "docker-modem": { + "version": "1.0.7", + "from": "docker-modem@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-1.0.7.tgz", + "dependencies": { + "debug": { + "version": "3.2.6", + "from": "debug@>=3.2.5 <4.0.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz" + }, + "isarray": { + "version": "0.0.1", + "from": "isarray@0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + }, + "ms": { + "version": "2.1.1", + "from": "ms@>=2.1.1 <3.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz" + }, + "readable-stream": { + "version": "1.0.34", + "from": "readable-stream@>=1.0.26-4 <1.1.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz" + }, + "string_decoder": { + "version": "0.10.31", + "from": "string_decoder@>=0.10.0 <0.11.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + } + } + }, + "dockerode": { + "version": "2.5.7", + "from": "dockerode@>=2.5.3 <3.0.0", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-2.5.7.tgz" + }, + "dottie": { + "version": "2.0.1", + "from": "dottie@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.1.tgz" + }, + "dtrace-provider": { + "version": "0.2.8", + "from": "dtrace-provider@0.2.8", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.2.8.tgz", + "dev": true, + "optional": true + }, + "duplexify": { + "version": "3.6.1", + "from": "duplexify@>=3.6.0 <4.0.0", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz" + }, + "ecc-jsbn": { + "version": "0.1.2", + "from": "ecc-jsbn@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz" + }, + "ecdsa-sig-formatter": { + "version": "1.0.10", + "from": "ecdsa-sig-formatter@1.0.10", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz" + }, + "ee-first": { + "version": "1.1.1", + "from": "ee-first@1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" + }, + "emitter-listener": { + "version": "1.1.2", + "from": "emitter-listener@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz" + }, + "encodeurl": { + "version": "1.0.2", + "from": "encodeurl@>=1.0.2 <1.1.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" + }, + "end-of-stream": { + "version": "1.4.1", + "from": "end-of-stream@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz" + }, + "ent": { + "version": "2.2.0", + "from": "ent@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz" + }, + "es6-promise": { + "version": "4.2.5", + "from": "es6-promise@>=4.0.3 <5.0.0", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz" + }, + "es6-promisify": { + "version": "5.0.0", + "from": "es6-promisify@>=5.0.0 <6.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz" + }, + "escape-html": { + "version": "1.0.3", + "from": "escape-html@>=1.0.3 <1.1.0", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" + }, + "escape-string-regexp": { + "version": "1.0.5", + "from": "escape-string-regexp@1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "dev": true + }, + "esprima": { + "version": "1.0.4", + "from": "esprima@>=1.0.2 <1.1.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "dev": true + }, + "etag": { + "version": "1.8.1", + "from": "etag@>=1.8.1 <1.9.0", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" + }, + "eventemitter2": { + "version": "0.4.14", + "from": "eventemitter2@>=0.4.13 <0.5.0", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "dev": true + }, + "exit": { + "version": "0.1.2", + "from": "exit@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "dev": true + }, + "express": { + "version": "4.16.4", + "from": "express@>=4.2.0 <5.0.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "dependencies": { + "statuses": { + "version": "1.4.0", + "from": "statuses@>=1.4.0 <1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz" + } + } + }, + "extend": { + "version": "3.0.2", + "from": "extend@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" + }, + "extsprintf": { + "version": "1.3.0", + "from": "extsprintf@1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" + }, + "fast-deep-equal": { + "version": "2.0.1", + "from": "fast-deep-equal@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "from": "fast-json-stable-stringify@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz" + }, + "finalhandler": { + "version": "1.1.1", + "from": "finalhandler@1.1.1", + "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "dependencies": { + "statuses": { + "version": "1.4.0", + "from": "statuses@>=1.4.0 <1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz" + } + } + }, + "findit2": { + "version": "2.2.3", + "from": "findit2@>=2.2.3 <3.0.0", + "resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz" + }, + "findup-sync": { + "version": "0.1.3", + "from": "findup-sync@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", + "dev": true, + "dependencies": { + "glob": { + "version": "3.2.11", + "from": "glob@>=3.2.9 <3.3.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "dev": true + }, + "lodash": { + "version": "2.4.2", + "from": "lodash@>=2.4.1 <2.5.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "dev": true + }, + "lru-cache": { + "version": "2.7.3", + "from": "lru-cache@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "dev": true + }, + "minimatch": { + "version": "0.3.0", + "from": "minimatch@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "dev": true + } + } + }, + "follow-redirects": { + "version": "1.5.10", + "from": "follow-redirects@>=1.3.0 <2.0.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "dependencies": { + "debug": { + "version": "3.1.0", + "from": "debug@3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz" + } + } + }, + "forever-agent": { + "version": "0.6.1", + "from": "forever-agent@>=0.6.1 <0.7.0", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" + }, + "form-data": { + "version": "2.3.3", + "from": "form-data@>=2.3.2 <2.4.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz" + }, + "forwarded": { + "version": "0.1.2", + "from": "forwarded@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz" + }, + "fresh": { + "version": "0.5.2", + "from": "fresh@0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" + }, + "fs-constants": { + "version": "1.0.0", + "from": "fs-constants@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz" + }, + "fs-extra": { + "version": "0.16.5", + "from": "fs-extra@>=0.16.3 <0.17.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.16.5.tgz" + }, + "fs-minipass": { + "version": "1.2.5", + "from": "fs-minipass@>=1.2.5 <2.0.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz" + }, + "fs.realpath": { + "version": "1.0.0", + "from": "fs.realpath@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + }, + "fstream": { + "version": "1.0.11", + "from": "fstream@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "dependencies": { + "graceful-fs": { + "version": "4.1.15", + "from": "graceful-fs@>=4.1.2 <5.0.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz" + }, + "mkdirp": { + "version": "0.5.1", + "from": "mkdirp@>=0.5.0 >=0.0.0 <1.0.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" + } + } + }, + "fstream-ignore": { + "version": "1.0.5", + "from": "fstream-ignore@>=1.0.5 <2.0.0", + "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz" + }, + "gauge": { + "version": "2.7.4", + "from": "gauge@>=2.7.3 <2.8.0", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz" + }, + "gaxios": { + "version": "1.0.4", + "from": "gaxios@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-1.0.4.tgz" + }, + "gcp-metadata": { + "version": "0.9.3", + "from": "gcp-metadata@>=0.9.0 <0.10.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-0.9.3.tgz" + }, + "generic-pool": { + "version": "3.4.2", + "from": "generic-pool@>=3.4.0 <4.0.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.4.2.tgz" + }, + "getobject": { + "version": "0.1.0", + "from": "getobject@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "from": "getpass@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz" + }, + "glob": { + "version": "7.1.3", + "from": "glob@>=7.0.5 <8.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz" + }, + "google-auth-library": { + "version": "2.0.2", + "from": "google-auth-library@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-2.0.2.tgz", + "dependencies": { + "gcp-metadata": { + "version": "0.7.0", + "from": "gcp-metadata@>=0.7.0 <0.8.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-0.7.0.tgz" + } + } + }, + "google-p12-pem": { + "version": "1.0.3", + "from": "google-p12-pem@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-1.0.3.tgz" + }, + "graceful-fs": { + "version": "3.0.11", + "from": "graceful-fs@>=3.0.5 <4.0.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz" + }, + "growl": { + "version": "1.7.0", + "from": "growl@1.7.x", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", + "dev": true + }, + "grunt": { + "version": "0.4.5", + "from": "grunt@>=0.4.2 <0.5.0", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", + "dev": true, + "dependencies": { + "async": { + "version": "0.1.22", + "from": "async@>=0.1.22 <0.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", + "dev": true + }, + "coffee-script": { + "version": "1.3.3", + "from": "coffee-script@>=1.3.3 <1.4.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz", + "dev": true + }, + "glob": { + "version": "3.1.21", + "from": "glob@>=3.1.21 <3.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", + "dev": true + }, + "graceful-fs": { + "version": "1.2.3", + "from": "graceful-fs@>=1.2.0 <1.3.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", + "dev": true + }, + "iconv-lite": { + "version": "0.2.11", + "from": "iconv-lite@>=0.2.11 <0.3.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", + "dev": true + }, + "inherits": { + "version": "1.0.2", + "from": "inherits@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "dev": true + }, + "lodash": { + "version": "0.9.2", + "from": "lodash@>=0.9.2 <0.10.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", + "dev": true + }, + "lru-cache": { + "version": "2.7.3", + "from": "lru-cache@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "dev": true + }, + "minimatch": { + "version": "0.2.14", + "from": "minimatch@>=0.2.12 <0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "dev": true + }, + "nopt": { + "version": "1.0.10", + "from": "nopt@>=1.0.10 <1.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "dev": true + }, + "rimraf": { + "version": "2.2.8", + "from": "rimraf@>=2.2.8 <2.3.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "dev": true + } + } + }, + "grunt-bunyan": { + "version": "0.5.0", + "from": "grunt-bunyan@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/grunt-bunyan/-/grunt-bunyan-0.5.0.tgz", + "dev": true, + "dependencies": { + "lodash": { + "version": "2.4.2", + "from": "lodash@>=2.4.1 <2.5.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "dev": true + } + } + }, + "grunt-contrib-clean": { + "version": "0.5.0", + "from": "grunt-contrib-clean@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-0.5.0.tgz", + "dev": true, + "dependencies": { + "rimraf": { + "version": "2.2.8", + "from": "rimraf@>=2.2.1 <2.3.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "dev": true + } + } + }, + "grunt-contrib-coffee": { + "version": "0.7.0", + "from": "grunt-contrib-coffee@>=0.7.0 <0.8.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-coffee/-/grunt-contrib-coffee-0.7.0.tgz", + "dev": true, + "dependencies": { + "coffee-script": { + "version": "1.6.3", + "from": "coffee-script@>=1.6.2 <1.7.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.3.tgz", + "dev": true + } + } + }, + "grunt-execute": { + "version": "0.1.5", + "from": "grunt-execute@>=0.1.5 <0.2.0", + "resolved": "https://registry.npmjs.org/grunt-execute/-/grunt-execute-0.1.5.tgz", + "dev": true + }, + "grunt-legacy-log": { + "version": "0.1.3", + "from": "grunt-legacy-log@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", + "dev": true, + "dependencies": { + "lodash": { + "version": "2.4.2", + "from": "lodash@>=2.4.1 <2.5.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "dev": true + }, + "underscore.string": { + "version": "2.3.3", + "from": "underscore.string@>=2.3.3 <2.4.0", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", + "dev": true + } + } + }, + "grunt-legacy-log-utils": { + "version": "0.1.1", + "from": "grunt-legacy-log-utils@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", + "dev": true, + "dependencies": { + "lodash": { + "version": "2.4.2", + "from": "lodash@>=2.4.1 <2.5.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "dev": true + }, + "underscore.string": { + "version": "2.3.3", + "from": "underscore.string@>=2.3.3 <2.4.0", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", + "dev": true + } + } + }, + "grunt-legacy-util": { + "version": "0.2.0", + "from": "grunt-legacy-util@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", + "dev": true, + "dependencies": { + "async": { + "version": "0.1.22", + "from": "async@>=0.1.22 <0.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", + "dev": true + }, + "lodash": { + "version": "0.9.2", + "from": "lodash@>=0.9.2 <0.10.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", + "dev": true + } + } + }, + "grunt-mkdir": { + "version": "1.0.0", + "from": "grunt-mkdir@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/grunt-mkdir/-/grunt-mkdir-1.0.0.tgz" + }, + "grunt-mocha-test": { + "version": "0.8.2", + "from": "grunt-mocha-test@>=0.8.1 <0.9.0", + "resolved": "https://registry.npmjs.org/grunt-mocha-test/-/grunt-mocha-test-0.8.2.tgz", + "dev": true, + "dependencies": { + "glob": { + "version": "3.2.3", + "from": "glob@3.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", + "dev": true + }, + "graceful-fs": { + "version": "2.0.3", + "from": "graceful-fs@>=2.0.0 <2.1.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", + "dev": true + }, + "lru-cache": { + "version": "2.7.3", + "from": "lru-cache@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "dev": true + }, + "minimatch": { + "version": "0.2.14", + "from": "minimatch@>=0.2.11 <0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "dev": true + }, + "mocha": { + "version": "1.14.0", + "from": "mocha@>=1.14.0 <1.15.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.14.0.tgz", + "dev": true + } + } + }, + "grunt-shell": { + "version": "0.6.4", + "from": "grunt-shell@>=0.6.1 <0.7.0", + "resolved": "https://registry.npmjs.org/grunt-shell/-/grunt-shell-0.6.4.tgz", + "dev": true + }, + "gtoken": { + "version": "2.3.0", + "from": "gtoken@>=2.3.0 <3.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-2.3.0.tgz", + "dependencies": { + "mime": { + "version": "2.4.0", + "from": "mime@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz" + }, + "pify": { + "version": "3.0.0", + "from": "pify@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz" + } + } + }, + "har-schema": { + "version": "2.0.0", + "from": "har-schema@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz" + }, + "har-validator": { + "version": "5.1.3", + "from": "har-validator@>=5.1.0 <5.2.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz" + }, + "has-color": { + "version": "0.1.7", + "from": "has-color@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", + "dev": true + }, + "has-flag": { + "version": "2.0.0", + "from": "has-flag@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "from": "has-unicode@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz" + }, + "hawk": { + "version": "3.1.3", + "from": "hawk@3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz" + }, + "he": { + "version": "1.1.1", + "from": "he@1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "dev": true + }, + "heapdump": { + "version": "0.3.12", + "from": "heapdump@>=0.3.5 <0.4.0", + "resolved": "https://registry.npmjs.org/heapdump/-/heapdump-0.3.12.tgz" + }, + "hex2dec": { + "version": "1.1.1", + "from": "hex2dec@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/hex2dec/-/hex2dec-1.1.1.tgz" + }, + "hoek": { + "version": "2.16.3", + "from": "hoek@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" + }, + "hooker": { + "version": "0.2.3", + "from": "hooker@>=0.2.3 <0.3.0", + "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "dev": true + }, + "http-errors": { + "version": "1.6.3", + "from": "http-errors@>=1.6.3 <1.7.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz" + }, + "http-signature": { + "version": "1.2.0", + "from": "http-signature@>=1.2.0 <1.3.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz" + }, + "https-proxy-agent": { + "version": "2.2.1", + "from": "https-proxy-agent@>=2.2.1 <3.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", + "dependencies": { + "debug": { + "version": "3.2.6", + "from": "debug@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz" + }, + "ms": { + "version": "2.1.1", + "from": "ms@>=2.1.1 <3.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz" + } + } + }, + "iconv-lite": { + "version": "0.4.23", + "from": "iconv-lite@0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz" + }, + "ignore-walk": { + "version": "3.0.1", + "from": "ignore-walk@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz" + }, + "inflection": { + "version": "1.12.0", + "from": "inflection@1.12.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz" + }, + "inflight": { + "version": "1.0.6", + "from": "inflight@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + }, + "inherits": { + "version": "2.0.3", + "from": "inherits@2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" + }, + "ini": { + "version": "1.3.5", + "from": "ini@>=1.3.0 <1.4.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz" + }, + "ipaddr.js": { + "version": "1.8.0", + "from": "ipaddr.js@1.8.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz" + }, + "is": { + "version": "3.3.0", + "from": "is@>=3.2.0 <4.0.0", + "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz" + }, + "is-bluebird": { + "version": "1.0.2", + "from": "is-bluebird@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz" + }, + "is-buffer": { + "version": "1.1.6", + "from": "is-buffer@>=1.1.5 <2.0.0", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "from": "is-fullwidth-code-point@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz" + }, + "is-typedarray": { + "version": "1.0.0", + "from": "is-typedarray@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" + }, + "isarray": { + "version": "1.0.0", + "from": "isarray@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + }, + "isstream": { + "version": "0.1.2", + "from": "isstream@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" + }, + "jade": { + "version": "0.26.3", + "from": "jade@0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "dev": true, + "dependencies": { + "commander": { + "version": "0.6.1", + "from": "commander@0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "dev": true + }, + "mkdirp": { + "version": "0.3.0", + "from": "mkdirp@0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "dev": true + } + } + }, + "js-yaml": { + "version": "2.0.5", + "from": "js-yaml@>=2.0.5 <2.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", + "dev": true + }, + "jsbn": { + "version": "0.1.1", + "from": "jsbn@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz" + }, + "json-bigint": { + "version": "0.3.0", + "from": "json-bigint@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz" + }, + "json-schema": { + "version": "0.2.3", + "from": "json-schema@0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz" + }, + "json-schema-traverse": { + "version": "0.4.1", + "from": "json-schema-traverse@>=0.4.1 <0.5.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" + }, + "json-stable-stringify": { + "version": "1.0.1", + "from": "json-stable-stringify@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz" + }, + "json-stringify-safe": { + "version": "5.0.1", + "from": "json-stringify-safe@5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" + }, + "jsonfile": { + "version": "2.4.0", + "from": "jsonfile@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "dependencies": { + "graceful-fs": { + "version": "4.1.15", + "from": "graceful-fs@>=4.1.6 <5.0.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "optional": true + } + } + }, + "jsonify": { + "version": "0.0.0", + "from": "jsonify@>=0.0.0 <0.1.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz" + }, + "jsonparse": { + "version": "1.3.1", + "from": "jsonparse@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz" + }, + "JSONStream": { + "version": "1.3.2", + "from": "JSONStream@1.3.2", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz" + }, + "jsprim": { + "version": "1.4.1", + "from": "jsprim@>=1.2.2 <2.0.0", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz" + }, + "jwa": { + "version": "1.1.6", + "from": "jwa@>=1.1.5 <2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz" + }, + "jws": { + "version": "3.1.5", + "from": "jws@>=3.1.5 <4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz" + }, + "lockfile": { + "version": "1.0.4", + "from": "lockfile@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz" + }, + "lodash": { + "version": "4.17.11", + "from": "lodash@>=4.17.1 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz" + }, + "lodash.pickby": { + "version": "4.6.0", + "from": "lodash.pickby@>=4.6.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz" + }, + "logger-sharelatex": { + "version": "1.5.4", + "from": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", + "resolved": "git+https://github.com/sharelatex/logger-sharelatex.git#9ee7b52eb2bbd8fcbb1e2c708587c1e93fd4c733" + }, + "long": { + "version": "4.0.0", + "from": "long@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz" + }, + "lru-cache": { + "version": "5.1.1", + "from": "lru-cache@>=5.0.0 <6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" + }, + "lsmod": { + "version": "1.0.0", + "from": "lsmod@1.0.0", + "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz" + }, + "lynx": { + "version": "0.0.11", + "from": "lynx@0.0.11", + "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.0.11.tgz" + }, + "media-typer": { + "version": "0.3.0", + "from": "media-typer@0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" + }, + "merge-descriptors": { + "version": "1.0.1", + "from": "merge-descriptors@1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz" + }, + "mersenne": { + "version": "0.0.4", + "from": "mersenne@>=0.0.3 <0.1.0", + "resolved": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz" + }, + "methods": { + "version": "1.1.2", + "from": "methods@>=1.1.2 <1.2.0", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" + }, + "metrics-sharelatex": { + "version": "2.0.12", + "from": "git+https://github.com/sharelatex/metrics-sharelatex.git#v2.0.12", + "resolved": "git+https://github.com/sharelatex/metrics-sharelatex.git#3ac1621ef049e2f2d88a83b3a41011333d609662", + "dependencies": { + "coffee-script": { + "version": "1.6.0", + "from": "coffee-script@1.6.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz" + }, + "lynx": { + "version": "0.1.1", + "from": "lynx@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.1.1.tgz" + }, + "underscore": { + "version": "1.6.0", + "from": "underscore@>=1.6.0 <1.7.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz" + } + } + }, + "mime": { + "version": "1.4.1", + "from": "mime@1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz" + }, + "mime-db": { + "version": "1.37.0", + "from": "mime-db@>=1.37.0 <1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz" + }, + "mime-types": { + "version": "2.1.21", + "from": "mime-types@>=2.1.18 <2.2.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz" + }, + "minimatch": { + "version": "3.0.4", + "from": "minimatch@>=3.0.4 <4.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" + }, + "minimist": { + "version": "0.0.8", + "from": "minimist@0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + }, + "minipass": { + "version": "2.3.5", + "from": "minipass@>=2.3.4 <3.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz" + }, + "minizlib": { + "version": "1.2.1", + "from": "minizlib@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz" + }, + "mkdirp": { + "version": "0.3.5", + "from": "mkdirp@0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz" + }, + "mocha": { + "version": "4.1.0", + "from": "mocha@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz", + "dev": true, + "dependencies": { + "commander": { + "version": "2.11.0", + "from": "commander@2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "dev": true + }, + "debug": { + "version": "3.1.0", + "from": "debug@3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "dev": true + }, + "diff": { + "version": "3.3.1", + "from": "diff@3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "dev": true + }, + "glob": { + "version": "7.1.2", + "from": "glob@7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "dev": true + }, + "growl": { + "version": "1.10.3", + "from": "growl@1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "from": "mkdirp@0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "dev": true + } + } + }, + "module-details-from-path": { + "version": "1.0.3", + "from": "module-details-from-path@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz" + }, + "moment": { + "version": "2.23.0", + "from": "moment@>=2.20.0 <3.0.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.23.0.tgz" + }, + "moment-timezone": { + "version": "0.5.23", + "from": "moment-timezone@>=0.5.14 <0.6.0", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.23.tgz" + }, + "ms": { + "version": "2.0.0", + "from": "ms@2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + }, + "mv": { + "version": "2.1.1", + "from": "mv@~2", + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "dev": true, + "optional": true, + "dependencies": { + "glob": { + "version": "6.0.4", + "from": "glob@>=6.0.1 <7.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "dev": true, + "optional": true + }, + "mkdirp": { + "version": "0.5.1", + "from": "mkdirp@>=0.5.1 <0.6.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "dev": true, + "optional": true + }, + "rimraf": { + "version": "2.4.5", + "from": "rimraf@>=2.4.0 <2.5.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "dev": true, + "optional": true + } + } + }, + "mysql": { + "version": "2.6.2", + "from": "mysql@2.6.2", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.6.2.tgz", + "dependencies": { + "bignumber.js": { + "version": "2.0.7", + "from": "bignumber.js@2.0.7", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.7.tgz" + }, + "isarray": { + "version": "0.0.1", + "from": "isarray@0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + }, + "readable-stream": { + "version": "1.1.14", + "from": "readable-stream@>=1.1.13 <1.2.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz" + }, + "string_decoder": { + "version": "0.10.31", + "from": "string_decoder@>=0.10.0 <0.11.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + } + } + }, + "nan": { + "version": "2.12.0", + "from": "nan@>=2.11.1 <3.0.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.0.tgz" + }, + "natives": { + "version": "1.1.6", + "from": "natives@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz" + }, + "ncp": { + "version": "2.0.0", + "from": "ncp@~2.0.0", + "resolved": "http://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.4", + "from": "needle@>=2.2.1 <3.0.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.4.tgz" + }, + "negotiator": { + "version": "0.6.1", + "from": "negotiator@0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz" + }, + "node-fetch": { + "version": "2.3.0", + "from": "node-fetch@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz" + }, + "node-forge": { + "version": "0.7.6", + "from": "node-forge@>=0.7.5 <0.8.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz" + }, + "node-pre-gyp": { + "version": "0.10.3", + "from": "node-pre-gyp@>=0.10.3 <0.11.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz", + "dependencies": { + "mkdirp": { + "version": "0.5.1", + "from": "mkdirp@>=0.5.1 <0.6.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" + } + } + }, + "nopt": { + "version": "4.0.1", + "from": "nopt@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz" + }, + "npm-bundled": { + "version": "1.0.5", + "from": "npm-bundled@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz" + }, + "npm-packlist": { + "version": "1.1.12", + "from": "npm-packlist@>=1.1.6 <2.0.0", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.12.tgz" + }, + "npmlog": { + "version": "4.1.2", + "from": "npmlog@>=4.0.2 <5.0.0", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz" + }, + "number-is-nan": { + "version": "1.0.1", + "from": "number-is-nan@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz" + }, + "oauth-sign": { + "version": "0.9.0", + "from": "oauth-sign@>=0.9.0 <0.10.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz" + }, + "object-assign": { + "version": "4.1.1", + "from": "object-assign@>=4.1.0 <5.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + }, + "on-finished": { + "version": "2.3.0", + "from": "on-finished@>=2.3.0 <2.4.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz" + }, + "once": { + "version": "1.4.0", + "from": "once@>=1.3.1 <2.0.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + }, + "os-homedir": { + "version": "1.0.2", + "from": "os-homedir@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz" + }, + "os-tmpdir": { + "version": "1.0.2", + "from": "os-tmpdir@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" + }, + "osenv": { + "version": "0.1.5", + "from": "osenv@>=0.1.4 <0.2.0", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz" + }, + "p-limit": { + "version": "2.0.0", + "from": "p-limit@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz" + }, + "p-try": { + "version": "2.0.0", + "from": "p-try@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz" + }, + "parse-duration": { + "version": "0.1.1", + "from": "parse-duration@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-0.1.1.tgz" + }, + "parse-ms": { + "version": "2.0.0", + "from": "parse-ms@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.0.0.tgz" + }, + "parseurl": { + "version": "1.3.2", + "from": "parseurl@>=1.3.2 <1.4.0", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz" + }, + "path-is-absolute": { + "version": "1.0.1", + "from": "path-is-absolute@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + }, + "path-parse": { + "version": "1.0.6", + "from": "path-parse@>=1.0.6 <2.0.0", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz" + }, + "path-to-regexp": { + "version": "0.1.7", + "from": "path-to-regexp@0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" + }, + "performance-now": { + "version": "2.1.0", + "from": "performance-now@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" + }, + "pify": { + "version": "4.0.1", + "from": "pify@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz" + }, + "pretty-ms": { + "version": "4.0.0", + "from": "pretty-ms@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-4.0.0.tgz" + }, + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@>=2.0.0 <2.1.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz" + }, + "prom-client": { + "version": "11.2.0", + "from": "prom-client@>=11.1.3 <12.0.0", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-11.2.0.tgz" + }, + "protobufjs": { + "version": "6.8.8", + "from": "protobufjs@>=6.8.6 <6.9.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz" + }, + "proxy-addr": { + "version": "2.0.4", + "from": "proxy-addr@>=2.0.4 <2.1.0", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz" + }, + "psl": { + "version": "1.1.31", + "from": "psl@>=1.1.24 <2.0.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz" + }, + "pump": { + "version": "1.0.3", + "from": "pump@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz" + }, + "punycode": { + "version": "2.1.1", + "from": "punycode@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" + }, + "qs": { + "version": "6.5.2", + "from": "qs@6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz" + }, + "range-parser": { + "version": "1.2.0", + "from": "range-parser@>=1.2.0 <1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz" + }, + "raven": { + "version": "1.2.1", + "from": "raven@>=1.1.3 <2.0.0", + "resolved": "https://registry.npmjs.org/raven/-/raven-1.2.1.tgz" + }, + "raw-body": { + "version": "2.3.3", + "from": "raw-body@2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz" + }, + "rc": { + "version": "1.2.8", + "from": "rc@>=1.2.7 <2.0.0", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "dependencies": { + "minimist": { + "version": "1.2.0", + "from": "minimist@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz" + } + } + }, + "readable-stream": { + "version": "2.3.6", + "from": "readable-stream@>=2.2.2 <3.0.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz" + }, + "request": { + "version": "2.88.0", + "from": "request@>=2.21.0 <3.0.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "dependencies": { + "uuid": { + "version": "3.3.2", + "from": "uuid@>=3.3.2 <4.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz" + } + } + }, + "require-all": { + "version": "1.0.0", + "from": "require-all@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/require-all/-/require-all-1.0.0.tgz" + }, + "require-in-the-middle": { + "version": "3.1.0", + "from": "require-in-the-middle@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-3.1.0.tgz" + }, + "require-like": { + "version": "0.1.2", + "from": "require-like@0.1.2", + "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", + "dev": true + }, + "resolve": { + "version": "1.9.0", + "from": "resolve@>=1.5.0 <2.0.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz" + }, + "retry-as-promised": { + "version": "2.3.2", + "from": "retry-as-promised@>=2.3.2 <3.0.0", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-2.3.2.tgz" + }, + "retry-axios": { + "version": "0.3.2", + "from": "retry-axios@0.3.2", + "resolved": "https://registry.npmjs.org/retry-axios/-/retry-axios-0.3.2.tgz" + }, + "retry-request": { + "version": "4.0.0", + "from": "retry-request@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.0.0.tgz" + }, + "rimraf": { + "version": "2.6.2", + "from": "rimraf@>=2.2.8 <3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz" + }, + "safe-buffer": { + "version": "5.1.2", + "from": "safe-buffer@>=5.1.1 <5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + }, + "safer-buffer": { + "version": "2.1.2", + "from": "safer-buffer@>=2.1.2 <3.0.0", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + }, + "sandboxed-module": { + "version": "0.3.0", + "from": "sandboxed-module@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/sandboxed-module/-/sandboxed-module-0.3.0.tgz", + "dev": true, + "dependencies": { + "stack-trace": { + "version": "0.0.6", + "from": "stack-trace@0.0.6", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.6.tgz", + "dev": true + } + } + }, + "sax": { + "version": "1.2.4", + "from": "sax@>=1.2.4 <2.0.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz" + }, + "semver": { + "version": "5.6.0", + "from": "semver@>=5.5.0 <6.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz" + }, + "send": { + "version": "0.16.2", + "from": "send@0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "dependencies": { + "statuses": { + "version": "1.4.0", + "from": "statuses@>=1.4.0 <1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz" + } + } + }, + "sequelize": { + "version": "4.42.0", + "from": "sequelize@>=4.38.0 <5.0.0", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-4.42.0.tgz", + "dependencies": { + "debug": { + "version": "3.2.6", + "from": "debug@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz" + }, + "ms": { + "version": "2.1.1", + "from": "ms@>=2.1.1 <3.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz" + }, + "uuid": { + "version": "3.3.2", + "from": "uuid@>=3.2.1 <4.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz" + } + } + }, + "serve-static": { + "version": "1.13.2", + "from": "serve-static@1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz" + }, + "set-blocking": { + "version": "2.0.0", + "from": "set-blocking@>=2.0.0 <2.1.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" + }, + "setprototypeof": { + "version": "1.1.0", + "from": "setprototypeof@1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz" + }, + "settings-sharelatex": { + "version": "1.0.0", + "from": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", + "resolved": "git+https://github.com/sharelatex/settings-sharelatex.git#cbc5e41c1dbe6789721a14b3fdae05bf22546559", + "dependencies": { + "coffee-script": { + "version": "1.6.0", + "from": "coffee-script@1.6.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz" + } + } + }, + "shimmer": { + "version": "1.2.0", + "from": "shimmer@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.0.tgz" + }, + "sigmund": { + "version": "1.0.1", + "from": "sigmund@~1.0.0", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "from": "signal-exit@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz" + }, + "sinon": { + "version": "1.7.3", + "from": "sinon@>=1.7.3 <1.8.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.7.3.tgz", + "dev": true + }, + "smoke-test-sharelatex": { + "version": "1.0.1", + "from": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", + "resolved": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#bc3e93d18ccee219c0d99e8b02c984ccdd842e1c" + }, + "sntp": { + "version": "1.0.9", + "from": "sntp@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" + }, + "source-map": { + "version": "0.6.1", + "from": "source-map@>=0.6.1 <0.7.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + }, + "split": { + "version": "1.0.1", + "from": "split@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz" + }, + "split-ca": { + "version": "1.0.1", + "from": "split-ca@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz" + }, + "sqlite3": { + "version": "4.0.4", + "from": "sqlite3@>=4.0.2 <5.0.0", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.0.4.tgz", + "dependencies": { + "nan": { + "version": "2.10.0", + "from": "nan@>=2.10.0 <2.11.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz" + } + } + }, + "sshpk": { + "version": "1.15.2", + "from": "sshpk@>=1.7.0 <2.0.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz" + }, + "stack-trace": { + "version": "0.0.9", + "from": "stack-trace@0.0.9", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz" + }, + "statsd-parser": { + "version": "0.0.4", + "from": "statsd-parser@>=0.0.4 <0.1.0", + "resolved": "https://registry.npmjs.org/statsd-parser/-/statsd-parser-0.0.4.tgz" + }, + "statuses": { + "version": "1.5.0", + "from": "statuses@>=1.4.0 <2.0.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" + }, + "stream-shift": { + "version": "1.0.0", + "from": "stream-shift@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz" + }, + "string_decoder": { + "version": "1.1.1", + "from": "string_decoder@>=1.1.1 <1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + }, + "string-width": { + "version": "1.0.2", + "from": "string-width@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz" + }, + "stringstream": { + "version": "0.0.6", + "from": "stringstream@>=0.0.4 <0.1.0", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz" + }, + "strip-ansi": { + "version": "3.0.1", + "from": "strip-ansi@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" + }, + "strip-json-comments": { + "version": "2.0.1", + "from": "strip-json-comments@>=2.0.1 <2.1.0", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" + }, + "supports-color": { + "version": "4.4.0", + "from": "supports-color@4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "dev": true + }, + "symbol-observable": { + "version": "1.2.0", + "from": "symbol-observable@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz" + }, + "tar": { + "version": "4.4.8", + "from": "tar@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "dependencies": { + "mkdirp": { + "version": "0.5.1", + "from": "mkdirp@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" + } + } + }, + "tar-fs": { + "version": "1.16.3", + "from": "tar-fs@>=1.16.3 <1.17.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", + "dependencies": { + "mkdirp": { + "version": "0.5.1", + "from": "mkdirp@>=0.5.1 <0.6.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" + } + } + }, + "tar-pack": { + "version": "3.4.1", + "from": "tar-pack@>=3.4.0 <4.0.0", + "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.1.tgz", + "dependencies": { + "tar": { + "version": "2.2.1", + "from": "tar@>=2.2.1 <3.0.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz" + } + } + }, + "tar-stream": { + "version": "1.6.2", + "from": "tar-stream@>=1.1.2 <2.0.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz" + }, + "tdigest": { + "version": "0.1.1", + "from": "tdigest@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.1.tgz" + }, + "teeny-request": { + "version": "3.11.3", + "from": "teeny-request@>=3.11.1 <4.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-3.11.3.tgz", + "dependencies": { + "uuid": { + "version": "3.3.2", + "from": "uuid@>=3.3.2 <4.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz" + } + } + }, + "terraformer": { + "version": "1.0.9", + "from": "terraformer@>=1.0.5 <1.1.0", + "resolved": "https://registry.npmjs.org/terraformer/-/terraformer-1.0.9.tgz" + }, + "terraformer-wkt-parser": { + "version": "1.2.0", + "from": "terraformer-wkt-parser@>=1.1.2 <2.0.0", + "resolved": "https://registry.npmjs.org/terraformer-wkt-parser/-/terraformer-wkt-parser-1.2.0.tgz" + }, + "through": { + "version": "2.3.8", + "from": "through@>=2.2.7 <3.0.0", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + }, + "through2": { + "version": "2.0.5", + "from": "through2@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz" + }, + "timekeeper": { + "version": "0.0.4", + "from": "timekeeper@0.0.4", + "resolved": "https://registry.npmjs.org/timekeeper/-/timekeeper-0.0.4.tgz", + "dev": true + }, + "to-buffer": { + "version": "1.1.1", + "from": "to-buffer@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz" + }, + "toposort-class": { + "version": "1.0.1", + "from": "toposort-class@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz" + }, + "tough-cookie": { + "version": "2.4.3", + "from": "tough-cookie@>=2.4.3 <2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "dependencies": { + "punycode": { + "version": "1.4.1", + "from": "punycode@>=1.4.1 <2.0.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "from": "tunnel-agent@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz" + }, + "tweetnacl": { + "version": "0.14.5", + "from": "tweetnacl@>=0.14.0 <0.15.0", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" + }, + "type-detect": { + "version": "0.1.1", + "from": "type-detect@0.1.1", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "dev": true + }, + "type-is": { + "version": "1.6.16", + "from": "type-is@>=1.6.16 <1.7.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz" + }, + "typedarray": { + "version": "0.0.6", + "from": "typedarray@>=0.0.6 <0.0.7", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" + }, + "uid-number": { + "version": "0.0.6", + "from": "uid-number@>=0.0.6 <0.0.7", + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz" + }, + "underscore": { + "version": "1.9.1", + "from": "underscore@>=1.8.2 <2.0.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz" + }, + "underscore.string": { + "version": "2.2.1", + "from": "underscore.string@>=2.2.1 <2.3.0", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "from": "unpipe@1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + }, + "uri-js": { + "version": "4.2.2", + "from": "uri-js@>=4.2.2 <5.0.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz" + }, + "util-deprecate": { + "version": "1.0.2", + "from": "util-deprecate@>=1.0.1 <1.1.0", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + }, + "utils-merge": { + "version": "1.0.1", + "from": "utils-merge@1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" + }, + "uuid": { + "version": "3.0.0", + "from": "uuid@3.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz" + }, + "v8-profiler": { + "version": "5.7.0", + "from": "v8-profiler@>=5.2.4 <6.0.0", + "resolved": "https://registry.npmjs.org/v8-profiler/-/v8-profiler-5.7.0.tgz", + "dependencies": { + "ajv": { + "version": "4.11.8", + "from": "ajv@>=4.9.1 <5.0.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz" + }, + "assert-plus": { + "version": "0.2.0", + "from": "assert-plus@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz" + }, + "aws-sign2": { + "version": "0.6.0", + "from": "aws-sign2@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz" + }, + "form-data": { + "version": "2.1.4", + "from": "form-data@>=2.1.1 <2.2.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz" + }, + "har-schema": { + "version": "1.0.5", + "from": "har-schema@>=1.0.5 <2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz" + }, + "har-validator": { + "version": "4.2.1", + "from": "har-validator@>=4.2.1 <4.3.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz" + }, + "http-signature": { + "version": "1.1.1", + "from": "http-signature@>=1.1.0 <1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz" + }, + "mkdirp": { + "version": "0.5.1", + "from": "mkdirp@>=0.5.1 <0.6.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" + }, + "node-pre-gyp": { + "version": "0.6.39", + "from": "node-pre-gyp@>=0.6.34 <0.7.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz" + }, + "oauth-sign": { + "version": "0.8.2", + "from": "oauth-sign@>=0.8.1 <0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz" + }, + "performance-now": { + "version": "0.2.0", + "from": "performance-now@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz" + }, + "punycode": { + "version": "1.4.1", + "from": "punycode@>=1.4.1 <2.0.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" + }, + "qs": { + "version": "6.4.0", + "from": "qs@>=6.4.0 <6.5.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz" + }, + "request": { + "version": "2.81.0", + "from": "request@2.81.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz" + }, + "tar": { + "version": "2.2.1", + "from": "tar@>=2.2.1 <3.0.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz" + }, + "tough-cookie": { + "version": "2.3.4", + "from": "tough-cookie@>=2.3.0 <2.4.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz" + } + } + }, + "validator": { + "version": "10.9.0", + "from": "validator@>=10.4.0 <11.0.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.9.0.tgz" + }, + "vary": { + "version": "1.1.2", + "from": "vary@>=1.1.2 <1.2.0", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" + }, + "verror": { + "version": "1.10.0", + "from": "verror@1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz" + }, + "which": { + "version": "1.0.9", + "from": "which@>=1.0.5 <1.1.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "from": "wide-align@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz" + }, + "wkx": { + "version": "0.4.6", + "from": "wkx@>=0.4.1 <0.5.0", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.6.tgz" + }, + "wrappy": { + "version": "1.0.2", + "from": "wrappy@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + }, + "wrench": { + "version": "1.5.9", + "from": "wrench@>=1.5.4 <1.6.0", + "resolved": "https://registry.npmjs.org/wrench/-/wrench-1.5.9.tgz" + }, + "xtend": { + "version": "4.0.1", + "from": "xtend@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" + }, + "yallist": { + "version": "3.0.3", + "from": "yallist@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz" + } + } +} From 00cc5f05e57b80836e80dde76acb35855f080e63 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 8 Jan 2019 12:56:16 +0000 Subject: [PATCH 474/754] pull clsi compile size limit into setting and bump to 7mb --- services/clsi/app.coffee | 4 ++-- services/clsi/config/settings.defaults.coffee | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index 10bceee5d5..f81021e1e8 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -58,7 +58,7 @@ app.param 'build_id', (req, res, next, build_id) -> next new Error("invalid build id #{build_id}") -app.post "/project/:project_id/compile", bodyParser.json(limit: "5mb"), CompileController.compile +app.post "/project/:project_id/compile", bodyParser.json(limit: Settings.compileSizeLimit), CompileController.compile app.post "/project/:project_id/compile/stop", CompileController.stopCompile app.delete "/project/:project_id", CompileController.clearCache @@ -68,7 +68,7 @@ app.get "/project/:project_id/wordcount", CompileController.wordcount app.get "/project/:project_id/status", CompileController.status # Per-user containers -app.post "/project/:project_id/user/:user_id/compile", bodyParser.json(limit: "5mb"), CompileController.compile +app.post "/project/:project_id/user/:user_id/compile", bodyParser.json(limit: Settings.compileSizeLimit), CompileController.compile app.post "/project/:project_id/user/:user_id/compile/stop", CompileController.stopCompile app.delete "/project/:project_id/user/:user_id", CompileController.clearCache diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index 88f4532f4f..ad3f04d8bd 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -15,6 +15,8 @@ module.exports = retry: max: 10 + compileSizeLimit: process.env["COMPILE_SIZE_LIMIT"] or "7mb" + path: compilesDir: Path.resolve(__dirname + "/../compiles") clsiCacheDir: Path.resolve(__dirname + "/../cache") @@ -32,6 +34,7 @@ module.exports = apis: clsi: url: "http://#{process.env['CLSI_HOST'] or 'localhost'}:3013" + smokeTest: process.env["SMOKE_TEST"] or false project_cache_length_ms: 1000 * 60 * 60 * 24 From 6e3fd0effb630cb7f58b5af34921e7bd05543d3c Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Fri, 11 Jan 2019 10:18:37 +0000 Subject: [PATCH 475/754] Bump logger to v1.5.9 and settings to v1.1.0 --- services/clsi/npm-shrinkwrap.json | 471 +++++++++++++++++++++++------- services/clsi/package.json | 4 +- 2 files changed, 360 insertions(+), 115 deletions(-) diff --git a/services/clsi/npm-shrinkwrap.json b/services/clsi/npm-shrinkwrap.json index 4853442cd9..48e31b1101 100644 --- a/services/clsi/npm-shrinkwrap.json +++ b/services/clsi/npm-shrinkwrap.json @@ -118,6 +118,28 @@ "from": "@sindresorhus/is@>=0.13.0 <0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.13.0.tgz" }, + "@sinonjs/commons": { + "version": "1.3.0", + "from": "@sinonjs/commons@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.3.0.tgz", + "dependencies": { + "type-detect": { + "version": "4.0.8", + "from": "type-detect@4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" + } + } + }, + "@sinonjs/formatio": { + "version": "3.1.0", + "from": "@sinonjs/formatio@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.1.0.tgz" + }, + "@sinonjs/samsam": { + "version": "3.0.2", + "from": "@sinonjs/samsam@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.0.2.tgz" + }, "@types/caseless": { "version": "0.12.1", "from": "@types/caseless@*", @@ -218,19 +240,16 @@ "version": "0.1.16", "from": "argparse@>=0.1.11 <0.2.0", "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", - "dev": true, "dependencies": { "underscore": { "version": "1.7.0", "from": "underscore@>=1.7.0 <1.8.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz" }, "underscore.string": { "version": "2.4.0", "from": "underscore.string@>=2.4.0 <2.5.0", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz" } } }, @@ -239,6 +258,11 @@ "from": "array-flatten@1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" }, + "array-from": { + "version": "2.1.1", + "from": "array-from@>=2.1.1 <3.0.0", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz" + }, "arrify": { "version": "1.0.1", "from": "arrify@>=1.0.1 <2.0.0", @@ -421,6 +445,11 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.3.0.tgz", "dev": true }, + "check-error": { + "version": "1.0.2", + "from": "check-error@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz" + }, "chownr": { "version": "1.1.1", "from": "chownr@>=1.0.1 <2.0.0", @@ -444,14 +473,12 @@ "coffee-script": { "version": "1.6.0", "from": "coffee-script@1.6.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz" }, "colors": { "version": "0.6.2", "from": "colors@>=0.6.2 <0.7.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz" }, "combined-stream": { "version": "1.0.7", @@ -461,8 +488,7 @@ "commander": { "version": "2.0.0", "from": "commander@2.0.0", - "resolved": "http://registry.npmjs.org/commander/-/commander-2.0.0.tgz", - "dev": true + "resolved": "http://registry.npmjs.org/commander/-/commander-2.0.0.tgz" }, "concat-map": { "version": "0.0.1", @@ -527,8 +553,7 @@ "dateformat": { "version": "1.0.2-1.2.3", "from": "dateformat@1.0.2-1.2.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz" }, "debug": { "version": "2.6.9", @@ -579,8 +604,7 @@ "diff": { "version": "1.0.7", "from": "diff@1.0.7", - "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz" }, "docker-modem": { "version": "1.0.7", @@ -689,14 +713,12 @@ "escape-string-regexp": { "version": "1.0.5", "from": "escape-string-regexp@1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" }, "esprima": { "version": "1.0.4", "from": "esprima@>=1.0.2 <1.1.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz" }, "etag": { "version": "1.8.1", @@ -706,14 +728,12 @@ "eventemitter2": { "version": "0.4.14", "from": "eventemitter2@>=0.4.13 <0.5.0", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz" }, "exit": { "version": "0.1.2", "from": "exit@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" }, "express": { "version": "4.16.4", @@ -768,31 +788,26 @@ "version": "0.1.3", "from": "findup-sync@>=0.1.2 <0.2.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", - "dev": true, "dependencies": { "glob": { "version": "3.2.11", "from": "glob@>=3.2.9 <3.3.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz" }, "lodash": { "version": "2.4.2", "from": "lodash@>=2.4.1 <2.5.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz" }, "lru-cache": { "version": "2.7.3", "from": "lru-cache@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz" }, "minimatch": { "version": "0.3.0", "from": "minimatch@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz" } } }, @@ -890,11 +905,15 @@ "from": "generic-pool@>=3.4.0 <4.0.0", "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.4.2.tgz" }, + "get-func-name": { + "version": "2.0.0", + "from": "get-func-name@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz" + }, "getobject": { "version": "0.1.0", "from": "getobject@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz" }, "getpass": { "version": "0.1.7", @@ -931,80 +950,67 @@ "growl": { "version": "1.7.0", "from": "growl@1.7.x", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz" }, "grunt": { "version": "0.4.5", "from": "grunt@>=0.4.2 <0.5.0", "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", - "dev": true, "dependencies": { "async": { "version": "0.1.22", "from": "async@>=0.1.22 <0.2.0", - "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz" }, "coffee-script": { "version": "1.3.3", "from": "coffee-script@>=1.3.3 <1.4.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz" }, "glob": { "version": "3.1.21", "from": "glob@>=3.1.21 <3.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz" }, "graceful-fs": { "version": "1.2.3", "from": "graceful-fs@>=1.2.0 <1.3.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz" }, "iconv-lite": { "version": "0.2.11", "from": "iconv-lite@>=0.2.11 <0.3.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz" }, "inherits": { "version": "1.0.2", "from": "inherits@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz" }, "lodash": { "version": "0.9.2", "from": "lodash@>=0.9.2 <0.10.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz" }, "lru-cache": { "version": "2.7.3", "from": "lru-cache@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz" }, "minimatch": { "version": "0.2.14", "from": "minimatch@>=0.2.12 <0.3.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz" }, "nopt": { "version": "1.0.10", "from": "nopt@>=1.0.10 <1.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz" }, "rimraf": { "version": "2.2.8", "from": "rimraf@>=2.2.8 <2.3.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz" } } }, @@ -1012,13 +1018,11 @@ "version": "0.5.0", "from": "grunt-bunyan@>=0.5.0 <0.6.0", "resolved": "https://registry.npmjs.org/grunt-bunyan/-/grunt-bunyan-0.5.0.tgz", - "dev": true, "dependencies": { "lodash": { "version": "2.4.2", "from": "lodash@>=2.4.1 <2.5.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz" } } }, @@ -1060,19 +1064,16 @@ "version": "0.1.3", "from": "grunt-legacy-log@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", - "dev": true, "dependencies": { "lodash": { "version": "2.4.2", "from": "lodash@>=2.4.1 <2.5.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz" }, "underscore.string": { "version": "2.3.3", "from": "underscore.string@>=2.3.3 <2.4.0", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz" } } }, @@ -1080,19 +1081,16 @@ "version": "0.1.1", "from": "grunt-legacy-log-utils@>=0.1.1 <0.2.0", "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", - "dev": true, "dependencies": { "lodash": { "version": "2.4.2", "from": "lodash@>=2.4.1 <2.5.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz" }, "underscore.string": { "version": "2.3.3", "from": "underscore.string@>=2.3.3 <2.4.0", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz" } } }, @@ -1100,19 +1098,16 @@ "version": "0.2.0", "from": "grunt-legacy-util@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", - "dev": true, "dependencies": { "async": { "version": "0.1.22", "from": "async@>=0.1.22 <0.2.0", - "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz" }, "lodash": { "version": "0.9.2", "from": "lodash@>=0.9.2 <0.10.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz" } } }, @@ -1192,6 +1187,18 @@ "from": "har-validator@>=5.1.0 <5.2.0", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz" }, + "has-ansi": { + "version": "0.1.0", + "from": "has-ansi@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", + "dependencies": { + "ansi-regex": { + "version": "0.2.1", + "from": "ansi-regex@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz" + } + } + }, "has-color": { "version": "0.1.7", "from": "has-color@>=0.1.0 <0.2.0", @@ -1238,8 +1245,7 @@ "hooker": { "version": "0.2.3", "from": "hooker@>=0.2.3 <0.3.0", - "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz" }, "http-errors": { "version": "1.6.3", @@ -1342,27 +1348,23 @@ "version": "0.26.3", "from": "jade@0.26.3", "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", - "dev": true, "dependencies": { "commander": { "version": "0.6.1", "from": "commander@0.6.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz" }, "mkdirp": { "version": "0.3.0", "from": "mkdirp@0.3.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz" } } }, "js-yaml": { "version": "2.0.5", "from": "js-yaml@>=2.0.5 <2.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz" }, "jsbn": { "version": "0.1.1", @@ -1427,6 +1429,11 @@ "from": "jsprim@>=1.2.2 <2.0.0", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz" }, + "just-extend": { + "version": "4.0.2", + "from": "just-extend@>=4.0.2 <5.0.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz" + }, "jwa": { "version": "1.1.6", "from": "jwa@>=1.1.5 <2.0.0", @@ -1447,15 +1454,204 @@ "from": "lodash@>=4.17.1 <5.0.0", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz" }, + "lodash.get": { + "version": "4.4.2", + "from": "lodash.get@>=4.4.2 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz" + }, "lodash.pickby": { "version": "4.6.0", "from": "lodash.pickby@>=4.6.0 <5.0.0", "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz" }, "logger-sharelatex": { - "version": "1.5.4", - "from": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", - "resolved": "git+https://github.com/sharelatex/logger-sharelatex.git#9ee7b52eb2bbd8fcbb1e2c708587c1e93fd4c733" + "version": "1.5.9", + "from": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.9", + "resolved": "git+https://github.com/sharelatex/logger-sharelatex.git#e8e1b95052f62e107336053e4a983f81cdbdf589", + "dependencies": { + "ansi-regex": { + "version": "0.2.1", + "from": "ansi-regex@^0.2.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz" + }, + "ansi-styles": { + "version": "1.1.0", + "from": "ansi-styles@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz" + }, + "assertion-error": { + "version": "1.1.0", + "from": "assertion-error@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz" + }, + "bunyan": { + "version": "1.5.1", + "from": "bunyan@1.5.1", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.5.1.tgz" + }, + "chai": { + "version": "4.2.0", + "from": "chai@latest", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz" + }, + "chalk": { + "version": "0.5.1", + "from": "chalk@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz" + }, + "coffee-script": { + "version": "1.12.4", + "from": "coffee-script@1.12.4", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.4.tgz" + }, + "deep-eql": { + "version": "3.0.1", + "from": "deep-eql@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz" + }, + "dtrace-provider": { + "version": "0.6.0", + "from": "dtrace-provider@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.6.0.tgz", + "optional": true + }, + "fs-extra": { + "version": "0.9.1", + "from": "fs-extra@>=0.9.1 <0.10.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.9.1.tgz", + "dependencies": { + "mkdirp": { + "version": "0.5.1", + "from": "mkdirp@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" + } + } + }, + "glob": { + "version": "3.2.3", + "from": "glob@3.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz" + }, + "graceful-fs": { + "version": "2.0.3", + "from": "graceful-fs@>=2.0.0 <2.1.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz" + }, + "grunt-contrib-clean": { + "version": "0.6.0", + "from": "grunt-contrib-clean@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-0.6.0.tgz" + }, + "grunt-contrib-coffee": { + "version": "0.11.1", + "from": "grunt-contrib-coffee@>=0.11.0 <0.12.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-coffee/-/grunt-contrib-coffee-0.11.1.tgz", + "dependencies": { + "coffee-script": { + "version": "1.7.1", + "from": "coffee-script@>=1.7.0 <1.8.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.7.1.tgz" + } + } + }, + "grunt-execute": { + "version": "0.2.2", + "from": "grunt-execute@>=0.2.2 <0.3.0", + "resolved": "https://registry.npmjs.org/grunt-execute/-/grunt-execute-0.2.2.tgz" + }, + "grunt-mocha-test": { + "version": "0.11.0", + "from": "grunt-mocha-test@>=0.11.0 <0.12.0", + "resolved": "https://registry.npmjs.org/grunt-mocha-test/-/grunt-mocha-test-0.11.0.tgz" + }, + "has-flag": { + "version": "3.0.0", + "from": "has-flag@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" + }, + "jsonfile": { + "version": "1.1.1", + "from": "jsonfile@>=1.1.0 <1.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.1.1.tgz" + }, + "lodash": { + "version": "2.4.2", + "from": "lodash@>=2.4.1 <2.5.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz" + }, + "lru-cache": { + "version": "2.7.3", + "from": "lru-cache@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz" + }, + "minimatch": { + "version": "0.2.14", + "from": "minimatch@>=0.2.11 <0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz" + }, + "mocha": { + "version": "1.20.1", + "from": "mocha@>=1.20.0 <1.21.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.20.1.tgz" + }, + "ncp": { + "version": "0.5.1", + "from": "ncp@>=0.5.1 <0.6.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.5.1.tgz" + }, + "rimraf": { + "version": "2.2.8", + "from": "rimraf@>=2.2.1 <2.3.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz" + }, + "sandboxed-module": { + "version": "2.0.3", + "from": "sandboxed-module@latest", + "resolved": "https://registry.npmjs.org/sandboxed-module/-/sandboxed-module-2.0.3.tgz" + }, + "sinon": { + "version": "7.2.2", + "from": "sinon@latest", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.2.tgz", + "dependencies": { + "diff": { + "version": "3.5.0", + "from": "diff@>=3.5.0 <4.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz" + }, + "supports-color": { + "version": "5.5.0", + "from": "supports-color@>=5.5.0 <6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" + } + } + }, + "strip-ansi": { + "version": "0.3.0", + "from": "strip-ansi@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz" + }, + "supports-color": { + "version": "0.2.0", + "from": "supports-color@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz" + }, + "timekeeper": { + "version": "1.0.0", + "from": "timekeeper@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/timekeeper/-/timekeeper-1.0.0.tgz" + }, + "type-detect": { + "version": "4.0.8", + "from": "type-detect@>=4.0.5 <5.0.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" + } + } + }, + "lolex": { + "version": "3.0.0", + "from": "lolex@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-3.0.0.tgz" }, "long": { "version": "4.0.0", @@ -1627,28 +1823,24 @@ "version": "2.1.1", "from": "mv@~2", "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", - "dev": true, "optional": true, "dependencies": { "glob": { "version": "6.0.4", "from": "glob@>=6.0.1 <7.0.0", "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "dev": true, "optional": true }, "mkdirp": { "version": "0.5.1", "from": "mkdirp@>=0.5.1 <0.6.0", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "dev": true, "optional": true }, "rimraf": { "version": "2.4.5", "from": "rimraf@>=2.4.0 <2.5.0", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", - "dev": true, "optional": true } } @@ -1694,7 +1886,6 @@ "version": "2.0.0", "from": "ncp@~2.0.0", "resolved": "http://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "dev": true, "optional": true }, "needle": { @@ -1707,6 +1898,28 @@ "from": "negotiator@0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz" }, + "nise": { + "version": "1.4.8", + "from": "nise@>=1.4.7 <2.0.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.8.tgz", + "dependencies": { + "isarray": { + "version": "0.0.1", + "from": "isarray@0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + }, + "lolex": { + "version": "2.7.5", + "from": "lolex@>=2.3.2 <3.0.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz" + }, + "path-to-regexp": { + "version": "1.7.0", + "from": "path-to-regexp@>=1.7.0 <2.0.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz" + } + } + }, "node-fetch": { "version": "2.3.0", "from": "node-fetch@>=2.2.0 <3.0.0", @@ -1829,6 +2042,11 @@ "from": "path-to-regexp@0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" }, + "pathval": { + "version": "1.1.0", + "from": "pathval@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz" + }, "performance-now": { "version": "2.1.0", "from": "performance-now@>=2.1.0 <3.0.0", @@ -1941,8 +2159,7 @@ "require-like": { "version": "0.1.2", "from": "require-like@0.1.2", - "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz" }, "resolve": { "version": "1.9.0", @@ -1974,6 +2191,12 @@ "from": "safe-buffer@>=5.1.1 <5.2.0", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" }, + "safe-json-stringify": { + "version": "1.2.0", + "from": "safe-json-stringify@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", + "optional": true + }, "safer-buffer": { "version": "2.1.2", "from": "safer-buffer@>=2.1.2 <3.0.0", @@ -2053,16 +2276,9 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz" }, "settings-sharelatex": { - "version": "1.0.0", - "from": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", - "resolved": "git+https://github.com/sharelatex/settings-sharelatex.git#cbc5e41c1dbe6789721a14b3fdae05bf22546559", - "dependencies": { - "coffee-script": { - "version": "1.6.0", - "from": "coffee-script@1.6.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz" - } - } + "version": "1.1.0", + "from": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.1.0", + "resolved": "git+https://github.com/sharelatex/settings-sharelatex.git#93f63d029b52fef8825c3a401b2b6a7ba29b4750" }, "shimmer": { "version": "1.2.0", @@ -2072,8 +2288,7 @@ "sigmund": { "version": "1.0.1", "from": "sigmund@~1.0.0", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz" }, "signal-exit": { "version": "3.0.2", @@ -2089,7 +2304,34 @@ "smoke-test-sharelatex": { "version": "1.0.1", "from": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", - "resolved": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#bc3e93d18ccee219c0d99e8b02c984ccdd842e1c" + "resolved": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#bc3e93d18ccee219c0d99e8b02c984ccdd842e1c", + "dependencies": { + "glob": { + "version": "3.2.3", + "from": "glob@3.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz" + }, + "graceful-fs": { + "version": "2.0.3", + "from": "graceful-fs@>=2.0.0 <2.1.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz" + }, + "lru-cache": { + "version": "2.7.3", + "from": "lru-cache@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz" + }, + "minimatch": { + "version": "0.2.14", + "from": "minimatch@>=0.2.11 <0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz" + }, + "mocha": { + "version": "1.17.1", + "from": "mocha@>=1.17.0 <1.18.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.17.1.tgz" + } + } }, "sntp": { "version": "1.0.9", @@ -2252,6 +2494,11 @@ "from": "terraformer-wkt-parser@>=1.1.2 <2.0.0", "resolved": "https://registry.npmjs.org/terraformer-wkt-parser/-/terraformer-wkt-parser-1.2.0.tgz" }, + "text-encoding": { + "version": "0.6.4", + "from": "text-encoding@>=0.6.4 <0.7.0", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz" + }, "through": { "version": "2.3.8", "from": "through@>=2.2.7 <3.0.0", @@ -2329,8 +2576,7 @@ "underscore.string": { "version": "2.2.1", "from": "underscore.string@>=2.2.1 <2.3.0", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz" }, "unpipe": { "version": "1.0.0", @@ -2462,8 +2708,7 @@ "which": { "version": "1.0.9", "from": "which@>=1.0.5 <1.1.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz" }, "wide-align": { "version": "1.1.3", diff --git a/services/clsi/package.json b/services/clsi/package.json index 49f5873ed7..f981e5fac6 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -29,14 +29,14 @@ "grunt-mkdir": "^1.0.0", "heapdump": "^0.3.5", "lockfile": "^1.0.3", - "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.4", + "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.9", "lynx": "0.0.11", "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v2.0.12", "mkdirp": "0.3.5", "mysql": "2.6.2", "request": "^2.21.0", "sequelize": "^4.38.0", - "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", + "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.1.0", "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", "sqlite3": "^4.0.2", "underscore": "^1.8.2", From fe60edca561d45710c932d2275b23450bb4806ca Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Fri, 11 Jan 2019 10:19:47 +0000 Subject: [PATCH 476/754] Init metrics at top of app.coffee --- services/clsi/app.coffee | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index f81021e1e8..ec9fc80311 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -1,3 +1,6 @@ +Metrics = require "metrics-sharelatex" +Metrics.initialize("clsi") + CompileController = require "./app/js/CompileController" Settings = require "settings-sharelatex" logger = require "logger-sharelatex" @@ -12,8 +15,7 @@ Errors = require './app/js/Errors' Path = require "path" fs = require "fs" -Metrics = require "metrics-sharelatex" -Metrics.initialize("clsi") + Metrics.open_sockets.monitor(logger) Metrics.memory.monitor(logger) From cc00d5311be552d472717142caec8e429dc1c2fd Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Fri, 11 Jan 2019 11:52:10 +0000 Subject: [PATCH 477/754] Bump buildscript to 1.1.11 --- services/clsi/.github/ISSUE_TEMPLATE.md | 38 ++++++++++++++++ .../clsi/.github/PULL_REQUEST_TEMPLATE.md | 45 +++++++++++++++++++ services/clsi/Dockerfile | 2 +- services/clsi/Makefile | 2 +- services/clsi/buildscript.txt | 9 ++++ services/clsi/docker-compose.ci.yml | 3 +- services/clsi/docker-compose.yml | 2 +- services/clsi/package.json | 2 +- 8 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 services/clsi/.github/ISSUE_TEMPLATE.md create mode 100644 services/clsi/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 services/clsi/buildscript.txt diff --git a/services/clsi/.github/ISSUE_TEMPLATE.md b/services/clsi/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..e0093aa90c --- /dev/null +++ b/services/clsi/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,38 @@ +<!-- BUG REPORT TEMPLATE --> + +## Steps to Reproduce +<!-- Describe the steps leading up to when / where you found the bug. --> +<!-- Screenshots may be helpful here. --> + +1. +2. +3. + +## Expected Behaviour +<!-- What should have happened when you completed the steps above? --> + +## Observed Behaviour +<!-- What actually happened when you completed the steps above? --> +<!-- Screenshots may be helpful here. --> + +## Context +<!-- How has this issue affected you? What were you trying to accomplish? --> + +## Technical Info +<!-- Provide any technical details that may be applicable (or N/A if not applicable). --> + +* URL: +* Browser Name and version: +* Operating System and version (desktop or mobile): +* Signed in as: +* Project and/or file: + +## Analysis +<!--- Optionally, document investigation of / suggest a fix for the bug, e.g. 'comes from this line / commit' --> + +## Who Needs to Know? +<!-- If you want to bring this to the attention of particular people, @-mention them below. --> +<!-- If a user reported this bug and should be notified when it is fixed, provide the Front conversation link. --> + +- +- diff --git a/services/clsi/.github/PULL_REQUEST_TEMPLATE.md b/services/clsi/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..ed25ee83c1 --- /dev/null +++ b/services/clsi/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,45 @@ +<!-- Please review https://github.com/overleaf/write_latex/blob/master/.github/CONTRIBUTING.md for guidance on what is expected in each section. --> + +### Description + + + +#### Screenshots + + + +#### Related Issues / PRs + + + +### Review + + + +#### Potential Impact + + + +#### Manual Testing Performed + +- [ ] +- [ ] + +#### Accessibility + + + +### Deployment + + + +#### Deployment Checklist + +- [ ] Update documentation not included in the PR (if any) +- [ ] + +#### Metrics and Monitoring + + + +#### Who Needs to Know? diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index ea550b2892..861da2a06e 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -20,4 +20,4 @@ WORKDIR /app RUN chmod 0755 ./install_deps.sh && ./install_deps.sh ENTRYPOINT ["/bin/sh", "entrypoint.sh"] -CMD ["node","app.js"] +CMD ["node", "--expose-gc", "app.js"] diff --git a/services/clsi/Makefile b/services/clsi/Makefile index b1ce2934af..e7a1e341df 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.9 +# Version: 1.1.11 BUILD_NUMBER ?= local BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt new file mode 100644 index 0000000000..abdf7ed9bf --- /dev/null +++ b/services/clsi/buildscript.txt @@ -0,0 +1,9 @@ +--script-version=1.1.11 +clsi +--node-version=6.15.1 +--acceptance-creds=None +--language=coffeescript +--dependencies=mongo,redis +--docker-repos=gcr.io/overleaf-ops +--kube=false +--build-target=docker diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index f6c8a27fd0..698c5cb1b9 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.9 +# Version: 1.1.11 version: "2" @@ -10,6 +10,7 @@ services: image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER command: npm run test:unit:_run + test_acceptance: build: . image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 371e6e768d..5c3c4c86a6 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.9 +# Version: 1.1.11 version: "2" diff --git a/services/clsi/package.json b/services/clsi/package.json index f981e5fac6..a0e0a43bce 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -7,7 +7,7 @@ "url": "https://github.com/sharelatex/clsi-sharelatex.git" }, "scripts": { - "compile:app": "([ -e app/coffee ] && coffee $COFFEE_OPTIONS -o app/js -c app/coffee || echo 'No CoffeeScript folder to compile') && ( [ -e app.coffee ] && coffee $COFFEE_OPTIONS -c app.coffee || echo 'No CoffeeScript app to compile')", + "compile:app": "([ -e app/coffee ] && coffee -m $COFFEE_OPTIONS -o app/js -c app/coffee || echo 'No CoffeeScript folder to compile') && ( [ -e app.coffee ] && coffee -m $COFFEE_OPTIONS -c app.coffee || echo 'No CoffeeScript app to compile')", "start": "npm run compile:app && node $NODE_APP_OPTIONS app.js", "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 30000 --exit $@ test/acceptance/js", "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- --grep=$MOCHA_GREP", From 4aa2f7de328355eb1997baee96635b81556c9376 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Fri, 11 Jan 2019 12:06:45 +0000 Subject: [PATCH 478/754] Remove grunt --- services/clsi/Gruntfile.coffee | 104 ----------- services/clsi/npm-shrinkwrap.json | 280 ++++++++++-------------------- services/clsi/package.json | 20 +-- 3 files changed, 100 insertions(+), 304 deletions(-) delete mode 100644 services/clsi/Gruntfile.coffee diff --git a/services/clsi/Gruntfile.coffee b/services/clsi/Gruntfile.coffee deleted file mode 100644 index a30f141ed5..0000000000 --- a/services/clsi/Gruntfile.coffee +++ /dev/null @@ -1,104 +0,0 @@ -spawn = require("child_process").spawn - -module.exports = (grunt) -> - grunt.initConfig - coffee: - app_src: - expand: true, - flatten: true, - cwd: "app" - src: ['coffee/*.coffee'], - dest: 'app/js/', - ext: '.js' - - app: - src: "app.coffee" - dest: "app.js" - - unit_tests: - expand: true - cwd: "test/unit/coffee" - src: ["**/*.coffee"] - dest: "test/unit/js/" - ext: ".js" - - acceptance_tests: - expand: true - cwd: "test/acceptance/coffee" - src: ["**/*.coffee"] - dest: "test/acceptance/js/" - ext: ".js" - - smoke_tests: - expand: true - cwd: "test/smoke/coffee" - src: ["**/*.coffee"] - dest: "test/smoke/js" - ext: ".js" - - clean: - app: ["app/js/"] - unit_tests: ["test/unit/js"] - acceptance_tests: ["test/acceptance/js"] - smoke_tests: ["test/smoke/js"] - - execute: - app: - src: "app.js" - - mkdir: - all: - options: - create: ["cache", "compiles"] - - mochaTest: - unit: - options: - reporter: "spec" - grep: grunt.option("grep") - src: ["test/unit/js/**/*.js"] - acceptance: - options: - reporter: "spec" - timeout: 40000 - grep: grunt.option("grep") - src: ["test/acceptance/js/**/*.js"] - smoke: - options: - reported: "spec" - timeout: 10000 - src: ["test/smoke/js/**/*.js"] - - grunt.loadNpmTasks 'grunt-contrib-coffee' - grunt.loadNpmTasks 'grunt-contrib-clean' - grunt.loadNpmTasks 'grunt-mocha-test' - grunt.loadNpmTasks 'grunt-shell' - grunt.loadNpmTasks 'grunt-execute' - grunt.loadNpmTasks 'grunt-bunyan' - grunt.loadNpmTasks 'grunt-mkdir' - - grunt.registerTask 'compile:bin', () -> - callback = @async() - proc = spawn "cc", [ - "-o", "bin/synctex", "-Isrc/synctex", - "src/synctex.c", "src/synctex/synctex_parser.c", "src/synctex/synctex_parser_utils.c", "-lz" - ], stdio: "inherit" - proc.on "close", callback - - grunt.registerTask 'compile:app', ['clean:app', 'coffee:app', 'coffee:app_src', 'coffee:smoke_tests', 'compile:bin'] - grunt.registerTask 'run', ['compile:app', 'bunyan', 'execute'] - - grunt.registerTask 'compile:unit_tests', ['clean:unit_tests', 'coffee:unit_tests'] - grunt.registerTask 'test:unit', ['compile:app', 'compile:unit_tests', 'mochaTest:unit'] - - grunt.registerTask 'compile:acceptance_tests', ['clean:acceptance_tests', 'coffee:acceptance_tests'] - grunt.registerTask 'test:acceptance', ['compile:acceptance_tests', 'mochaTest:acceptance'] - - grunt.registerTask 'compile:smoke_tests', ['clean:smoke_tests', 'coffee:smoke_tests'] - grunt.registerTask 'test:smoke', ['compile:smoke_tests', 'mochaTest:smoke'] - - grunt.registerTask 'install', 'compile:app' - - grunt.registerTask 'default', ['mkdir', 'run'] - - diff --git a/services/clsi/npm-shrinkwrap.json b/services/clsi/npm-shrinkwrap.json index 48e31b1101..83b6b5037b 100644 --- a/services/clsi/npm-shrinkwrap.json +++ b/services/clsi/npm-shrinkwrap.json @@ -221,10 +221,9 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" }, "ansi-styles": { - "version": "0.2.0", - "from": "ansi-styles@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-0.2.0.tgz", - "dev": true + "version": "1.1.0", + "from": "ansi-styles@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz" }, "aproba": { "version": "1.2.0", @@ -440,10 +439,26 @@ "dev": true }, "chalk": { - "version": "0.3.0", - "from": "chalk@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.3.0.tgz", - "dev": true + "version": "0.5.1", + "from": "chalk@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", + "dependencies": { + "ansi-regex": { + "version": "0.2.1", + "from": "ansi-regex@^0.2.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz" + }, + "strip-ansi": { + "version": "0.3.0", + "from": "strip-ansi@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz" + }, + "supports-color": { + "version": "0.2.0", + "from": "supports-color@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz" + } + } }, "check-error": { "version": "1.0.2", @@ -954,7 +969,7 @@ }, "grunt": { "version": "0.4.5", - "from": "grunt@>=0.4.2 <0.5.0", + "from": "grunt@>=0.4.5 <0.5.0", "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", "dependencies": { "async": { @@ -994,7 +1009,7 @@ }, "lru-cache": { "version": "2.7.3", - "from": "lru-cache@>=2.0.0 <3.0.0", + "from": "lru-cache@2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz" }, "minimatch": { @@ -1027,38 +1042,38 @@ } }, "grunt-contrib-clean": { - "version": "0.5.0", - "from": "grunt-contrib-clean@>=0.5.0 <0.6.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-0.5.0.tgz", - "dev": true, + "version": "0.6.0", + "from": "grunt-contrib-clean@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-0.6.0.tgz", "dependencies": { "rimraf": { "version": "2.2.8", - "from": "rimraf@>=2.2.1 <2.3.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "dev": true + "from": "rimraf@~2.2.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz" } } }, "grunt-contrib-coffee": { - "version": "0.7.0", - "from": "grunt-contrib-coffee@>=0.7.0 <0.8.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-coffee/-/grunt-contrib-coffee-0.7.0.tgz", - "dev": true, + "version": "0.11.1", + "from": "grunt-contrib-coffee@>=0.11.0 <0.12.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-coffee/-/grunt-contrib-coffee-0.11.1.tgz", "dependencies": { "coffee-script": { - "version": "1.6.3", - "from": "coffee-script@>=1.6.2 <1.7.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.3.tgz", - "dev": true + "version": "1.7.1", + "from": "coffee-script@>=1.7.0 <1.8.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.7.1.tgz" + }, + "lodash": { + "version": "2.4.2", + "from": "lodash@>=2.4.1 <2.5.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz" } } }, "grunt-execute": { - "version": "0.1.5", - "from": "grunt-execute@>=0.1.5 <0.2.0", - "resolved": "https://registry.npmjs.org/grunt-execute/-/grunt-execute-0.1.5.tgz", - "dev": true + "version": "0.2.2", + "from": "grunt-execute@>=0.2.2 <0.3.0", + "resolved": "https://registry.npmjs.org/grunt-execute/-/grunt-execute-0.2.2.tgz" }, "grunt-legacy-log": { "version": "0.1.3", @@ -1101,65 +1116,75 @@ "dependencies": { "async": { "version": "0.1.22", - "from": "async@>=0.1.22 <0.2.0", + "from": "async@~0.1.22", "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz" }, "lodash": { "version": "0.9.2", - "from": "lodash@>=0.9.2 <0.10.0", + "from": "lodash@~0.9.2", "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz" } } }, - "grunt-mkdir": { - "version": "1.0.0", - "from": "grunt-mkdir@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/grunt-mkdir/-/grunt-mkdir-1.0.0.tgz" - }, "grunt-mocha-test": { - "version": "0.8.2", - "from": "grunt-mocha-test@>=0.8.1 <0.9.0", - "resolved": "https://registry.npmjs.org/grunt-mocha-test/-/grunt-mocha-test-0.8.2.tgz", - "dev": true, + "version": "0.11.0", + "from": "grunt-mocha-test@>=0.11.0 <0.12.0", + "resolved": "https://registry.npmjs.org/grunt-mocha-test/-/grunt-mocha-test-0.11.0.tgz", "dependencies": { + "fs-extra": { + "version": "0.9.1", + "from": "fs-extra@>=0.9.1 <0.10.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.9.1.tgz" + }, "glob": { "version": "3.2.3", "from": "glob@3.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz" }, "graceful-fs": { "version": "2.0.3", "from": "graceful-fs@>=2.0.0 <2.1.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz" + }, + "jsonfile": { + "version": "1.1.1", + "from": "jsonfile@>=1.1.0 <1.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.1.1.tgz" }, "lru-cache": { "version": "2.7.3", - "from": "lru-cache@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "dev": true + "from": "lru-cache@2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz" }, "minimatch": { "version": "0.2.14", - "from": "minimatch@>=0.2.11 <0.3.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "dev": true + "from": "minimatch@~0.2.11", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz" + }, + "mkdirp": { + "version": "0.5.1", + "from": "mkdirp@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" }, "mocha": { - "version": "1.14.0", - "from": "mocha@>=1.14.0 <1.15.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.14.0.tgz", - "dev": true + "version": "1.20.1", + "from": "mocha@>=1.20.0 <1.21.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.20.1.tgz", + "dependencies": { + "mkdirp": { + "version": "0.3.5", + "from": "mkdirp@0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz" + } + } + }, + "ncp": { + "version": "0.5.1", + "from": "ncp@>=0.5.1 <0.6.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.5.1.tgz" } } }, - "grunt-shell": { - "version": "0.6.4", - "from": "grunt-shell@>=0.6.1 <0.7.0", - "resolved": "https://registry.npmjs.org/grunt-shell/-/grunt-shell-0.6.4.tgz", - "dev": true - }, "gtoken": { "version": "2.3.0", "from": "gtoken@>=2.3.0 <3.0.0", @@ -1199,12 +1224,6 @@ } } }, - "has-color": { - "version": "0.1.7", - "from": "has-color@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", - "dev": true - }, "has-flag": { "version": "2.0.0", "from": "has-flag@>=2.0.0 <3.0.0", @@ -1469,16 +1488,6 @@ "from": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.9", "resolved": "git+https://github.com/sharelatex/logger-sharelatex.git#e8e1b95052f62e107336053e4a983f81cdbdf589", "dependencies": { - "ansi-regex": { - "version": "0.2.1", - "from": "ansi-regex@^0.2.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz" - }, - "ansi-styles": { - "version": "1.1.0", - "from": "ansi-styles@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz" - }, "assertion-error": { "version": "1.1.0", "from": "assertion-error@>=1.1.0 <2.0.0", @@ -1494,11 +1503,6 @@ "from": "chai@latest", "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz" }, - "chalk": { - "version": "0.5.1", - "from": "chalk@>=0.5.0 <0.6.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz" - }, "coffee-script": { "version": "1.12.4", "from": "coffee-script@1.12.4", @@ -1509,101 +1513,22 @@ "from": "deep-eql@>=3.0.1 <4.0.0", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz" }, + "diff": { + "version": "3.5.0", + "from": "diff@>=3.5.0 <4.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz" + }, "dtrace-provider": { "version": "0.6.0", "from": "dtrace-provider@>=0.6.0 <0.7.0", "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.6.0.tgz", "optional": true }, - "fs-extra": { - "version": "0.9.1", - "from": "fs-extra@>=0.9.1 <0.10.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.9.1.tgz", - "dependencies": { - "mkdirp": { - "version": "0.5.1", - "from": "mkdirp@>=0.5.0 <0.6.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" - } - } - }, - "glob": { - "version": "3.2.3", - "from": "glob@3.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz" - }, - "graceful-fs": { - "version": "2.0.3", - "from": "graceful-fs@>=2.0.0 <2.1.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz" - }, - "grunt-contrib-clean": { - "version": "0.6.0", - "from": "grunt-contrib-clean@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-0.6.0.tgz" - }, - "grunt-contrib-coffee": { - "version": "0.11.1", - "from": "grunt-contrib-coffee@>=0.11.0 <0.12.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-coffee/-/grunt-contrib-coffee-0.11.1.tgz", - "dependencies": { - "coffee-script": { - "version": "1.7.1", - "from": "coffee-script@>=1.7.0 <1.8.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.7.1.tgz" - } - } - }, - "grunt-execute": { - "version": "0.2.2", - "from": "grunt-execute@>=0.2.2 <0.3.0", - "resolved": "https://registry.npmjs.org/grunt-execute/-/grunt-execute-0.2.2.tgz" - }, - "grunt-mocha-test": { - "version": "0.11.0", - "from": "grunt-mocha-test@>=0.11.0 <0.12.0", - "resolved": "https://registry.npmjs.org/grunt-mocha-test/-/grunt-mocha-test-0.11.0.tgz" - }, "has-flag": { "version": "3.0.0", "from": "has-flag@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" }, - "jsonfile": { - "version": "1.1.1", - "from": "jsonfile@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.1.1.tgz" - }, - "lodash": { - "version": "2.4.2", - "from": "lodash@>=2.4.1 <2.5.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz" - }, - "lru-cache": { - "version": "2.7.3", - "from": "lru-cache@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz" - }, - "minimatch": { - "version": "0.2.14", - "from": "minimatch@>=0.2.11 <0.3.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz" - }, - "mocha": { - "version": "1.20.1", - "from": "mocha@>=1.20.0 <1.21.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.20.1.tgz" - }, - "ncp": { - "version": "0.5.1", - "from": "ncp@>=0.5.1 <0.6.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.5.1.tgz" - }, - "rimraf": { - "version": "2.2.8", - "from": "rimraf@>=2.2.1 <2.3.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz" - }, "sandboxed-module": { "version": "2.0.3", "from": "sandboxed-module@latest", @@ -1612,29 +1537,12 @@ "sinon": { "version": "7.2.2", "from": "sinon@latest", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.2.tgz", - "dependencies": { - "diff": { - "version": "3.5.0", - "from": "diff@>=3.5.0 <4.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz" - }, - "supports-color": { - "version": "5.5.0", - "from": "supports-color@>=5.5.0 <6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" - } - } - }, - "strip-ansi": { - "version": "0.3.0", - "from": "strip-ansi@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz" + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.2.tgz" }, "supports-color": { - "version": "0.2.0", - "from": "supports-color@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz" + "version": "5.5.0", + "from": "supports-color@>=5.5.0 <6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" }, "timekeeper": { "version": "1.0.0", diff --git a/services/clsi/package.json b/services/clsi/package.json index a0e0a43bce..065cdce62b 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -26,7 +26,6 @@ "dockerode": "^2.5.3", "express": "^4.2.0", "fs-extra": "^0.16.3", - "grunt-mkdir": "^1.0.0", "heapdump": "^0.3.5", "lockfile": "^1.0.3", "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.9", @@ -44,19 +43,12 @@ "wrench": "~1.5.4" }, "devDependencies": { - "mocha": "^4.0.1", - "coffee-script": "1.6.0", - "chai": "~1.8.1", - "sinon": "~1.7.3", - "grunt": "~0.4.2", - "grunt-contrib-coffee": "~0.7.0", - "grunt-contrib-clean": "~0.5.0", - "grunt-shell": "~0.6.1", - "grunt-mocha-test": "~0.8.1", - "sandboxed-module": "~0.3.0", - "timekeeper": "0.0.4", - "grunt-execute": "^0.1.5", "bunyan": "^0.22.1", - "grunt-bunyan": "^0.5.0" + "chai": "~1.8.1", + "coffee-script": "1.6.0", + "mocha": "^4.0.1", + "sandboxed-module": "~0.3.0", + "sinon": "~1.7.3", + "timekeeper": "0.0.4" } } From f39b51de047378ce53f353d467f6fb97fd6e7e09 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Fri, 11 Jan 2019 12:11:36 +0000 Subject: [PATCH 479/754] Add **/*.map to .gitignore --- services/clsi/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/.gitignore b/services/clsi/.gitignore index 7fb78eefa0..048a75b9a8 100644 --- a/services/clsi/.gitignore +++ b/services/clsi/.gitignore @@ -7,6 +7,7 @@ test/acceptance/js test/acceptance/fixtures/tmp compiles app.js +**/*.map .DS_Store *~ cache From b826b6ce627239e047a82f3e0506595df4c74ef9 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Tue, 15 Jan 2019 11:12:21 +0000 Subject: [PATCH 480/754] Pass arguments to node, not to runuser --- services/clsi/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh index cb2580cbbf..f6d4bb1e63 100755 --- a/services/clsi/entrypoint.sh +++ b/services/clsi/entrypoint.sh @@ -20,4 +20,4 @@ chown -R node:node /app chown -R node:node /app/bin ./bin/install_texlive_gce.sh -exec runuser -u node "$@" \ No newline at end of file +exec runuser -u node "$*" From 3e747542ac81f6c5235cae808ec6aa37122df973 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Tue, 15 Jan 2019 11:29:04 +0000 Subject: [PATCH 481/754] Correctly pass command with arguments to runuser --- services/clsi/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh index f6d4bb1e63..ea295c7e10 100755 --- a/services/clsi/entrypoint.sh +++ b/services/clsi/entrypoint.sh @@ -20,4 +20,4 @@ chown -R node:node /app chown -R node:node /app/bin ./bin/install_texlive_gce.sh -exec runuser -u node "$*" +exec runuser -u node -- "$@" From 7a3f1c81bd90bb687b2325eafa7baec9e92c0406 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Wed, 16 Jan 2019 15:11:49 +0000 Subject: [PATCH 482/754] Switch to node 10 --- services/clsi/Dockerfile | 4 +- services/clsi/app.coffee | 2 +- services/clsi/npm-shrinkwrap.json | 3041 +++++++++++------ services/clsi/package.json | 6 +- .../coffee/ExampleDocumentTests.coffee | 5 +- 5 files changed, 1990 insertions(+), 1068 deletions(-) diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 861da2a06e..d2bcb1efe2 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -1,4 +1,4 @@ -FROM node:6.15.1 as app +FROM node:10.15.0 as app WORKDIR /app @@ -12,7 +12,7 @@ COPY . /app RUN npm run compile:all -FROM node:6.15.1 +FROM node:10.15.0 COPY --from=app /app /app diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index ec9fc80311..fcf67c3ca5 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -146,7 +146,7 @@ app.get "/health_check", (req, res)-> app.get "/smoke_test_force", (req, res)-> smokeTest.run(require.resolve(__dirname + "/test/smoke/js/SmokeTests.js"))(req, res) -profiler = require "v8-profiler" +profiler = require "v8-profiler-node8" app.get "/profile", (req, res) -> time = parseInt(req.query.time || "1000") profiler.startProfiling("test") diff --git a/services/clsi/npm-shrinkwrap.json b/services/clsi/npm-shrinkwrap.json index 83b6b5037b..9fe83ed3ad 100644 --- a/services/clsi/npm-shrinkwrap.json +++ b/services/clsi/npm-shrinkwrap.json @@ -1,2652 +1,3571 @@ { "name": "node-clsi", "version": "0.1.4", + "lockfileVersion": 1, + "requires": true, "dependencies": { "@google-cloud/common": { "version": "0.27.0", - "from": "@google-cloud/common@>=0.27.0 <0.28.0", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.27.0.tgz" + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.27.0.tgz", + "integrity": "sha1-MdvVLXRy8mBt8FsZSIfbK1lb2tU=", + "requires": { + "@google-cloud/projectify": "^0.3.2", + "@google-cloud/promisify": "^0.3.0", + "@types/duplexify": "^3.5.0", + "@types/request": "^2.47.0", + "arrify": "^1.0.1", + "duplexify": "^3.6.0", + "ent": "^2.2.0", + "extend": "^3.0.1", + "google-auth-library": "^2.0.0", + "pify": "^4.0.0", + "retry-request": "^4.0.0" + } }, "@google-cloud/debug-agent": { "version": "3.0.1", - "from": "@google-cloud/debug-agent@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/@google-cloud/debug-agent/-/debug-agent-3.0.1.tgz", + "integrity": "sha1-jGNJQkujDbEqTVaE7v19OvpmBQ4=", + "requires": { + "@google-cloud/common": "^0.27.0", + "@sindresorhus/is": "^0.13.0", + "acorn": "^5.0.3", + "coffeescript": "^2.0.0", + "console-log-level": "^1.4.0", + "extend": "^3.0.1", + "findit2": "^2.2.3", + "gcp-metadata": "^0.9.0", + "lodash.pickby": "^4.6.0", + "p-limit": "^2.0.0", + "pify": "^4.0.1", + "semver": "^5.5.0", + "source-map": "^0.6.1", + "split": "^1.0.0", + "teeny-request": "^3.11.1" + }, "dependencies": { "coffeescript": { "version": "2.3.2", - "from": "coffeescript@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.3.2.tgz" + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.3.2.tgz", + "integrity": "sha1-6FSnAg3+R7fPTdQSBC4y7x4mmBA=" } } }, "@google-cloud/profiler": { "version": "0.2.3", - "from": "@google-cloud/profiler@>=0.2.3 <0.3.0", "resolved": "https://registry.npmjs.org/@google-cloud/profiler/-/profiler-0.2.3.tgz", + "integrity": "sha1-Fj3738Mwuug1X+RuHlvgZTV7H1w=", + "requires": { + "@google-cloud/common": "^0.26.0", + "@types/console-log-level": "^1.4.0", + "@types/semver": "^5.5.0", + "bindings": "^1.2.1", + "console-log-level": "^1.4.0", + "delay": "^4.0.1", + "extend": "^3.0.1", + "gcp-metadata": "^0.9.0", + "nan": "^2.11.1", + "parse-duration": "^0.1.1", + "pify": "^4.0.0", + "pretty-ms": "^4.0.0", + "protobufjs": "~6.8.6", + "semver": "^5.5.0", + "teeny-request": "^3.3.0" + }, "dependencies": { "@google-cloud/common": { "version": "0.26.2", - "from": "@google-cloud/common@>=0.26.0 <0.27.0", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.26.2.tgz" + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.26.2.tgz", + "integrity": "sha1-nFTiRxqEqgMelaJIJJduCA8lVkU=", + "requires": { + "@google-cloud/projectify": "^0.3.2", + "@google-cloud/promisify": "^0.3.0", + "@types/duplexify": "^3.5.0", + "@types/request": "^2.47.0", + "arrify": "^1.0.1", + "duplexify": "^3.6.0", + "ent": "^2.2.0", + "extend": "^3.0.1", + "google-auth-library": "^2.0.0", + "pify": "^4.0.0", + "retry-request": "^4.0.0", + "through2": "^3.0.0" + } }, "through2": { "version": "3.0.0", - "from": "through2@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.0.tgz" + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.0.tgz", + "integrity": "sha1-RotGHfnNn8wXDyLr9oUuRn5Xj/I=", + "requires": { + "readable-stream": "2 || 3", + "xtend": "~4.0.1" + } } } }, "@google-cloud/projectify": { "version": "0.3.2", - "from": "@google-cloud/projectify@>=0.3.2 <0.4.0", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-0.3.2.tgz" + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-0.3.2.tgz", + "integrity": "sha1-7VTJjK5kbcA6dC6sKIGEoT0zpMI=" }, "@google-cloud/promisify": { "version": "0.3.1", - "from": "@google-cloud/promisify@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-0.3.1.tgz" + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-0.3.1.tgz", + "integrity": "sha1-9kHm2USo4KBe4MsQkd+mAIm+zbo=" }, "@google-cloud/trace-agent": { "version": "3.5.0", - "from": "@google-cloud/trace-agent@>=3.2.0 <4.0.0", "resolved": "https://registry.npmjs.org/@google-cloud/trace-agent/-/trace-agent-3.5.0.tgz", + "integrity": "sha1-GxU16eW26wAIXbbIWptLZJTjehY=", + "requires": { + "@google-cloud/common": "^0.28.0", + "builtin-modules": "^3.0.0", + "console-log-level": "^1.4.0", + "continuation-local-storage": "^3.2.1", + "extend": "^3.0.0", + "gcp-metadata": "^0.9.0", + "hex2dec": "^1.0.1", + "is": "^3.2.0", + "methods": "^1.1.1", + "require-in-the-middle": "^3.0.0", + "semver": "^5.4.1", + "shimmer": "^1.2.0", + "teeny-request": "^3.11.1", + "uuid": "^3.0.1" + }, "dependencies": { "@google-cloud/common": { "version": "0.28.0", - "from": "@google-cloud/common@>=0.28.0 <0.29.0", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.28.0.tgz" + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.28.0.tgz", + "integrity": "sha1-cOnTDHkOsPH/tuUpunI+ysO3prw=", + "requires": { + "@google-cloud/projectify": "^0.3.2", + "@google-cloud/promisify": "^0.3.0", + "@types/duplexify": "^3.5.0", + "@types/request": "^2.47.0", + "arrify": "^1.0.1", + "duplexify": "^3.6.0", + "ent": "^2.2.0", + "extend": "^3.0.1", + "google-auth-library": "^2.0.0", + "pify": "^4.0.0", + "retry-request": "^4.0.0" + } }, "uuid": { "version": "3.3.2", - "from": "uuid@>=3.0.1 <4.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz" + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha1-G0r0lV6zB3xQHCOHL8ZROBFYcTE=" } } }, "@protobufjs/aspromise": { "version": "1.1.2", - "from": "@protobufjs/aspromise@>=1.1.2 <2.0.0", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz" + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" }, "@protobufjs/base64": { "version": "1.1.2", - "from": "@protobufjs/base64@>=1.1.2 <2.0.0", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz" + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha1-TIVzDlm5ofHzSQR9vyQpYDS7JzU=" }, "@protobufjs/codegen": { "version": "2.0.4", - "from": "@protobufjs/codegen@>=2.0.4 <3.0.0", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz" + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha1-fvN/DQEPsCitGtWXIuUG2SYoFcs=" }, "@protobufjs/eventemitter": { "version": "1.1.0", - "from": "@protobufjs/eventemitter@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz" + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" }, "@protobufjs/fetch": { "version": "1.1.0", - "from": "@protobufjs/fetch@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz" + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } }, "@protobufjs/float": { "version": "1.0.2", - "from": "@protobufjs/float@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz" + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" }, "@protobufjs/inquire": { "version": "1.1.0", - "from": "@protobufjs/inquire@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz" + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" }, "@protobufjs/path": { "version": "1.1.2", - "from": "@protobufjs/path@>=1.1.2 <2.0.0", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz" + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" }, "@protobufjs/pool": { "version": "1.1.0", - "from": "@protobufjs/pool@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz" + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" }, "@protobufjs/utf8": { "version": "1.1.0", - "from": "@protobufjs/utf8@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz" + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, "@sindresorhus/is": { "version": "0.13.0", - "from": "@sindresorhus/is@>=0.13.0 <0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.13.0.tgz" + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.13.0.tgz", + "integrity": "sha1-qF0GwTZY0MyVhFN99KmUI0YfEUs=", + "requires": { + "symbol-observable": "^1.2.0" + } }, "@sinonjs/commons": { "version": "1.3.0", - "from": "@sinonjs/commons@>=1.2.0 <2.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.3.0.tgz", + "integrity": "sha1-UKJ1QBa28wqZTO2m2aCow2rdqEk=", + "requires": { + "type-detect": "4.0.8" + }, "dependencies": { "type-detect": { "version": "4.0.8", - "from": "type-detect@4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha1-dkb7XxiHHPu3dJ5pvTmmOI63RQw=" } } }, "@sinonjs/formatio": { "version": "3.1.0", - "from": "@sinonjs/formatio@>=3.1.0 <4.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.1.0.tgz" + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.1.0.tgz", + "integrity": "sha1-asnR6xghmE2ExJlnJuRdFkbYzOU=", + "requires": { + "@sinonjs/samsam": "^2 || ^3" + } }, "@sinonjs/samsam": { "version": "3.0.2", - "from": "@sinonjs/samsam@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.0.2.tgz" + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.0.2.tgz", + "integrity": "sha1-ME+zO9VYWgst+KTIAfy0f6hNjkM=", + "requires": { + "@sinonjs/commons": "^1.0.2", + "array-from": "^2.1.1", + "lodash.get": "^4.4.2" + } }, "@types/caseless": { "version": "0.12.1", - "from": "@types/caseless@*", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz" + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz", + "integrity": "sha1-l5TGnIOF0BkqzEcaVA0fjg0WIYo=" }, "@types/console-log-level": { "version": "1.4.0", - "from": "@types/console-log-level@>=1.4.0 <2.0.0", - "resolved": "https://registry.npmjs.org/@types/console-log-level/-/console-log-level-1.4.0.tgz" + "resolved": "https://registry.npmjs.org/@types/console-log-level/-/console-log-level-1.4.0.tgz", + "integrity": "sha1-7/ccQa689RyLpa2LBdfVQkviuPM=" }, "@types/duplexify": { "version": "3.6.0", - "from": "@types/duplexify@>=3.5.0 <4.0.0", - "resolved": "https://registry.npmjs.org/@types/duplexify/-/duplexify-3.6.0.tgz" + "resolved": "https://registry.npmjs.org/@types/duplexify/-/duplexify-3.6.0.tgz", + "integrity": "sha1-38grZL06IWj1vSZESvFlvwI33Ng=", + "requires": { + "@types/node": "*" + } }, "@types/form-data": { "version": "2.2.1", - "from": "@types/form-data@*", - "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz" + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", + "integrity": "sha1-7is7jqoRwJOCiZU2BrdFtzjFSx4=", + "requires": { + "@types/node": "*" + } }, "@types/geojson": { "version": "1.0.6", - "from": "@types/geojson@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.6.tgz" + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.6.tgz", + "integrity": "sha1-PgKXJyjGkkjCrwjWCkjLuGgP/98=" }, "@types/long": { "version": "4.0.0", - "from": "@types/long@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz" + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz", + "integrity": "sha1-cZVR0jUtMBrIuB23Mqy2vcKNve8=" }, "@types/node": { "version": "10.12.15", - "from": "@types/node@*", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.15.tgz" + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.15.tgz", + "integrity": "sha1-IOhWUbYv2GZW5Xycm8dxqxVwvFk=" }, "@types/request": { "version": "2.48.1", - "from": "@types/request@>=2.47.0 <3.0.0", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz" + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz", + "integrity": "sha1-5ALWkapmcPu/8ZV7FfEnAjCrQvo=", + "requires": { + "@types/caseless": "*", + "@types/form-data": "*", + "@types/node": "*", + "@types/tough-cookie": "*" + } }, "@types/semver": { "version": "5.5.0", - "from": "@types/semver@>=5.5.0 <6.0.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz" + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz", + "integrity": "sha1-FGwqKe59O65L8vyydGNuJkyBPEU=" }, "@types/tough-cookie": { "version": "2.3.4", - "from": "@types/tough-cookie@*", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.4.tgz" + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha1-ghh4uBv6uXG5OiZaVh1U6mH5BZ8=" + }, + "JSONStream": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz", + "integrity": "sha1-wQI3G27Dp887hHygDCC7D85Mbeo=", + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } }, "abbrev": { "version": "1.1.1", - "from": "abbrev@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg=" }, "accepts": { "version": "1.3.5", - "from": "accepts@>=1.3.5 <1.4.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz" + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + } }, "acorn": { "version": "5.7.3", - "from": "acorn@>=5.0.3 <6.0.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz" + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha1-Z6ojG/iBKXS4UjWpZ3Hra9B+onk=" }, "agent-base": { "version": "4.2.1", - "from": "agent-base@>=4.1.0 <5.0.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz" + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", + "integrity": "sha1-2J5ZmfeXh1Z0wH2H8mD8Qeg+jKk=", + "requires": { + "es6-promisify": "^5.0.0" + } }, "ajv": { "version": "6.6.2", - "from": "ajv@>=6.5.5 <7.0.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz" + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz", + "integrity": "sha1-ys7M9HS/P8POOxR0Q3EaJAY8ww0=", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } }, "ansi-regex": { "version": "2.1.1", - "from": "ansi-regex@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { "version": "1.1.0", - "from": "ansi-styles@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz" + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", + "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=" }, "aproba": { "version": "1.2.0", - "from": "aproba@>=1.0.3 <2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz" + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=" }, "are-we-there-yet": { "version": "1.1.5", - "from": "are-we-there-yet@>=1.1.2 <1.2.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz" + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha1-SzXClE8GKov82mZBB2A1D+nd/CE=", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } }, "argparse": { "version": "0.1.16", - "from": "argparse@>=0.1.11 <0.2.0", "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", + "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", + "requires": { + "underscore": "~1.7.0", + "underscore.string": "~2.4.0" + }, "dependencies": { "underscore": { "version": "1.7.0", - "from": "underscore@>=1.7.0 <1.8.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz" + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=" }, "underscore.string": { "version": "2.4.0", - "from": "underscore.string@>=2.4.0 <2.5.0", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz" + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", + "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=" } } }, "array-flatten": { "version": "1.1.1", - "from": "array-flatten@1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "array-from": { "version": "2.1.1", - "from": "array-from@>=2.1.1 <3.0.0", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz" + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=" }, "arrify": { "version": "1.0.1", - "from": "arrify@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz" + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" }, "asn1": { "version": "0.2.4", - "from": "asn1@>=0.2.3 <0.3.0", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz" + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha1-jSR136tVO7M+d7VOWeiAu4ziMTY=", + "requires": { + "safer-buffer": "~2.1.0" + } }, "assert-plus": { "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "assertion-error": { "version": "1.0.0", - "from": "assertion-error@1.0.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", + "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", "dev": true }, "async": { "version": "0.2.9", - "from": "async@0.2.9", - "resolved": "https://registry.npmjs.org/async/-/async-0.2.9.tgz" + "resolved": "https://registry.npmjs.org/async/-/async-0.2.9.tgz", + "integrity": "sha1-32MGD789Myhqdqr21Vophtn/hhk=" }, "async-listener": { "version": "0.6.10", - "from": "async-listener@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.10.tgz" + "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.10.tgz", + "integrity": "sha1-p8l6vlcLpgLXgic8DeYKUePhfLw=", + "requires": { + "semver": "^5.3.0", + "shimmer": "^1.1.0" + } }, "asynckit": { "version": "0.4.0", - "from": "asynckit@>=0.4.0 <0.5.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "aws-sign2": { "version": "0.7.0", - "from": "aws-sign2@>=0.7.0 <0.8.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz" + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { "version": "1.8.0", - "from": "aws4@>=1.8.0 <2.0.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz" + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha1-8OAD2cqef1nHpQiUXXsu+aBKVC8=" }, "axios": { "version": "0.18.0", - "from": "axios@>=0.18.0 <0.19.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz" + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz", + "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", + "requires": { + "follow-redirects": "^1.3.0", + "is-buffer": "^1.1.5" + } }, "balanced-match": { "version": "1.0.0", - "from": "balanced-match@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz" + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "bcrypt-pbkdf": { "version": "1.0.2", - "from": "bcrypt-pbkdf@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz" + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } }, "bignumber.js": { "version": "7.2.1", - "from": "bignumber.js@>=7.0.0 <8.0.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz" + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", + "integrity": "sha1-gMBIdZ2CaACAfEv9Uh5Q7bulel8=" }, "bindings": { "version": "1.3.1", - "from": "bindings@>=1.2.1 <2.0.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.1.tgz" + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.1.tgz", + "integrity": "sha1-Ifx8bWfBhRbsWqooFbFF/3eybqU=" }, "bintrees": { "version": "1.0.1", - "from": "bintrees@1.0.1", - "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.1.tgz" + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.1.tgz", + "integrity": "sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ=" }, "bl": { "version": "1.2.2", - "from": "bl@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz" - }, - "block-stream": { - "version": "0.0.9", - "from": "block-stream@*", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz" + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "integrity": "sha1-oWCRFxcQPAdBDO9j71Gzl8Alr5w=", + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } }, "bluebird": { "version": "3.5.3", - "from": "bluebird@>=3.5.0 <4.0.0", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz" + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", + "integrity": "sha1-fQHG+WFsmlGrD4xUmnnf5uwz76c=" }, "body-parser": { "version": "1.18.3", - "from": "body-parser@>=1.2.0 <2.0.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz" - }, - "boom": { - "version": "2.10.1", - "from": "boom@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz" + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + } }, "brace-expansion": { "version": "1.1.11", - "from": "brace-expansion@>=1.1.7 <2.0.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } }, "browser-stdout": { "version": "1.3.0", - "from": "browser-stdout@1.3.0", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", "dev": true }, "buffer-alloc": { "version": "1.2.0", - "from": "buffer-alloc@>=1.2.0 <2.0.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz" + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha1-iQ3ZDZI6hz4I4Q5f1RpX5bfM4Ow=", + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } }, "buffer-alloc-unsafe": { "version": "1.1.0", - "from": "buffer-alloc-unsafe@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz" + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha1-vX3CauKXLQ7aJTvgYdupkjScGfA=" }, "buffer-equal-constant-time": { "version": "1.0.1", - "from": "buffer-equal-constant-time@1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz" + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" }, "buffer-fill": { "version": "1.0.0", - "from": "buffer-fill@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz" + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" }, "buffer-from": { "version": "1.1.1", - "from": "buffer-from@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz" + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8=" }, "builtin-modules": { "version": "3.0.0", - "from": "builtin-modules@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.0.0.tgz" + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.0.0.tgz", + "integrity": "sha1-Hlh9RLAGYg2QKGzHqSOLvGEpyrE=" }, "bunyan": { "version": "0.22.3", - "from": "bunyan@>=0.22.1 <0.23.0", "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-0.22.3.tgz", - "dev": true + "integrity": "sha1-ehncG0yMZF90AkGnQPIkUUfGfsI=", + "dev": true, + "requires": { + "dtrace-provider": "0.2.8", + "mv": "~2" + } }, "buster-core": { "version": "0.6.4", - "from": "buster-core@0.6.4", "resolved": "https://registry.npmjs.org/buster-core/-/buster-core-0.6.4.tgz", + "integrity": "sha1-J79rrWdCROpyDzEdkAoMoct4YFA=", "dev": true }, "buster-format": { "version": "0.5.6", - "from": "buster-format@>=0.5.0 <0.6.0", "resolved": "https://registry.npmjs.org/buster-format/-/buster-format-0.5.6.tgz", - "dev": true + "integrity": "sha1-K4bDIuz14bCubm55Bev884fSq5U=", + "dev": true, + "requires": { + "buster-core": "=0.6.4" + } }, "bytes": { "version": "3.0.0", - "from": "bytes@3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz" + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" }, "caseless": { "version": "0.12.0", - "from": "caseless@>=0.12.0 <0.13.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz" + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "chai": { "version": "1.8.1", - "from": "chai@>=1.8.1 <1.9.0", "resolved": "https://registry.npmjs.org/chai/-/chai-1.8.1.tgz", - "dev": true + "integrity": "sha1-zHeGbV5+vKK9dRRLHtw3Coh4X3I=", + "dev": true, + "requires": { + "assertion-error": "1.0.0", + "deep-eql": "0.1.3" + } }, "chalk": { "version": "0.5.1", - "from": "chalk@>=0.5.0 <0.6.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", + "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", + "requires": { + "ansi-styles": "^1.1.0", + "escape-string-regexp": "^1.0.0", + "has-ansi": "^0.1.0", + "strip-ansi": "^0.3.0", + "supports-color": "^0.2.0" + }, "dependencies": { "ansi-regex": { "version": "0.2.1", - "from": "ansi-regex@^0.2.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz" + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", + "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=" }, "strip-ansi": { "version": "0.3.0", - "from": "strip-ansi@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz" + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", + "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", + "requires": { + "ansi-regex": "^0.2.1" + } }, "supports-color": { "version": "0.2.0", - "from": "supports-color@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz" + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", + "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=" } } }, "check-error": { "version": "1.0.2", - "from": "check-error@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz" + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=" }, "chownr": { "version": "1.1.1", - "from": "chownr@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz" + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha1-VHJri4//TfBTxCGH6AH7RBLfFJQ=" }, "cls-bluebird": { "version": "2.1.0", - "from": "cls-bluebird@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz" - }, - "co": { - "version": "4.6.0", - "from": "co@>=4.6.0 <5.0.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz" + "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz", + "integrity": "sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4=", + "requires": { + "is-bluebird": "^1.0.2", + "shimmer": "^1.1.0" + } }, "code-point-at": { "version": "1.1.0", - "from": "code-point-at@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz" + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "coffee-script": { "version": "1.6.0", - "from": "coffee-script@1.6.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz" + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", + "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=" + }, + "coffeescript": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.6.0.tgz", + "integrity": "sha1-bdTeHrYveE2MjYCWdVLLpUf/2d4=", + "dev": true }, "colors": { "version": "0.6.2", - "from": "colors@>=0.6.2 <0.7.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz" + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=" }, "combined-stream": { "version": "1.0.7", - "from": "combined-stream@>=1.0.6 <1.1.0", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz" + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha1-LR0kMXr7ir6V1tLAsHtXgTU52Cg=", + "requires": { + "delayed-stream": "~1.0.0" + } }, "commander": { "version": "2.0.0", - "from": "commander@2.0.0", - "resolved": "http://registry.npmjs.org/commander/-/commander-2.0.0.tgz" + "resolved": "http://registry.npmjs.org/commander/-/commander-2.0.0.tgz", + "integrity": "sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg=" }, "concat-map": { "version": "0.0.1", - "from": "concat-map@0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.2", - "from": "concat-stream@>=1.6.2 <1.7.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz" + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha1-kEvfGUzTEi/Gdcd/xKw9T/D9GjQ=", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } }, "console-control-strings": { "version": "1.1.0", - "from": "console-control-strings@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "console-log-level": { "version": "1.4.0", - "from": "console-log-level@>=1.4.0 <2.0.0", - "resolved": "https://registry.npmjs.org/console-log-level/-/console-log-level-1.4.0.tgz" + "resolved": "https://registry.npmjs.org/console-log-level/-/console-log-level-1.4.0.tgz", + "integrity": "sha1-QDWBi+6jflhQoMA8jUUMpfLNEhc=" }, "content-disposition": { "version": "0.5.2", - "from": "content-disposition@0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz" + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" }, "content-type": { "version": "1.0.4", - "from": "content-type@>=1.0.4 <1.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz" + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=" }, "continuation-local-storage": { "version": "3.2.1", - "from": "continuation-local-storage@>=3.2.1 <4.0.0", - "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz" + "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz", + "integrity": "sha1-EfYT906RT+mzTJKtLSj+auHbf/s=", + "requires": { + "async-listener": "^0.6.0", + "emitter-listener": "^1.1.1" + } }, "cookie": { "version": "0.3.1", - "from": "cookie@0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz" + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" }, "cookie-signature": { "version": "1.0.6", - "from": "cookie-signature@1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "core-util-is": { "version": "1.0.2", - "from": "core-util-is@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" - }, - "cryptiles": { - "version": "2.0.5", - "from": "cryptiles@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz" + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "dashdash": { "version": "1.14.1", - "from": "dashdash@>=1.12.0 <2.0.0", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz" + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } }, "dateformat": { "version": "1.0.2-1.2.3", - "from": "dateformat@1.0.2-1.2.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz" + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", + "integrity": "sha1-sCIMAt6YYXQztyhRz0fePfLNvuk=" }, "debug": { "version": "2.6.9", - "from": "debug@2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "requires": { + "ms": "2.0.0" + } }, "deep-eql": { "version": "0.1.3", - "from": "deep-eql@0.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", - "dev": true + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "dev": true, + "requires": { + "type-detect": "0.1.1" + } }, "deep-extend": { "version": "0.6.0", - "from": "deep-extend@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz" + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha1-xPp8lUBKF6nD6Mp+FTcxK3NjMKw=" }, "delay": { "version": "4.1.0", - "from": "delay@>=4.0.1 <5.0.0", - "resolved": "https://registry.npmjs.org/delay/-/delay-4.1.0.tgz" + "resolved": "https://registry.npmjs.org/delay/-/delay-4.1.0.tgz", + "integrity": "sha1-R0zSiAnaQdGgSKcKHYNfR6w3fNI=" }, "delayed-stream": { "version": "1.0.0", - "from": "delayed-stream@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "delegates": { "version": "1.0.0", - "from": "delegates@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "depd": { "version": "1.1.2", - "from": "depd@>=1.1.2 <1.2.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, "destroy": { "version": "1.0.4", - "from": "destroy@>=1.0.4 <1.1.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz" + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, "detect-libc": { "version": "1.0.3", - "from": "detect-libc@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz" + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, "diff": { "version": "1.0.7", - "from": "diff@1.0.7", - "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz" + "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", + "integrity": "sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ=" }, "docker-modem": { "version": "1.0.7", - "from": "docker-modem@>=1.0.0 <1.1.0", "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-1.0.7.tgz", + "integrity": "sha1-aXAqlcBg7rZ3X3nM3Mc05ZRpcqQ=", + "requires": { + "JSONStream": "1.3.2", + "debug": "^3.2.5", + "readable-stream": "~1.0.26-4", + "split-ca": "^1.0.0" + }, "dependencies": { "debug": { "version": "3.2.6", - "from": "debug@>=3.2.5 <4.0.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz" + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha1-6D0X3hbYp++3cX7b5fsQE17uYps=", + "requires": { + "ms": "^2.1.1" + } }, "isarray": { "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, "ms": { "version": "2.1.1", - "from": "ms@>=2.1.1 <3.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz" + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=" }, "readable-stream": { "version": "1.0.34", - "from": "readable-stream@>=1.0.26-4 <1.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz" + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } }, "string_decoder": { "version": "0.10.31", - "from": "string_decoder@>=0.10.0 <0.11.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" } } }, "dockerode": { "version": "2.5.7", - "from": "dockerode@>=2.5.3 <3.0.0", - "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-2.5.7.tgz" + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-2.5.7.tgz", + "integrity": "sha1-E9yewPfzU6wOUSJJ538y0aqhGZ4=", + "requires": { + "concat-stream": "~1.6.2", + "docker-modem": "1.0.x", + "tar-fs": "~1.16.3" + } }, "dottie": { "version": "2.0.1", - "from": "dottie@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.1.tgz" + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.1.tgz", + "integrity": "sha1-aXrZ1yAE23V00h+JJGajwoWJNlk=" }, "dtrace-provider": { "version": "0.2.8", - "from": "dtrace-provider@0.2.8", "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.2.8.tgz", + "integrity": "sha1-4kPxkhmqlfvw2PL/sH9b1k6U/iA=", "dev": true, "optional": true }, "duplexify": { "version": "3.6.1", - "from": "duplexify@>=3.6.0 <4.0.0", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz" + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz", + "integrity": "sha1-saeinEq/1jlYXvrszoDWZrHjQSU=", + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } }, "ecc-jsbn": { "version": "0.1.2", - "from": "ecc-jsbn@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz" + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } }, "ecdsa-sig-formatter": { "version": "1.0.10", - "from": "ecdsa-sig-formatter@1.0.10", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz" + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", + "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", + "requires": { + "safe-buffer": "^5.0.1" + } }, "ee-first": { "version": "1.1.1", - "from": "ee-first@1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "emitter-listener": { "version": "1.1.2", - "from": "emitter-listener@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz" + "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", + "integrity": "sha1-VrFA6PaZI3Wz18ssqxzHQy2WMug=", + "requires": { + "shimmer": "^1.2.0" + } }, "encodeurl": { "version": "1.0.2", - "from": "encodeurl@>=1.0.2 <1.1.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, "end-of-stream": { "version": "1.4.1", - "from": "end-of-stream@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz" + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=", + "requires": { + "once": "^1.4.0" + } }, "ent": { "version": "2.2.0", - "from": "ent@>=2.2.0 <3.0.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz" + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=" }, "es6-promise": { "version": "4.2.5", - "from": "es6-promise@>=4.0.3 <5.0.0", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz" + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz", + "integrity": "sha1-2m0NVpLvtGHggsFIF/4kJ9j10FQ=" }, "es6-promisify": { "version": "5.0.0", - "from": "es6-promisify@>=5.0.0 <6.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz" + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "requires": { + "es6-promise": "^4.0.3" + } }, "escape-html": { "version": "1.0.3", - "from": "escape-html@>=1.0.3 <1.1.0", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, "escape-string-regexp": { "version": "1.0.5", - "from": "escape-string-regexp@1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "esprima": { "version": "1.0.4", - "from": "esprima@>=1.0.2 <1.1.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz" + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=" }, "etag": { "version": "1.8.1", - "from": "etag@>=1.8.1 <1.9.0", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, "eventemitter2": { "version": "0.4.14", - "from": "eventemitter2@>=0.4.13 <0.5.0", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz" + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=" }, "exit": { "version": "0.1.2", - "from": "exit@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=" }, "express": { "version": "4.16.4", - "from": "express@>=4.2.0 <5.0.0", "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "integrity": "sha1-/d72GSYQniTFFeqX/S8b2/Yt8S4=", + "requires": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.3", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.2", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, "dependencies": { "statuses": { "version": "1.4.0", - "from": "statuses@>=1.4.0 <1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz" + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha1-u3PURtonlhBu/MG2AaJT1sRr0Ic=" } } }, "extend": { "version": "3.0.2", - "from": "extend@>=3.0.1 <4.0.0", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo=" }, "extsprintf": { "version": "1.3.0", - "from": "extsprintf@1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { "version": "2.0.1", - "from": "fast-deep-equal@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz" + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" }, "fast-json-stable-stringify": { "version": "2.0.0", - "from": "fast-json-stable-stringify@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz" + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, "finalhandler": { "version": "1.1.1", - "from": "finalhandler@1.1.1", "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha1-7r9O2EAHnIP0JJA4ydcDAIMBsQU=", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + }, "dependencies": { "statuses": { "version": "1.4.0", - "from": "statuses@>=1.4.0 <1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz" + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha1-u3PURtonlhBu/MG2AaJT1sRr0Ic=" } } }, "findit2": { "version": "2.2.3", - "from": "findit2@>=2.2.3 <3.0.0", - "resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz" + "resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz", + "integrity": "sha1-WKRmaX34piBc39vzlVNri9d3pfY=" }, "findup-sync": { "version": "0.1.3", - "from": "findup-sync@>=0.1.2 <0.2.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", + "integrity": "sha1-fz56l7gjksZTvwZYm9hRkOk8NoM=", + "requires": { + "glob": "~3.2.9", + "lodash": "~2.4.1" + }, "dependencies": { "glob": { "version": "3.2.11", - "from": "glob@>=3.2.9 <3.3.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz" + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "requires": { + "inherits": "2", + "minimatch": "0.3" + } }, "lodash": { "version": "2.4.2", - "from": "lodash@>=2.4.1 <2.5.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz" + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" }, "lru-cache": { "version": "2.7.3", - "from": "lru-cache@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz" + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" }, "minimatch": { "version": "0.3.0", - "from": "minimatch@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz" + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } } } }, "follow-redirects": { "version": "1.5.10", - "from": "follow-redirects@>=1.3.0 <2.0.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + }, "dependencies": { "debug": { "version": "3.1.0", - "from": "debug@3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz" + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } } } }, "forever-agent": { "version": "0.6.1", - "from": "forever-agent@>=0.6.1 <0.7.0", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { "version": "2.3.3", - "from": "form-data@>=2.3.2 <2.4.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz" + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha1-3M5SwF9kTymManq5Nr1yTO/786Y=", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } }, "forwarded": { "version": "0.1.2", - "from": "forwarded@>=0.1.2 <0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz" + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" }, "fresh": { "version": "0.5.2", - "from": "fresh@0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, "fs-constants": { "version": "1.0.0", - "from": "fs-constants@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz" + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=" }, "fs-extra": { "version": "0.16.5", - "from": "fs-extra@>=0.16.3 <0.17.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.16.5.tgz" + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.16.5.tgz", + "integrity": "sha1-GtZh+myGyWCM0bSe/G/Og0k5p1A=", + "requires": { + "graceful-fs": "^3.0.5", + "jsonfile": "^2.0.0", + "rimraf": "^2.2.8" + } }, "fs-minipass": { "version": "1.2.5", - "from": "fs-minipass@>=1.2.5 <2.0.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz" + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "integrity": "sha1-BsJ3IYRU7CiN93raVKA7hwKqy50=", + "requires": { + "minipass": "^2.2.1" + } }, "fs.realpath": { "version": "1.0.0", - "from": "fs.realpath@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" - }, - "fstream": { - "version": "1.0.11", - "from": "fstream@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", - "dependencies": { - "graceful-fs": { - "version": "4.1.15", - "from": "graceful-fs@>=4.1.2 <5.0.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz" - }, - "mkdirp": { - "version": "0.5.1", - "from": "mkdirp@>=0.5.0 >=0.0.0 <1.0.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" - } - } - }, - "fstream-ignore": { - "version": "1.0.5", - "from": "fstream-ignore@>=1.0.5 <2.0.0", - "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz" + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "gauge": { "version": "2.7.4", - "from": "gauge@>=2.7.3 <2.8.0", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz" + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } }, "gaxios": { "version": "1.0.4", - "from": "gaxios@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-1.0.4.tgz" + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-1.0.4.tgz", + "integrity": "sha512-5IqL40mfNrpgUQpzWkVZHLjDq62QHVn5+HmwI0Hf1haKPzAE6DftUxoGAf9pnEARwlK1A6tWmtjxLVl/kCzCFA==", + "requires": { + "extend": "^3.0.2", + "https-proxy-agent": "^2.2.1", + "node-fetch": "^2.2.0" + } }, "gcp-metadata": { "version": "0.9.3", - "from": "gcp-metadata@>=0.9.0 <0.10.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-0.9.3.tgz" + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-0.9.3.tgz", + "integrity": "sha1-H510lfdGChRSZIHynhFZbdVj3SY=", + "requires": { + "gaxios": "^1.0.2", + "json-bigint": "^0.3.0" + } }, "generic-pool": { "version": "3.4.2", - "from": "generic-pool@>=3.4.0 <4.0.0", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.4.2.tgz" + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.4.2.tgz", + "integrity": "sha1-kv9xllINZwg5pnMICSoSqt8valk=" }, "get-func-name": { "version": "2.0.0", - "from": "get-func-name@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz" + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=" }, "getobject": { "version": "0.1.0", - "from": "getobject@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz" + "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", + "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=" }, "getpass": { "version": "0.1.7", - "from": "getpass@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz" + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } }, "glob": { "version": "7.1.3", - "from": "glob@>=7.0.5 <8.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz" + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha1-OWCDLT8VdBCDQtr9OmezMsCWnfE=", + "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" + } }, "google-auth-library": { "version": "2.0.2", - "from": "google-auth-library@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-2.0.2.tgz", + "integrity": "sha1-ejFdIDZ0Svavyth7IQ7mY4tA9Xs=", + "requires": { + "axios": "^0.18.0", + "gcp-metadata": "^0.7.0", + "gtoken": "^2.3.0", + "https-proxy-agent": "^2.2.1", + "jws": "^3.1.5", + "lru-cache": "^5.0.0", + "semver": "^5.5.0" + }, "dependencies": { "gcp-metadata": { "version": "0.7.0", - "from": "gcp-metadata@>=0.7.0 <0.8.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-0.7.0.tgz" + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-0.7.0.tgz", + "integrity": "sha1-bDXbtSvaMqQnu5yY9UI33dG1QG8=", + "requires": { + "axios": "^0.18.0", + "extend": "^3.0.1", + "retry-axios": "0.3.2" + } } } }, "google-p12-pem": { "version": "1.0.3", - "from": "google-p12-pem@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-1.0.3.tgz" + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-1.0.3.tgz", + "integrity": "sha1-PYrMFAVzM5pbynsvaksga76m2Nc=", + "requires": { + "node-forge": "^0.7.5", + "pify": "^4.0.0" + } }, "graceful-fs": { "version": "3.0.11", - "from": "graceful-fs@>=3.0.5 <4.0.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz" + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", + "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", + "requires": { + "natives": "^1.1.0" + } }, "growl": { "version": "1.7.0", - "from": "growl@1.7.x", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz" + "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", + "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=" }, "grunt": { "version": "0.4.5", - "from": "grunt@>=0.4.5 <0.5.0", "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", + "integrity": "sha1-VpN81RlDJK3/bSB2MYMqnWuk5/A=", + "requires": { + "async": "~0.1.22", + "coffee-script": "~1.3.3", + "colors": "~0.6.2", + "dateformat": "1.0.2-1.2.3", + "eventemitter2": "~0.4.13", + "exit": "~0.1.1", + "findup-sync": "~0.1.2", + "getobject": "~0.1.0", + "glob": "~3.1.21", + "grunt-legacy-log": "~0.1.0", + "grunt-legacy-util": "~0.2.0", + "hooker": "~0.2.3", + "iconv-lite": "~0.2.11", + "js-yaml": "~2.0.5", + "lodash": "~0.9.2", + "minimatch": "~0.2.12", + "nopt": "~1.0.10", + "rimraf": "~2.2.8", + "underscore.string": "~2.2.1", + "which": "~1.0.5" + }, "dependencies": { "async": { "version": "0.1.22", - "from": "async@>=0.1.22 <0.2.0", - "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz" + "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", + "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=" }, "coffee-script": { "version": "1.3.3", - "from": "coffee-script@>=1.3.3 <1.4.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz" + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz", + "integrity": "sha1-FQ1rTLUiiUNp7+1qIQHCC8f0pPQ=" }, "glob": { "version": "3.1.21", - "from": "glob@>=3.1.21 <3.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz" + "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", + "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", + "requires": { + "graceful-fs": "~1.2.0", + "inherits": "1", + "minimatch": "~0.2.11" + } }, "graceful-fs": { "version": "1.2.3", - "from": "graceful-fs@>=1.2.0 <1.3.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz" + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", + "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=" }, "iconv-lite": { "version": "0.2.11", - "from": "iconv-lite@>=0.2.11 <0.3.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz" + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", + "integrity": "sha1-HOYKOleGSiktEyH/RgnKS7llrcg=" }, "inherits": { "version": "1.0.2", - "from": "inherits@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz" + "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=" }, "lodash": { "version": "0.9.2", - "from": "lodash@>=0.9.2 <0.10.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz" + "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", + "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=" }, "lru-cache": { "version": "2.7.3", - "from": "lru-cache@2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz" + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" }, "minimatch": { "version": "0.2.14", - "from": "minimatch@>=0.2.12 <0.3.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz" + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } }, "nopt": { "version": "1.0.10", - "from": "nopt@>=1.0.10 <1.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz" + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "requires": { + "abbrev": "1" + } }, "rimraf": { "version": "2.2.8", - "from": "rimraf@>=2.2.8 <2.3.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz" + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=" } } }, "grunt-bunyan": { "version": "0.5.0", - "from": "grunt-bunyan@>=0.5.0 <0.6.0", "resolved": "https://registry.npmjs.org/grunt-bunyan/-/grunt-bunyan-0.5.0.tgz", + "integrity": "sha1-aCnXbgGZQ9owQTk2MaNuKsgpsWw=", + "requires": { + "lodash": "~2.4.1" + }, "dependencies": { "lodash": { "version": "2.4.2", - "from": "lodash@>=2.4.1 <2.5.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz" + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" } } }, "grunt-contrib-clean": { "version": "0.6.0", - "from": "grunt-contrib-clean@>=0.6.0 <0.7.0", "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-0.6.0.tgz", + "integrity": "sha1-9TLbpLghJnTHwBPhRr2mY4uQSPY=", + "requires": { + "rimraf": "~2.2.1" + }, "dependencies": { "rimraf": { "version": "2.2.8", - "from": "rimraf@~2.2.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz" + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=" } } }, "grunt-contrib-coffee": { "version": "0.11.1", - "from": "grunt-contrib-coffee@>=0.11.0 <0.12.0", "resolved": "https://registry.npmjs.org/grunt-contrib-coffee/-/grunt-contrib-coffee-0.11.1.tgz", + "integrity": "sha1-+v48nuikQryNF9WlwZ/I5i2fP0U=", + "requires": { + "chalk": "~0.5.0", + "coffee-script": "~1.7.0", + "lodash": "~2.4.1" + }, "dependencies": { "coffee-script": { "version": "1.7.1", - "from": "coffee-script@>=1.7.0 <1.8.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.7.1.tgz" + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.7.1.tgz", + "integrity": "sha1-YplqhheAx15tUGnROCJyO3NAS/w=", + "requires": { + "mkdirp": "~0.3.5" + } }, "lodash": { "version": "2.4.2", - "from": "lodash@>=2.4.1 <2.5.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz" + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" } } }, "grunt-execute": { "version": "0.2.2", - "from": "grunt-execute@>=0.2.2 <0.3.0", - "resolved": "https://registry.npmjs.org/grunt-execute/-/grunt-execute-0.2.2.tgz" + "resolved": "https://registry.npmjs.org/grunt-execute/-/grunt-execute-0.2.2.tgz", + "integrity": "sha1-TpRf5XlZzA3neZCDtrQq7ZYWNQo=" }, "grunt-legacy-log": { "version": "0.1.3", - "from": "grunt-legacy-log@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", + "integrity": "sha1-7ClCboAwIa9ZAp+H0vnNczWgVTE=", + "requires": { + "colors": "~0.6.2", + "grunt-legacy-log-utils": "~0.1.1", + "hooker": "~0.2.3", + "lodash": "~2.4.1", + "underscore.string": "~2.3.3" + }, "dependencies": { "lodash": { "version": "2.4.2", - "from": "lodash@>=2.4.1 <2.5.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz" + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" }, "underscore.string": { "version": "2.3.3", - "from": "underscore.string@>=2.3.3 <2.4.0", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz" + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", + "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=" } } }, "grunt-legacy-log-utils": { "version": "0.1.1", - "from": "grunt-legacy-log-utils@>=0.1.1 <0.2.0", "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", + "integrity": "sha1-wHBrndkGThFvNvI/5OawSGcsD34=", + "requires": { + "colors": "~0.6.2", + "lodash": "~2.4.1", + "underscore.string": "~2.3.3" + }, "dependencies": { "lodash": { "version": "2.4.2", - "from": "lodash@>=2.4.1 <2.5.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz" + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" }, "underscore.string": { "version": "2.3.3", - "from": "underscore.string@>=2.3.3 <2.4.0", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz" + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", + "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=" } } }, "grunt-legacy-util": { "version": "0.2.0", - "from": "grunt-legacy-util@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", + "integrity": "sha1-kzJIhNv343qf98Am3/RR2UqeVUs=", + "requires": { + "async": "~0.1.22", + "exit": "~0.1.1", + "getobject": "~0.1.0", + "hooker": "~0.2.3", + "lodash": "~0.9.2", + "underscore.string": "~2.2.1", + "which": "~1.0.5" + }, "dependencies": { "async": { "version": "0.1.22", - "from": "async@~0.1.22", - "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz" + "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", + "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=" }, "lodash": { "version": "0.9.2", - "from": "lodash@~0.9.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz" + "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", + "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=" } } }, "grunt-mocha-test": { "version": "0.11.0", - "from": "grunt-mocha-test@>=0.11.0 <0.12.0", "resolved": "https://registry.npmjs.org/grunt-mocha-test/-/grunt-mocha-test-0.11.0.tgz", + "integrity": "sha1-deQboQdZDkrL0phgklwBLkQ8oyI=", + "requires": { + "fs-extra": "~0.9.1", + "hooker": "~0.2.3", + "mocha": "~1.20.0" + }, "dependencies": { "fs-extra": { "version": "0.9.1", - "from": "fs-extra@>=0.9.1 <0.10.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.9.1.tgz" + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.9.1.tgz", + "integrity": "sha1-h9v8ATg6jdzn2dVJbzYIVkiJ8VY=", + "requires": { + "jsonfile": "~1.1.0", + "mkdirp": "^0.5.0", + "ncp": "^0.5.1", + "rimraf": "^2.2.8" + } }, "glob": { "version": "3.2.3", - "from": "glob@3.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz" + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", + "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", + "requires": { + "graceful-fs": "~2.0.0", + "inherits": "2", + "minimatch": "~0.2.11" + } }, "graceful-fs": { "version": "2.0.3", - "from": "graceful-fs@>=2.0.0 <2.1.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz" + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", + "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=" }, "jsonfile": { "version": "1.1.1", - "from": "jsonfile@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.1.1.tgz" + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.1.1.tgz", + "integrity": "sha1-2k/WrXfxolUgPqY8e8Mtwx72RDM=" }, "lru-cache": { "version": "2.7.3", - "from": "lru-cache@2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz" + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" }, "minimatch": { "version": "0.2.14", - "from": "minimatch@~0.2.11", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz" + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } }, "mkdirp": { "version": "0.5.1", - "from": "mkdirp@>=0.5.0 <0.6.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } }, "mocha": { "version": "1.20.1", - "from": "mocha@>=1.20.0 <1.21.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.20.1.tgz", + "integrity": "sha1-80ODLZ/gx9l8ZPxwRI9RNt+f7Vs=", + "requires": { + "commander": "2.0.0", + "debug": "*", + "diff": "1.0.7", + "glob": "3.2.3", + "growl": "1.7.x", + "jade": "0.26.3", + "mkdirp": "0.3.5" + }, "dependencies": { "mkdirp": { "version": "0.3.5", - "from": "mkdirp@0.3.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz" + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=" } } }, "ncp": { "version": "0.5.1", - "from": "ncp@>=0.5.1 <0.6.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.5.1.tgz" + "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.5.1.tgz", + "integrity": "sha1-dDmFMW49tFkoG1hxaehFc1oFQ58=" } } }, "gtoken": { "version": "2.3.0", - "from": "gtoken@>=2.3.0 <3.0.0", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-2.3.0.tgz", + "integrity": "sha512-Jc9/8mV630cZE9FC5tIlJCZNdUjwunvlwOtCz6IDlaiB4Sz68ki29a1+q97sWTnTYroiuF9B135rod9zrQdHLw==", + "requires": { + "axios": "^0.18.0", + "google-p12-pem": "^1.0.0", + "jws": "^3.1.4", + "mime": "^2.2.0", + "pify": "^3.0.0" + }, "dependencies": { "mime": { "version": "2.4.0", - "from": "mime@>=2.2.0 <3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz" + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==" }, "pify": { "version": "3.0.0", - "from": "pify@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz" + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" } } }, "har-schema": { "version": "2.0.0", - "from": "har-schema@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz" + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { "version": "5.1.3", - "from": "har-validator@>=5.1.0 <5.2.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz" + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha1-HvievT5JllV2de7ZiTEQ3DUPoIA=", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } }, "has-ansi": { "version": "0.1.0", - "from": "has-ansi@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", + "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", + "requires": { + "ansi-regex": "^0.2.0" + }, "dependencies": { "ansi-regex": { "version": "0.2.1", - "from": "ansi-regex@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz" + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", + "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=" } } }, "has-flag": { "version": "2.0.0", - "from": "has-flag@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", "dev": true }, "has-unicode": { "version": "2.0.1", - "from": "has-unicode@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz" - }, - "hawk": { - "version": "3.1.3", - "from": "hawk@3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz" + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, "he": { "version": "1.1.1", - "from": "he@1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, "heapdump": { "version": "0.3.12", - "from": "heapdump@>=0.3.5 <0.4.0", - "resolved": "https://registry.npmjs.org/heapdump/-/heapdump-0.3.12.tgz" + "resolved": "https://registry.npmjs.org/heapdump/-/heapdump-0.3.12.tgz", + "integrity": "sha1-ViO+eBaoqSqy1CsbQi+egppY2ok=", + "requires": { + "nan": "^2.11.1" + } }, "hex2dec": { "version": "1.1.1", - "from": "hex2dec@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/hex2dec/-/hex2dec-1.1.1.tgz" - }, - "hoek": { - "version": "2.16.3", - "from": "hoek@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" + "resolved": "https://registry.npmjs.org/hex2dec/-/hex2dec-1.1.1.tgz", + "integrity": "sha512-E1nK1KCHaX9NvY3wkCMpZxj3oGokO5fgjcKUBaOgSkkvNogm8ngb8isKtzlxnLT37/JXLODVYXz9ti99Bxz8gg==" }, "hooker": { "version": "0.2.3", - "from": "hooker@>=0.2.3 <0.3.0", - "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz" + "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=" }, "http-errors": { "version": "1.6.3", - "from": "http-errors@>=1.6.3 <1.7.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz" + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } }, "http-signature": { "version": "1.2.0", - "from": "http-signature@>=1.2.0 <1.3.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz" + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } }, "https-proxy-agent": { "version": "2.2.1", - "from": "https-proxy-agent@>=2.2.1 <3.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", + "integrity": "sha1-UVUpcPoE1yPgTFbQQXjD+SWSu8A=", + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + }, "dependencies": { "debug": { "version": "3.2.6", - "from": "debug@>=3.1.0 <4.0.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz" + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha1-6D0X3hbYp++3cX7b5fsQE17uYps=", + "requires": { + "ms": "^2.1.1" + } }, "ms": { "version": "2.1.1", - "from": "ms@>=2.1.1 <3.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz" + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=" } } }, "iconv-lite": { "version": "0.4.23", - "from": "iconv-lite@0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz" + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha1-KXhx9jvlB63Pv8pxXQzQ7thOmmM=", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } }, "ignore-walk": { "version": "3.0.1", - "from": "ignore-walk@>=3.0.1 <4.0.0", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz" + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha1-qD5i59JyrA47VRqqgoMaGbafgvg=", + "requires": { + "minimatch": "^3.0.4" + } }, "inflection": { "version": "1.12.0", - "from": "inflection@1.12.0", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz" + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", + "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" }, "inflight": { "version": "1.0.6", - "from": "inflight@>=1.0.4 <2.0.0", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } }, "inherits": { "version": "2.0.3", - "from": "inherits@2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", - "from": "ini@>=1.3.0 <1.4.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz" + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=" }, "ipaddr.js": { "version": "1.8.0", - "from": "ipaddr.js@1.8.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz" + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" }, "is": { "version": "3.3.0", - "from": "is@>=3.2.0 <4.0.0", - "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz" + "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", + "integrity": "sha1-Yc/23TxBk9uUo9YlggcrROVkXXk=" }, "is-bluebird": { "version": "1.0.2", - "from": "is-bluebird@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz" + "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", + "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=" }, "is-buffer": { "version": "1.1.6", - "from": "is-buffer@>=1.1.5 <2.0.0", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz" + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=" }, "is-fullwidth-code-point": { "version": "1.0.0", - "from": "is-fullwidth-code-point@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz" + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } }, "is-typedarray": { "version": "1.0.0", - "from": "is-typedarray@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "isarray": { "version": "1.0.0", - "from": "isarray@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isstream": { "version": "0.1.2", - "from": "isstream@>=0.1.2 <0.2.0", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "jade": { "version": "0.26.3", - "from": "jade@0.26.3", "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "requires": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, "dependencies": { "commander": { "version": "0.6.1", - "from": "commander@0.6.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz" + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=" }, "mkdirp": { "version": "0.3.0", - "from": "mkdirp@0.3.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz" + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=" } } }, "js-yaml": { "version": "2.0.5", - "from": "js-yaml@>=2.0.5 <2.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz" + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", + "integrity": "sha1-olrmUJmZ6X3yeMZxnaEb0Gh3Q6g=", + "requires": { + "argparse": "~ 0.1.11", + "esprima": "~ 1.0.2" + } }, "jsbn": { "version": "0.1.1", - "from": "jsbn@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz" + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "json-bigint": { "version": "0.3.0", - "from": "json-bigint@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz" + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz", + "integrity": "sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4=", + "requires": { + "bignumber.js": "^7.0.0" + } }, "json-schema": { "version": "0.2.3", - "from": "json-schema@0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz" + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-traverse": { "version": "0.4.1", - "from": "json-schema-traverse@>=0.4.1 <0.5.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" - }, - "json-stable-stringify": { - "version": "1.0.1", - "from": "json-stable-stringify@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz" + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=" }, "json-stringify-safe": { "version": "5.0.1", - "from": "json-stringify-safe@5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "jsonfile": { "version": "2.4.0", - "from": "jsonfile@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "requires": { + "graceful-fs": "^4.1.6" + }, "dependencies": { "graceful-fs": { "version": "4.1.15", - "from": "graceful-fs@>=4.1.6 <5.0.0", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha1-/7cD4QZuig7qpMi4C6klPu77+wA=", "optional": true } } }, - "jsonify": { - "version": "0.0.0", - "from": "jsonify@>=0.0.0 <0.1.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz" - }, "jsonparse": { "version": "1.3.1", - "from": "jsonparse@>=1.2.0 <2.0.0", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz" - }, - "JSONStream": { - "version": "1.3.2", - "from": "JSONStream@1.3.2", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz" + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" }, "jsprim": { "version": "1.4.1", - "from": "jsprim@>=1.2.2 <2.0.0", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz" + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } }, "just-extend": { "version": "4.0.2", - "from": "just-extend@>=4.0.2 <5.0.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz" + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", + "integrity": "sha1-8/R/ffyg+YnFVBCn68iFSwcQivw=" }, "jwa": { "version": "1.1.6", - "from": "jwa@>=1.1.5 <2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz" + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", + "integrity": "sha1-hyQOdsmAjb3hh4PPImTvSSnuUOY=", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.10", + "safe-buffer": "^5.0.1" + } }, "jws": { "version": "3.1.5", - "from": "jws@>=3.1.5 <4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz" + "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", + "integrity": "sha1-gNEtBbKT0ehB58uLTmnlYa3Pg08=", + "requires": { + "jwa": "^1.1.5", + "safe-buffer": "^5.0.1" + } }, "lockfile": { "version": "1.0.4", - "from": "lockfile@>=1.0.3 <2.0.0", - "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz" + "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz", + "integrity": "sha1-B/gZ0lrkj4flOOZXi2lkpJgaVgk=", + "requires": { + "signal-exit": "^3.0.2" + } }, "lodash": { "version": "4.17.11", - "from": "lodash@>=4.17.1 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz" + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha1-s56mIp72B+zYniyN8SU2iRysm40=" }, "lodash.get": { "version": "4.4.2", - "from": "lodash.get@>=4.4.2 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz" + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" }, "lodash.pickby": { "version": "4.6.0", - "from": "lodash.pickby@>=4.6.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz" + "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz", + "integrity": "sha1-feoh2MGNdwOifHBMFdO4SmfjOv8=" }, "logger-sharelatex": { - "version": "1.5.9", + "version": "git+https://github.com/sharelatex/logger-sharelatex.git#e8e1b95052f62e107336053e4a983f81cdbdf589", "from": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.9", - "resolved": "git+https://github.com/sharelatex/logger-sharelatex.git#e8e1b95052f62e107336053e4a983f81cdbdf589", + "requires": { + "bunyan": "1.5.1", + "chai": "^4.2.0", + "coffee-script": "1.12.4", + "grunt": "^0.4.5", + "grunt-bunyan": "^0.5.0", + "grunt-contrib-clean": "^0.6.0", + "grunt-contrib-coffee": "^0.11.0", + "grunt-execute": "^0.2.2", + "grunt-mocha-test": "^0.11.0", + "raven": "^1.1.3", + "sandboxed-module": "^2.0.3", + "sinon": "^7.2.2", + "timekeeper": "^1.0.0" + }, "dependencies": { "assertion-error": { "version": "1.1.0", - "from": "assertion-error@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz" + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha1-5gtrDo8wG9l+U3UhW9pAbIURjAs=" }, "bunyan": { "version": "1.5.1", - "from": "bunyan@1.5.1", - "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.5.1.tgz" + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.5.1.tgz", + "integrity": "sha1-X259RMQ7lS9WsPQTCeOrEjkbTi0=", + "requires": { + "dtrace-provider": "~0.6", + "mv": "~2", + "safe-json-stringify": "~1" + } }, "chai": { "version": "4.2.0", - "from": "chai@latest", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz" + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha1-dgqnLPION5XoSxKHfODoNzeqKeU=", + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } }, "coffee-script": { "version": "1.12.4", - "from": "coffee-script@1.12.4", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.4.tgz" + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.4.tgz", + "integrity": "sha1-/hvO2X/h+zknuZjytFYW4GWL4f8=" }, "deep-eql": { "version": "3.0.1", - "from": "deep-eql@>=3.0.1 <4.0.0", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz" + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha1-38lARACtHI/gI+faHfHBR8S0RN8=", + "requires": { + "type-detect": "^4.0.0" + } }, "diff": { "version": "3.5.0", - "from": "diff@>=3.5.0 <4.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz" + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha1-gAwN0eCov7yVg1wgKtIg/jF+WhI=" }, "dtrace-provider": { "version": "0.6.0", - "from": "dtrace-provider@>=0.6.0 <0.7.0", "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.6.0.tgz", - "optional": true + "integrity": "sha1-CweNVReTfYcxAUUtkUZzdVe3XlE=", + "optional": true, + "requires": { + "nan": "^2.0.8" + } }, "has-flag": { "version": "3.0.0", - "from": "has-flag@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "sandboxed-module": { "version": "2.0.3", - "from": "sandboxed-module@latest", - "resolved": "https://registry.npmjs.org/sandboxed-module/-/sandboxed-module-2.0.3.tgz" + "resolved": "https://registry.npmjs.org/sandboxed-module/-/sandboxed-module-2.0.3.tgz", + "integrity": "sha1-x+VFkzm7y6KMUwPusz9ug4e/upY=", + "requires": { + "require-like": "0.1.2", + "stack-trace": "0.0.9" + } }, "sinon": { "version": "7.2.2", - "from": "sinon@latest", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.2.tgz" + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.2.tgz", + "integrity": "sha1-OI7KvUL6k8WSv8cdNacIlNWgygc=", + "requires": { + "@sinonjs/commons": "^1.2.0", + "@sinonjs/formatio": "^3.1.0", + "@sinonjs/samsam": "^3.0.2", + "diff": "^3.5.0", + "lolex": "^3.0.0", + "nise": "^1.4.7", + "supports-color": "^5.5.0" + } }, "supports-color": { "version": "5.5.0", - "from": "supports-color@>=5.5.0 <6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "requires": { + "has-flag": "^3.0.0" + } }, "timekeeper": { "version": "1.0.0", - "from": "timekeeper@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/timekeeper/-/timekeeper-1.0.0.tgz" + "resolved": "https://registry.npmjs.org/timekeeper/-/timekeeper-1.0.0.tgz", + "integrity": "sha1-Lziu4elLEd1m2FgP8aqdzGoroNg=" }, "type-detect": { "version": "4.0.8", - "from": "type-detect@>=4.0.5 <5.0.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha1-dkb7XxiHHPu3dJ5pvTmmOI63RQw=" } } }, "lolex": { "version": "3.0.0", - "from": "lolex@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-3.0.0.tgz" + "resolved": "https://registry.npmjs.org/lolex/-/lolex-3.0.0.tgz", + "integrity": "sha1-8E7hqKoT9g8avXsOj0IT7HLsGT4=" }, "long": { "version": "4.0.0", - "from": "long@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz" + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha1-mntxz7fTYaGU6lVSQckvdGjVvyg=" }, "lru-cache": { "version": "5.1.1", - "from": "lru-cache@>=5.0.0 <6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha1-HaJ+ZxAnGUdpXa9oSOhH8B2EuSA=", + "requires": { + "yallist": "^3.0.2" + } }, "lsmod": { "version": "1.0.0", - "from": "lsmod@1.0.0", - "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz" + "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz", + "integrity": "sha1-mgD3bco26yP6BTUK/htYXUKZ5ks=" }, "lynx": { "version": "0.0.11", - "from": "lynx@0.0.11", - "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.0.11.tgz" + "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.0.11.tgz", + "integrity": "sha1-LPoU5EP9LZKlm3efQVZ84cxpZaM=", + "requires": { + "mersenne": "~0.0.3", + "statsd-parser": "~0.0.4" + } }, "media-typer": { "version": "0.3.0", - "from": "media-typer@0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "merge-descriptors": { "version": "1.0.1", - "from": "merge-descriptors@1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz" + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, "mersenne": { "version": "0.0.4", - "from": "mersenne@>=0.0.3 <0.1.0", - "resolved": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz" + "resolved": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz", + "integrity": "sha1-QB/ex+whzbngPNPTAhOY2iGycIU=" }, "methods": { "version": "1.1.2", - "from": "methods@>=1.1.2 <1.2.0", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "metrics-sharelatex": { - "version": "2.0.12", + "version": "git+https://github.com/sharelatex/metrics-sharelatex.git#3ac1621ef049e2f2d88a83b3a41011333d609662", "from": "git+https://github.com/sharelatex/metrics-sharelatex.git#v2.0.12", - "resolved": "git+https://github.com/sharelatex/metrics-sharelatex.git#3ac1621ef049e2f2d88a83b3a41011333d609662", + "requires": { + "@google-cloud/debug-agent": "^3.0.0", + "@google-cloud/profiler": "^0.2.3", + "@google-cloud/trace-agent": "^3.2.0", + "coffee-script": "1.6.0", + "lynx": "~0.1.1", + "prom-client": "^11.1.3", + "underscore": "~1.6.0" + }, "dependencies": { "coffee-script": { "version": "1.6.0", - "from": "coffee-script@1.6.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz" + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", + "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=" }, "lynx": { "version": "0.1.1", - "from": "lynx@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.1.1.tgz" + "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.1.1.tgz", + "integrity": "sha1-Mxjc7xaQi4KG6Bisz9sxzXQkj50=", + "requires": { + "mersenne": "~0.0.3", + "statsd-parser": "~0.0.4" + } }, "underscore": { "version": "1.6.0", - "from": "underscore@>=1.6.0 <1.7.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz" + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" } } }, "mime": { "version": "1.4.1", - "from": "mime@1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz" + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha1-Eh+evEnjdm8xGnbh+hyAA8SwOqY=" }, "mime-db": { "version": "1.37.0", - "from": "mime-db@>=1.37.0 <1.38.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz" + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha1-C2oM5v2+lXbiXx8tL96IMNwK0Ng=" }, "mime-types": { "version": "2.1.21", - "from": "mime-types@>=2.1.18 <2.2.0", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz" + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha1-KJlaoey3cHQv5q5+WPkYHHRLP5Y=", + "requires": { + "mime-db": "~1.37.0" + } }, "minimatch": { "version": "3.0.4", - "from": "minimatch@>=3.0.4 <4.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "requires": { + "brace-expansion": "^1.1.7" + } }, "minimist": { "version": "0.0.8", - "from": "minimist@0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "minipass": { "version": "2.3.5", - "from": "minipass@>=2.3.4 <3.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz" + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha1-ys6+SSAiSX9law8PUeJoKp7S2Eg=", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } }, "minizlib": { "version": "1.2.1", - "from": "minizlib@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz" + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha1-3SfqYTYkPHyIBoToZyuzpF/ZthQ=", + "requires": { + "minipass": "^2.2.1" + } }, "mkdirp": { "version": "0.3.5", - "from": "mkdirp@0.3.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz" + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=" }, "mocha": { "version": "4.1.0", - "from": "mocha@>=4.0.1 <5.0.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz", + "integrity": "sha1-fYbPvPNcuCnidUwy4XNV7AUzh5Q=", "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.11.0", + "debug": "3.1.0", + "diff": "3.3.1", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.3", + "he": "1.1.1", + "mkdirp": "0.5.1", + "supports-color": "4.4.0" + }, "dependencies": { "commander": { "version": "2.11.0", - "from": "commander@2.11.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha1-FXFS/R56bI2YpbcVzzdt+SgARWM=", "dev": true }, "debug": { "version": "3.1.0", - "from": "debug@3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "dev": true + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "dev": true, + "requires": { + "ms": "2.0.0" + } }, "diff": { "version": "3.3.1", - "from": "diff@3.3.1", "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha1-qoVnpu7QPFMfyJ0/cRzQ5SWd7HU=", "dev": true }, "glob": { "version": "7.1.2", - "from": "glob@7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "dev": true + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } }, "growl": { "version": "1.10.3", - "from": "growl@1.10.3", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha1-GSa6kM8+3+KttJJ/WIC8IsZseQ8=", "dev": true }, "mkdirp": { "version": "0.5.1", - "from": "mkdirp@0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "dev": true + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } } } }, "module-details-from-path": { "version": "1.0.3", - "from": "module-details-from-path@>=1.0.3 <2.0.0", - "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz" + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", + "integrity": "sha1-EUyUlnPiqKNenTV4hSeqN7Z52is=" }, "moment": { "version": "2.23.0", - "from": "moment@>=2.20.0 <3.0.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.23.0.tgz" + "resolved": "https://registry.npmjs.org/moment/-/moment-2.23.0.tgz", + "integrity": "sha1-dZ6kkayX1UusWtd2mW4qWMwbwiU=" }, "moment-timezone": { "version": "0.5.23", - "from": "moment-timezone@>=0.5.14 <0.6.0", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.23.tgz" + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.23.tgz", + "integrity": "sha1-fLsA2ywUxxsZMDy0ew+wpthlFGM=", + "requires": { + "moment": ">= 2.9.0" + } }, "ms": { "version": "2.0.0", - "from": "ms@2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "mv": { "version": "2.1.1", - "from": "mv@~2", "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", "optional": true, + "requires": { + "mkdirp": "~0.5.1", + "ncp": "~2.0.0", + "rimraf": "~2.4.0" + }, "dependencies": { "glob": { "version": "6.0.4", - "from": "glob@>=6.0.1 <7.0.0", "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "optional": true + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "optional": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } }, "mkdirp": { "version": "0.5.1", - "from": "mkdirp@>=0.5.1 <0.6.0", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "optional": true + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "optional": true, + "requires": { + "minimist": "0.0.8" + } }, "rimraf": { "version": "2.4.5", - "from": "rimraf@>=2.4.0 <2.5.0", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", - "optional": true + "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", + "optional": true, + "requires": { + "glob": "^6.0.1" + } } } }, "mysql": { "version": "2.6.2", - "from": "mysql@2.6.2", "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.6.2.tgz", + "integrity": "sha1-k3Gd0yT1fUHET7bX+GDPjmOFk7A=", + "requires": { + "bignumber.js": "2.0.7", + "readable-stream": "~1.1.13", + "require-all": "~1.0.0" + }, "dependencies": { "bignumber.js": { "version": "2.0.7", - "from": "bignumber.js@2.0.7", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.7.tgz" + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.7.tgz", + "integrity": "sha1-husHB89qURCQnSPm6nQ0wU9QDxw=" }, "isarray": { "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, "readable-stream": { "version": "1.1.14", - "from": "readable-stream@>=1.1.13 <1.2.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz" + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } }, "string_decoder": { "version": "0.10.31", - "from": "string_decoder@>=0.10.0 <0.11.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" } } }, "nan": { "version": "2.12.0", - "from": "nan@>=2.11.1 <3.0.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.0.tgz" + "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.0.tgz", + "integrity": "sha1-nUQ/214TogdwzF5gLu5ZdgpoWIU=" }, "natives": { "version": "1.1.6", - "from": "natives@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz" + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz", + "integrity": "sha1-pgO0pJirdxc2ErnqGs3sTZgPALs=" }, "ncp": { "version": "2.0.0", - "from": "ncp@~2.0.0", "resolved": "http://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", "optional": true }, "needle": { "version": "2.2.4", - "from": "needle@>=2.2.1 <3.0.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.4.tgz" + "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.4.tgz", + "integrity": "sha1-UZMb/4JTOxkot9HWngHxsA/9Kk4=", + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } }, "negotiator": { "version": "0.6.1", - "from": "negotiator@0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz" + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" }, "nise": { "version": "1.4.8", - "from": "nise@>=1.4.7 <2.0.0", "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.8.tgz", + "integrity": "sha1-zpHDHobPmyxMrEnX/Nf1Z3m/1rA=", + "requires": { + "@sinonjs/formatio": "^3.1.0", + "just-extend": "^4.0.2", + "lolex": "^2.3.2", + "path-to-regexp": "^1.7.0", + "text-encoding": "^0.6.4" + }, "dependencies": { "isarray": { "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, "lolex": { "version": "2.7.5", - "from": "lolex@>=2.3.2 <3.0.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz" + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", + "integrity": "sha1-ETAB1Wv8fgLVbjYpHMXEE9GqBzM=" }, "path-to-regexp": { "version": "1.7.0", - "from": "path-to-regexp@>=1.7.0 <2.0.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz" + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "requires": { + "isarray": "0.0.1" + } } } }, "node-fetch": { "version": "2.3.0", - "from": "node-fetch@>=2.2.0 <3.0.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz" + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", + "integrity": "sha1-Gh2UC7+5FqHT4CGfA36J5x+MX6U=" }, "node-forge": { "version": "0.7.6", - "from": "node-forge@>=0.7.5 <0.8.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz" + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz", + "integrity": "sha1-/fO0GK7h+U8O9kLNY0hsd8qXJKw=" }, "node-pre-gyp": { - "version": "0.10.3", - "from": "node-pre-gyp@>=0.10.3 <0.11.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", + "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + }, "dependencies": { "mkdirp": { "version": "0.5.1", - "from": "mkdirp@>=0.5.1 <0.6.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } } } }, "nopt": { "version": "4.0.1", - "from": "nopt@>=4.0.1 <5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz" + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } }, "npm-bundled": { "version": "1.0.5", - "from": "npm-bundled@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz" + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz", + "integrity": "sha1-PBcyt7qTazoQMlrvYWRnwMy8yXk=" }, "npm-packlist": { "version": "1.1.12", - "from": "npm-packlist@>=1.1.6 <2.0.0", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.12.tgz" + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.12.tgz", + "integrity": "sha1-Ir3i68EucspIKr1nr8UetJN3JDo=", + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } }, "npmlog": { "version": "4.1.2", - "from": "npmlog@>=4.0.2 <5.0.0", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz" + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } }, "number-is-nan": { "version": "1.0.1", - "from": "number-is-nan@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz" + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "oauth-sign": { "version": "0.9.0", - "from": "oauth-sign@>=0.9.0 <0.10.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz" + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha1-R6ewFrqmi1+g7PPe4IqFxnmsZFU=" }, "object-assign": { "version": "4.1.1", - "from": "object-assign@>=4.1.0 <5.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "on-finished": { "version": "2.3.0", - "from": "on-finished@>=2.3.0 <2.4.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz" + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } }, "once": { "version": "1.4.0", - "from": "once@>=1.3.1 <2.0.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } }, "os-homedir": { "version": "1.0.2", - "from": "os-homedir@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz" + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-tmpdir": { "version": "1.0.2", - "from": "os-tmpdir@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "osenv": { "version": "0.1.5", - "from": "osenv@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz" + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha1-hc36+uso6Gd/QW4odZK18/SepBA=", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } }, "p-limit": { "version": "2.0.0", - "from": "p-limit@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz" + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", + "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "requires": { + "p-try": "^2.0.0" + } }, "p-try": { "version": "2.0.0", - "from": "p-try@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz" + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha1-hQgLuHxkaI+keZb+j3376CEXYLE=" }, "parse-duration": { "version": "0.1.1", - "from": "parse-duration@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-0.1.1.tgz" + "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-0.1.1.tgz", + "integrity": "sha1-ExFN3JiRwezSgANiRFVN5DZHoiY=" }, "parse-ms": { "version": "2.0.0", - "from": "parse-ms@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.0.0.tgz" + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.0.0.tgz", + "integrity": "sha1-ezZAKVEAyvP6AQDMzrVmNbYvnWI=" }, "parseurl": { "version": "1.3.2", - "from": "parseurl@>=1.3.2 <1.4.0", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz" + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" }, "path-is-absolute": { "version": "1.0.1", - "from": "path-is-absolute@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-parse": { "version": "1.0.6", - "from": "path-parse@>=1.0.6 <2.0.0", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz" + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha1-1i27VnlAXXLEc37FhgDp3c8G0kw=" }, "path-to-regexp": { "version": "0.1.7", - "from": "path-to-regexp@0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, "pathval": { "version": "1.1.0", - "from": "pathval@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz" + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=" }, "performance-now": { "version": "2.1.0", - "from": "performance-now@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pify": { "version": "4.0.1", - "from": "pify@>=4.0.1 <5.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz" + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha1-SyzSXFDVmHNcUCkiJP2MbfQeMjE=" }, "pretty-ms": { "version": "4.0.0", - "from": "pretty-ms@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-4.0.0.tgz" + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-4.0.0.tgz", + "integrity": "sha1-Mbr0G5T9AiJwmKqgO9YmCOsNbpI=", + "requires": { + "parse-ms": "^2.0.0" + } }, "process-nextick-args": { "version": "2.0.0", - "from": "process-nextick-args@>=2.0.0 <2.1.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz" + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha1-o31zL0JxtKsa0HDTVQjoKQeI/6o=" }, "prom-client": { "version": "11.2.0", - "from": "prom-client@>=11.1.3 <12.0.0", - "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-11.2.0.tgz" + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-11.2.0.tgz", + "integrity": "sha512-4gUAq/GR5C8q5eWxOa7tA60AtmkMpbyBd/2btCayvd3h/7HzS0p/kESKRwggJgbFrfdhTCBpOwPAwKiI01Q0VQ==", + "requires": { + "tdigest": "^0.1.1" + } }, "protobufjs": { "version": "6.8.8", - "from": "protobufjs@>=6.8.6 <6.9.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz" + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", + "integrity": "sha1-yLTxKC/XqQ5vWxCe0RyEr4KQjnw=", + "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" + } }, "proxy-addr": { "version": "2.0.4", - "from": "proxy-addr@>=2.0.4 <2.1.0", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz" + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", + "integrity": "sha1-7PxzO/Iv+Mb0B/onUye5q2fki5M=", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.8.0" + } }, "psl": { "version": "1.1.31", - "from": "psl@>=1.1.24 <2.0.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz" + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha1-6aqG0BAbWxBcvpOsa3hM1UcnYYQ=" }, "pump": { "version": "1.0.3", - "from": "pump@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz" + "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", + "integrity": "sha1-Xf6DEcM7v2/BgmH580cCxHwIqVQ=", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } }, "punycode": { "version": "2.1.1", - "from": "punycode@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew=" }, "qs": { "version": "6.5.2", - "from": "qs@6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz" + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha1-yzroBuh0BERYTvFUzo7pjUA/PjY=" }, "range-parser": { "version": "1.2.0", - "from": "range-parser@>=1.2.0 <1.3.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz" + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" }, "raven": { "version": "1.2.1", - "from": "raven@>=1.1.3 <2.0.0", - "resolved": "https://registry.npmjs.org/raven/-/raven-1.2.1.tgz" + "resolved": "https://registry.npmjs.org/raven/-/raven-1.2.1.tgz", + "integrity": "sha1-lJwTTbAooZC3u/j3kKrlQbfAIL0=", + "requires": { + "cookie": "0.3.1", + "json-stringify-safe": "5.0.1", + "lsmod": "1.0.0", + "stack-trace": "0.0.9", + "uuid": "3.0.0" + } }, "raw-body": { "version": "2.3.3", - "from": "raw-body@2.3.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz" + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha1-GzJOzmtXBuFThVvBFIxlu39uoMM=", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + } }, "rc": { "version": "1.2.8", - "from": "rc@>=1.2.7 <2.0.0", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha1-zZJL9SAKB1uDwYjNa54hG3/A0+0=", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, "dependencies": { "minimist": { "version": "1.2.0", - "from": "minimist@>=1.2.0 <2.0.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz" + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" } } }, "readable-stream": { "version": "2.3.6", - "from": "readable-stream@>=2.2.2 <3.0.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz" + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } }, "request": { "version": "2.88.0", - "from": "request@>=2.21.0 <3.0.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha1-nC/KT301tZLv5Xx/ClXoEFIST+8=", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, "dependencies": { "uuid": { "version": "3.3.2", - "from": "uuid@>=3.3.2 <4.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz" + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha1-G0r0lV6zB3xQHCOHL8ZROBFYcTE=" } } }, "require-all": { "version": "1.0.0", - "from": "require-all@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/require-all/-/require-all-1.0.0.tgz" + "resolved": "https://registry.npmjs.org/require-all/-/require-all-1.0.0.tgz", + "integrity": "sha1-hINwjnzkxt+tmItQgPl4KbktIic=" }, "require-in-the-middle": { "version": "3.1.0", - "from": "require-in-the-middle@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-3.1.0.tgz" + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-3.1.0.tgz", + "integrity": "sha1-+vS5R8jY0liK5PJGSH4tcmKt0aQ=", + "requires": { + "module-details-from-path": "^1.0.3", + "resolve": "^1.5.0" + } }, "require-like": { "version": "0.1.2", - "from": "require-like@0.1.2", - "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz" + "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", + "integrity": "sha1-rW8wwTvs15cBDEaK+ndcDAprR/o=" }, "resolve": { "version": "1.9.0", - "from": "resolve@>=1.5.0 <2.0.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz" + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz", + "integrity": "sha1-oUxv36j5Kn3x2ZbLcQX6dEZY6gY=", + "requires": { + "path-parse": "^1.0.6" + } }, "retry-as-promised": { "version": "2.3.2", - "from": "retry-as-promised@>=2.3.2 <3.0.0", - "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-2.3.2.tgz" + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-2.3.2.tgz", + "integrity": "sha1-zZdO5P2bX+A8vzGHHuSCIcB3N7c=", + "requires": { + "bluebird": "^3.4.6", + "debug": "^2.6.9" + } }, "retry-axios": { "version": "0.3.2", - "from": "retry-axios@0.3.2", - "resolved": "https://registry.npmjs.org/retry-axios/-/retry-axios-0.3.2.tgz" + "resolved": "https://registry.npmjs.org/retry-axios/-/retry-axios-0.3.2.tgz", + "integrity": "sha1-V1fID1hbTMTEmGqi/9R6YMbTXhM=" }, "retry-request": { "version": "4.0.0", - "from": "retry-request@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.0.0.tgz" + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.0.0.tgz", + "integrity": "sha1-XDZhZiebPhDp16oTJ0RnoFy2kpA=", + "requires": { + "through2": "^2.0.0" + } }, "rimraf": { "version": "2.6.2", - "from": "rimraf@>=2.2.8 <3.0.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz" + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha1-LtgVDSShbqhlHm1u8PR8QVjOejY=", + "requires": { + "glob": "^7.0.5" + } }, "safe-buffer": { "version": "5.1.2", - "from": "safe-buffer@>=5.1.1 <5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=" }, "safe-json-stringify": { "version": "1.2.0", - "from": "safe-json-stringify@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", + "integrity": "sha1-NW5EvJjx+TzkXfFLzXwBzahuCv0=", "optional": true }, "safer-buffer": { "version": "2.1.2", - "from": "safer-buffer@>=2.1.2 <3.0.0", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=" }, "sandboxed-module": { "version": "0.3.0", - "from": "sandboxed-module@>=0.3.0 <0.4.0", "resolved": "https://registry.npmjs.org/sandboxed-module/-/sandboxed-module-0.3.0.tgz", + "integrity": "sha1-8fvvvYCaT2kHO9B8rm/H2y6vX2o=", "dev": true, + "requires": { + "require-like": "0.1.2", + "stack-trace": "0.0.6" + }, "dependencies": { "stack-trace": { "version": "0.0.6", - "from": "stack-trace@0.0.6", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.6.tgz", + "integrity": "sha1-HnGb1qJin/CcGJ4Xqe+QKpT8XbA=", "dev": true } } }, "sax": { "version": "1.2.4", - "from": "sax@>=1.2.4 <2.0.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz" + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk=" }, "semver": { "version": "5.6.0", - "from": "semver@>=5.5.0 <6.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz" + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha1-fnQlb7qknHWqfHogXMInmcrIAAQ=" }, "send": { "version": "0.16.2", - "from": "send@0.16.2", "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha1-bsyh4PjBVtFBWXVZhI32RzCmu8E=", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, "dependencies": { "statuses": { "version": "1.4.0", - "from": "statuses@>=1.4.0 <1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz" + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha1-u3PURtonlhBu/MG2AaJT1sRr0Ic=" } } }, "sequelize": { "version": "4.42.0", - "from": "sequelize@>=4.38.0 <5.0.0", "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-4.42.0.tgz", + "integrity": "sha1-Q5Rnunv+fVr8xW1is+CRhg+/GPM=", + "requires": { + "bluebird": "^3.5.0", + "cls-bluebird": "^2.1.0", + "debug": "^3.1.0", + "depd": "^1.1.0", + "dottie": "^2.0.0", + "generic-pool": "^3.4.0", + "inflection": "1.12.0", + "lodash": "^4.17.1", + "moment": "^2.20.0", + "moment-timezone": "^0.5.14", + "retry-as-promised": "^2.3.2", + "semver": "^5.5.0", + "terraformer-wkt-parser": "^1.1.2", + "toposort-class": "^1.0.1", + "uuid": "^3.2.1", + "validator": "^10.4.0", + "wkx": "^0.4.1" + }, "dependencies": { "debug": { "version": "3.2.6", - "from": "debug@>=3.1.0 <4.0.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz" + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha1-6D0X3hbYp++3cX7b5fsQE17uYps=", + "requires": { + "ms": "^2.1.1" + } }, "ms": { "version": "2.1.1", - "from": "ms@>=2.1.1 <3.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz" + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=" }, "uuid": { "version": "3.3.2", - "from": "uuid@>=3.2.1 <4.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz" + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha1-G0r0lV6zB3xQHCOHL8ZROBFYcTE=" } } }, "serve-static": { "version": "1.13.2", - "from": "serve-static@1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz" + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha1-CV6Ecv1bRiN9tQzkhqQ/S4bGzsE=", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + } }, "set-blocking": { "version": "2.0.0", - "from": "set-blocking@>=2.0.0 <2.1.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "setprototypeof": { "version": "1.1.0", - "from": "setprototypeof@1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz" + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=" }, "settings-sharelatex": { - "version": "1.1.0", + "version": "git+https://github.com/sharelatex/settings-sharelatex.git#93f63d029b52fef8825c3a401b2b6a7ba29b4750", "from": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.1.0", - "resolved": "git+https://github.com/sharelatex/settings-sharelatex.git#93f63d029b52fef8825c3a401b2b6a7ba29b4750" + "requires": { + "coffee-script": "1.6.0" + } }, "shimmer": { "version": "1.2.0", - "from": "shimmer@>=1.2.0 <2.0.0", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.0.tgz" + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.0.tgz", + "integrity": "sha1-+Wb3VVeJdj502IQRk2haXnhzZmU=" }, "sigmund": { "version": "1.0.1", - "from": "sigmund@~1.0.0", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz" + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" }, "signal-exit": { "version": "3.0.2", - "from": "signal-exit@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz" + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "sinon": { "version": "1.7.3", - "from": "sinon@>=1.7.3 <1.8.0", "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.7.3.tgz", - "dev": true + "integrity": "sha1-emnWnNApRYbHQyVO7/G1g6UJl/I=", + "dev": true, + "requires": { + "buster-format": "~0.5" + } }, "smoke-test-sharelatex": { - "version": "1.0.1", + "version": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#bc3e93d18ccee219c0d99e8b02c984ccdd842e1c", "from": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", - "resolved": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#bc3e93d18ccee219c0d99e8b02c984ccdd842e1c", + "requires": { + "mocha": "~1.17.0" + }, "dependencies": { "glob": { "version": "3.2.3", - "from": "glob@3.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz" + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", + "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", + "requires": { + "graceful-fs": "~2.0.0", + "inherits": "2", + "minimatch": "~0.2.11" + } }, "graceful-fs": { "version": "2.0.3", - "from": "graceful-fs@>=2.0.0 <2.1.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz" + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", + "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=" }, "lru-cache": { "version": "2.7.3", - "from": "lru-cache@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz" + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" }, "minimatch": { "version": "0.2.14", - "from": "minimatch@>=0.2.11 <0.3.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz" + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } }, "mocha": { "version": "1.17.1", - "from": "mocha@>=1.17.0 <1.18.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.17.1.tgz" + "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.17.1.tgz", + "integrity": "sha1-f3Zx1oUm0HS3uuZgyQmfh+DqHMs=", + "requires": { + "commander": "2.0.0", + "debug": "*", + "diff": "1.0.7", + "glob": "3.2.3", + "growl": "1.7.x", + "jade": "0.26.3", + "mkdirp": "0.3.5" + } } } }, - "sntp": { - "version": "1.0.9", - "from": "sntp@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" - }, "source-map": { "version": "0.6.1", - "from": "source-map@>=0.6.1 <0.7.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=" }, "split": { "version": "1.0.1", - "from": "split@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz" + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha1-YFvZvjA6pZ+zX5Ip++oN3snqB9k=", + "requires": { + "through": "2" + } }, "split-ca": { "version": "1.0.1", - "from": "split-ca@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz" + "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", + "integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY=" }, "sqlite3": { - "version": "4.0.4", - "from": "sqlite3@>=4.0.2 <5.0.0", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.0.4.tgz", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.0.6.tgz", + "integrity": "sha512-EqBXxHdKiwvNMRCgml86VTL5TK1i0IKiumnfxykX0gh6H6jaKijAXvE9O1N7+omfNSawR2fOmIyJZcfe8HYWpw==", + "requires": { + "nan": "~2.10.0", + "node-pre-gyp": "^0.11.0", + "request": "^2.87.0" + }, "dependencies": { "nan": { "version": "2.10.0", - "from": "nan@>=2.10.0 <2.11.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz" + "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", + "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" } } }, "sshpk": { "version": "1.15.2", - "from": "sshpk@>=1.7.0 <2.0.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz" + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", + "integrity": "sha1-yUbWvZsaOdDoY1dj9SQtbtbctik=", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } }, "stack-trace": { "version": "0.0.9", - "from": "stack-trace@0.0.9", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz" + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", + "integrity": "sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU=" }, "statsd-parser": { "version": "0.0.4", - "from": "statsd-parser@>=0.0.4 <0.1.0", - "resolved": "https://registry.npmjs.org/statsd-parser/-/statsd-parser-0.0.4.tgz" + "resolved": "https://registry.npmjs.org/statsd-parser/-/statsd-parser-0.0.4.tgz", + "integrity": "sha1-y9JDlTzELv/VSLXSI4jtaJ7GOb0=" }, "statuses": { "version": "1.5.0", - "from": "statuses@>=1.4.0 <2.0.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, "stream-shift": { "version": "1.0.0", - "from": "stream-shift@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz" - }, - "string_decoder": { - "version": "1.1.1", - "from": "string_decoder@>=1.1.1 <1.2.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" }, "string-width": { "version": "1.0.2", - "from": "string-width@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz" + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } }, - "stringstream": { - "version": "0.0.6", - "from": "stringstream@>=0.0.4 <0.1.0", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz" + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "requires": { + "safe-buffer": "~5.1.0" + } }, "strip-ansi": { "version": "3.0.1", - "from": "strip-ansi@>=3.0.1 <4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } }, "strip-json-comments": { "version": "2.0.1", - "from": "strip-json-comments@>=2.0.1 <2.1.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, "supports-color": { "version": "4.4.0", - "from": "supports-color@4.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "dev": true + "integrity": "sha1-iD992rwWUUKyphQn8zUt7RldGj4=", + "dev": true, + "requires": { + "has-flag": "^2.0.0" + } }, "symbol-observable": { "version": "1.2.0", - "from": "symbol-observable@>=1.2.0 <2.0.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz" + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha1-wiaIrtTqs83C3+rLtWFmBWCgCAQ=" }, "tar": { "version": "4.4.8", - "from": "tar@>=4.0.0 <5.0.0", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "integrity": "sha1-sZ7sP94qluZGZt+f20DFyhvDdH0=", + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + }, "dependencies": { "mkdirp": { "version": "0.5.1", - "from": "mkdirp@>=0.5.0 <0.6.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } } } }, "tar-fs": { "version": "1.16.3", - "from": "tar-fs@>=1.16.3 <1.17.0", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", + "integrity": "sha1-lmpiiEHaLEAQQGqCFny9Xgxy1Qk=", + "requires": { + "chownr": "^1.0.1", + "mkdirp": "^0.5.1", + "pump": "^1.0.0", + "tar-stream": "^1.1.2" + }, "dependencies": { "mkdirp": { "version": "0.5.1", - "from": "mkdirp@>=0.5.1 <0.6.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" - } - } - }, - "tar-pack": { - "version": "3.4.1", - "from": "tar-pack@>=3.4.0 <4.0.0", - "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.1.tgz", - "dependencies": { - "tar": { - "version": "2.2.1", - "from": "tar@>=2.2.1 <3.0.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz" + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } } } }, "tar-stream": { "version": "1.6.2", - "from": "tar-stream@>=1.1.2 <2.0.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz" + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha1-jqVdqzeXIlPZqa+Q/c1VmuQ1xVU=", + "requires": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + } }, "tdigest": { "version": "0.1.1", - "from": "tdigest@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.1.tgz" + "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.1.tgz", + "integrity": "sha1-Ljyyw56kSeVdHmzZEReszKRYgCE=", + "requires": { + "bintrees": "1.0.1" + } }, "teeny-request": { "version": "3.11.3", - "from": "teeny-request@>=3.11.1 <4.0.0", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-3.11.3.tgz", + "integrity": "sha1-M1xin3ZF5dZZk2LfLzIwxMvCOlU=", + "requires": { + "https-proxy-agent": "^2.2.1", + "node-fetch": "^2.2.0", + "uuid": "^3.3.2" + }, "dependencies": { "uuid": { "version": "3.3.2", - "from": "uuid@>=3.3.2 <4.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz" + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha1-G0r0lV6zB3xQHCOHL8ZROBFYcTE=" } } }, "terraformer": { "version": "1.0.9", - "from": "terraformer@>=1.0.5 <1.1.0", - "resolved": "https://registry.npmjs.org/terraformer/-/terraformer-1.0.9.tgz" + "resolved": "https://registry.npmjs.org/terraformer/-/terraformer-1.0.9.tgz", + "integrity": "sha1-d4Uf70pJyQs0XcU88mgJ/fKdzaY=", + "requires": { + "@types/geojson": "^1.0.0" + } }, "terraformer-wkt-parser": { "version": "1.2.0", - "from": "terraformer-wkt-parser@>=1.1.2 <2.0.0", - "resolved": "https://registry.npmjs.org/terraformer-wkt-parser/-/terraformer-wkt-parser-1.2.0.tgz" + "resolved": "https://registry.npmjs.org/terraformer-wkt-parser/-/terraformer-wkt-parser-1.2.0.tgz", + "integrity": "sha1-ydasPf8l9MC9NE6WH0JpSWGDTDQ=", + "requires": { + "@types/geojson": "^1.0.0", + "terraformer": "~1.0.5" + } }, "text-encoding": { "version": "0.6.4", - "from": "text-encoding@>=0.6.4 <0.7.0", - "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz" + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=" }, "through": { "version": "2.3.8", - "from": "through@>=2.2.7 <3.0.0", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "through2": { "version": "2.0.5", - "from": "through2@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz" + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha1-AcHjnrMdB8t9A6lqcIIyYLIxMs0=", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } }, "timekeeper": { "version": "0.0.4", - "from": "timekeeper@0.0.4", "resolved": "https://registry.npmjs.org/timekeeper/-/timekeeper-0.0.4.tgz", + "integrity": "sha1-kNt58X2Ni1NiFUOJSSuXJ2LP0nY=", "dev": true }, "to-buffer": { "version": "1.1.1", - "from": "to-buffer@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz" + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=" }, "toposort-class": { "version": "1.0.1", - "from": "toposort-class@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz" + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", + "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" }, "tough-cookie": { "version": "2.4.3", - "from": "tough-cookie@>=2.4.3 <2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha1-U/Nto/R3g7CSWvoG/587FlKA94E=", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, "dependencies": { "punycode": { "version": "1.4.1", - "from": "punycode@>=1.4.1 <2.0.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" } } }, "tunnel-agent": { "version": "0.6.0", - "from": "tunnel-agent@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz" + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } }, "tweetnacl": { "version": "0.14.5", - "from": "tweetnacl@>=0.14.0 <0.15.0", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, "type-detect": { "version": "0.1.1", - "from": "type-detect@0.1.1", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", "dev": true }, "type-is": { "version": "1.6.16", - "from": "type-is@>=1.6.16 <1.7.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz" + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha1-+JzjQVQcZysl7nrjxz3uOyvlAZQ=", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } }, "typedarray": { "version": "0.0.6", - "from": "typedarray@>=0.0.6 <0.0.7", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" - }, - "uid-number": { - "version": "0.0.6", - "from": "uid-number@>=0.0.6 <0.0.7", - "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz" + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "underscore": { "version": "1.9.1", - "from": "underscore@>=1.8.2 <2.0.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz" + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", + "integrity": "sha1-BtzjSg5op7q8KbNluOdLiSUgOWE=" }, "underscore.string": { "version": "2.2.1", - "from": "underscore.string@>=2.2.1 <2.3.0", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz" + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", + "integrity": "sha1-18D6KvXVoaZ/QlPa7pgTLnM/Dxk=" }, "unpipe": { "version": "1.0.0", - "from": "unpipe@1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, "uri-js": { "version": "4.2.2", - "from": "uri-js@>=4.2.2 <5.0.0", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz" + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", + "requires": { + "punycode": "^2.1.0" + } }, "util-deprecate": { "version": "1.0.2", - "from": "util-deprecate@>=1.0.1 <1.1.0", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "utils-merge": { "version": "1.0.1", - "from": "utils-merge@1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { "version": "3.0.0", - "from": "uuid@3.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz" + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz", + "integrity": "sha1-Zyj8BFnEUNeWqZwxg3VpvfZy1yg=" }, - "v8-profiler": { - "version": "5.7.0", - "from": "v8-profiler@>=5.2.4 <6.0.0", - "resolved": "https://registry.npmjs.org/v8-profiler/-/v8-profiler-5.7.0.tgz", + "v8-profiler-node8": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/v8-profiler-node8/-/v8-profiler-node8-6.0.1.tgz", + "integrity": "sha512-DD7L0c/2KeFFQxs20VaFPHq/CSintttW4YB+QJdJ5ZohxOegbsMCvnZKHRHVA9UZfVYqq6ZsLaywe74+3JpZjw==", + "requires": { + "nan": "^2.5.1", + "node-pre-gyp": "^0.11.0" + }, "dependencies": { - "ajv": { - "version": "4.11.8", - "from": "ajv@>=4.9.1 <5.0.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz" - }, - "assert-plus": { - "version": "0.2.0", - "from": "assert-plus@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz" - }, - "aws-sign2": { - "version": "0.6.0", - "from": "aws-sign2@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz" - }, - "form-data": { - "version": "2.1.4", - "from": "form-data@>=2.1.1 <2.2.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz" - }, - "har-schema": { - "version": "1.0.5", - "from": "har-schema@>=1.0.5 <2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz" - }, - "har-validator": { - "version": "4.2.1", - "from": "har-validator@>=4.2.1 <4.3.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz" - }, - "http-signature": { - "version": "1.1.1", - "from": "http-signature@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz" - }, "mkdirp": { "version": "0.5.1", - "from": "mkdirp@>=0.5.1 <0.6.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } }, "node-pre-gyp": { - "version": "0.6.39", - "from": "node-pre-gyp@>=0.6.34 <0.7.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz" - }, - "oauth-sign": { - "version": "0.8.2", - "from": "oauth-sign@>=0.8.1 <0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz" - }, - "performance-now": { - "version": "0.2.0", - "from": "performance-now@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz" - }, - "punycode": { - "version": "1.4.1", - "from": "punycode@>=1.4.1 <2.0.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" - }, - "qs": { - "version": "6.4.0", - "from": "qs@>=6.4.0 <6.5.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz" - }, - "request": { - "version": "2.81.0", - "from": "request@2.81.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz" - }, - "tar": { - "version": "2.2.1", - "from": "tar@>=2.2.1 <3.0.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz" - }, - "tough-cookie": { - "version": "2.3.4", - "from": "tough-cookie@>=2.3.0 <2.4.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz" + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", + "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } } } }, "validator": { "version": "10.9.0", - "from": "validator@>=10.4.0 <11.0.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-10.9.0.tgz" + "resolved": "https://registry.npmjs.org/validator/-/validator-10.9.0.tgz", + "integrity": "sha1-0QwRZztQYft8z0wRFEEkEbK6wqg=" }, "vary": { "version": "1.1.2", - "from": "vary@>=1.1.2 <1.2.0", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, "verror": { "version": "1.10.0", - "from": "verror@1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz" + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } }, "which": { "version": "1.0.9", - "from": "which@>=1.0.5 <1.1.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz" + "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", + "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=" }, "wide-align": { "version": "1.1.3", - "from": "wide-align@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz" + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha1-rgdOa9wMFKQx6ATmJFScYzsABFc=", + "requires": { + "string-width": "^1.0.2 || 2" + } }, "wkx": { "version": "0.4.6", - "from": "wkx@>=0.4.1 <0.5.0", - "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.6.tgz" + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.6.tgz", + "integrity": "sha1-Ioq1kuZFc4Lqb7efyCUFjQf85SM=", + "requires": { + "@types/node": "*" + } }, "wrappy": { "version": "1.0.2", - "from": "wrappy@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "wrench": { "version": "1.5.9", - "from": "wrench@>=1.5.4 <1.6.0", - "resolved": "https://registry.npmjs.org/wrench/-/wrench-1.5.9.tgz" + "resolved": "https://registry.npmjs.org/wrench/-/wrench-1.5.9.tgz", + "integrity": "sha1-QRaRxjqbJTGxcAJnJ5veyiOyFCo=" }, "xtend": { "version": "4.0.1", - "from": "xtend@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" }, "yallist": { "version": "3.0.3", - "from": "yallist@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz" + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha1-tLBJ4xS+VF486AIjbWzSLNkcPek=" } } } diff --git a/services/clsi/package.json b/services/clsi/package.json index 065cdce62b..416dd3e3e5 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -37,15 +37,15 @@ "sequelize": "^4.38.0", "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.1.0", "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", - "sqlite3": "^4.0.2", + "sqlite3": "^4.0.6", "underscore": "^1.8.2", - "v8-profiler": "^5.2.4", + "v8-profiler-node8": "^6.0.1", "wrench": "~1.5.4" }, "devDependencies": { "bunyan": "^0.22.1", "chai": "~1.8.1", - "coffee-script": "1.6.0", + "coffeescript": "1.6.0", "mocha": "^4.0.1", "sandboxed-module": "~0.3.0", "sinon": "~1.7.3", diff --git a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee index d4bd19f55c..9f96e6d6e2 100644 --- a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee +++ b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee @@ -33,7 +33,10 @@ compare = (originalPath, generatedPath, callback = (error, same) ->) -> proc.stderr.on "data", (chunk) -> stderr += chunk proc.on "exit", () -> if stderr.trim() == "0 (0)" - fs.unlink diff_file # remove output diff if test matches expected image + # remove output diff if test matches expected image + fs.unlink diff_file, (err) -> + if err + throw err callback null, true else console.log "compare result", stderr From 3829732494a370325d10b1e84756b87cf2808951 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Mon, 17 Dec 2018 19:21:53 +0000 Subject: [PATCH 483/754] Fix broken spacing --- services/clsi/app/coffee/RequestParser.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/RequestParser.coffee b/services/clsi/app/coffee/RequestParser.coffee index cabbac38a4..b887ed30b1 100644 --- a/services/clsi/app/coffee/RequestParser.coffee +++ b/services/clsi/app/coffee/RequestParser.coffee @@ -87,7 +87,7 @@ module.exports = RequestParser = throw "resource modified date could not be understood: #{resource.modified}" if !resource.url? and !resource.content? - throw "all resources should have either a url or content attribute" + throw "all resources should have either a url or content attribute" if resource.content? and typeof resource.content != "string" throw "content attribute should be a string" if resource.url? and typeof resource.url != "string" From 9eb3b0b221f57751a5e8fda3753e991e0d353724 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 24 Jan 2019 12:30:37 +0000 Subject: [PATCH 484/754] add epoll_pwait to secomp profile Last year golang changed from epoll_wait to epoll_pwait https://github.com/golang/go/issues/23750 This causes golang panic errors on mac when running secomp secure compiles using docker 18.09.1. It may start to become a problem on linux where we are running on 17.03.2-ce in production. --- services/clsi/seccomp/clsi-profile.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/clsi/seccomp/clsi-profile.json b/services/clsi/seccomp/clsi-profile.json index 34fd2520ad..e7e9dd010b 100644 --- a/services/clsi/seccomp/clsi-profile.json +++ b/services/clsi/seccomp/clsi-profile.json @@ -827,6 +827,10 @@ "name": "gettimeofday", "action": "SCMP_ACT_ALLOW", "args": [] + }, { + "name": "epoll_pwait", + "action": "SCMP_ACT_ALLOW", + "args": [] } ] } \ No newline at end of file From 038c81f868830dcd4c1bf071eea6d78393841c7a Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Tue, 12 Feb 2019 16:54:59 +0000 Subject: [PATCH 485/754] use explicit json content-type to avoid security issues with text/html --- .../clsi/app/coffee/CompileController.coffee | 6 +++--- .../unit/coffee/CompileControllerTests.coffee | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 0a51c94759..73a7fd1ae5 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -84,7 +84,7 @@ module.exports = CompileController = user_id = req.params.user_id CompileManager.syncFromCode project_id, user_id, file, line, column, (error, pdfPositions) -> return next(error) if error? - res.send JSON.stringify { + res.json { pdf: pdfPositions } @@ -96,7 +96,7 @@ module.exports = CompileController = user_id = req.params.user_id CompileManager.syncFromPdf project_id, user_id, page, h, v, (error, codePositions) -> return next(error) if error? - res.send JSON.stringify { + res.json { code: codePositions } @@ -109,7 +109,7 @@ module.exports = CompileController = CompileManager.wordcount project_id, user_id, file, image, (error, result) -> return next(error) if error? - res.send JSON.stringify { + res.json { texcount: result } diff --git a/services/clsi/test/unit/coffee/CompileControllerTests.coffee b/services/clsi/test/unit/coffee/CompileControllerTests.coffee index 529c732231..034adfcda7 100644 --- a/services/clsi/test/unit/coffee/CompileControllerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileControllerTests.coffee @@ -144,7 +144,7 @@ describe "CompileController", -> file: @file line: @line.toString() column: @column.toString() - @res.send = sinon.stub() + @res.json = sinon.stub() @CompileManager.syncFromCode = sinon.stub().callsArgWith(5, null, @pdfPositions = ["mock-positions"]) @CompileController.syncFromCode @req, @res, @next @@ -155,8 +155,8 @@ describe "CompileController", -> .should.equal true it "should return the positions", -> - @res.send - .calledWith(JSON.stringify + @res.json + .calledWith( pdf: @pdfPositions ) .should.equal true @@ -173,7 +173,7 @@ describe "CompileController", -> page: @page.toString() h: @h.toString() v: @v.toString() - @res.send = sinon.stub() + @res.json = sinon.stub() @CompileManager.syncFromPdf = sinon.stub().callsArgWith(5, null, @codePositions = ["mock-positions"]) @CompileController.syncFromPdf @req, @res, @next @@ -184,8 +184,8 @@ describe "CompileController", -> .should.equal true it "should return the positions", -> - @res.send - .calledWith(JSON.stringify + @res.json + .calledWith( code: @codePositions ) .should.equal true @@ -199,7 +199,7 @@ describe "CompileController", -> @req.query = file: @file image: @image = "example.com/image" - @res.send = sinon.stub() + @res.json = sinon.stub() @CompileManager.wordcount = sinon.stub().callsArgWith(4, null, @texcount = ["mock-texcount"]) @CompileController.wordcount @req, @res, @next @@ -210,8 +210,8 @@ describe "CompileController", -> .should.equal true it "should return the texcount info", -> - @res.send - .calledWith(JSON.stringify + @res.json + .calledWith( texcount: @texcount ) .should.equal true From 6877baf31493a6d1185c2425f8a12b51eeba54d8 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 22 Feb 2019 13:23:26 +0000 Subject: [PATCH 486/754] increase acceptance test timeout to 1 minute --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index 416dd3e3e5..4ae61cede0 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -9,7 +9,7 @@ "scripts": { "compile:app": "([ -e app/coffee ] && coffee -m $COFFEE_OPTIONS -o app/js -c app/coffee || echo 'No CoffeeScript folder to compile') && ( [ -e app.coffee ] && coffee -m $COFFEE_OPTIONS -c app.coffee || echo 'No CoffeeScript app to compile')", "start": "npm run compile:app && node $NODE_APP_OPTIONS app.js", - "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 30000 --exit $@ test/acceptance/js", + "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 60000 --exit $@ test/acceptance/js", "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- --grep=$MOCHA_GREP", "test:unit:_run": "mocha --recursive --reporter spec --exit $@ test/unit/js", "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- --grep=$MOCHA_GREP", From da7e65715b0bfca6fb3a39297ae59a0940560a87 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Mon, 4 Mar 2019 12:05:28 +0000 Subject: [PATCH 487/754] Bump logger to v1.6.0 --- services/clsi/npm-shrinkwrap.json | 690 +----------------------------- services/clsi/package.json | 2 +- 2 files changed, 9 insertions(+), 683 deletions(-) diff --git a/services/clsi/npm-shrinkwrap.json b/services/clsi/npm-shrinkwrap.json index 9fe83ed3ad..5a2c4dc936 100644 --- a/services/clsi/npm-shrinkwrap.json +++ b/services/clsi/npm-shrinkwrap.json @@ -221,39 +221,6 @@ "symbol-observable": "^1.2.0" } }, - "@sinonjs/commons": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.3.0.tgz", - "integrity": "sha1-UKJ1QBa28wqZTO2m2aCow2rdqEk=", - "requires": { - "type-detect": "4.0.8" - }, - "dependencies": { - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha1-dkb7XxiHHPu3dJ5pvTmmOI63RQw=" - } - } - }, - "@sinonjs/formatio": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.1.0.tgz", - "integrity": "sha1-asnR6xghmE2ExJlnJuRdFkbYzOU=", - "requires": { - "@sinonjs/samsam": "^2 || ^3" - } - }, - "@sinonjs/samsam": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.0.2.tgz", - "integrity": "sha1-ME+zO9VYWgst+KTIAfy0f6hNjkM=", - "requires": { - "@sinonjs/commons": "^1.0.2", - "array-from": "^2.1.1", - "lodash.get": "^4.4.2" - } - }, "@types/caseless": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz", @@ -368,11 +335,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, - "ansi-styles": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", - "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=" - }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", @@ -387,37 +349,11 @@ "readable-stream": "^2.0.6" } }, - "argparse": { - "version": "0.1.16", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", - "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", - "requires": { - "underscore": "~1.7.0", - "underscore.string": "~2.4.0" - }, - "dependencies": { - "underscore": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", - "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=" - }, - "underscore.string": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", - "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=" - } - } - }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, - "array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=" - }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -633,43 +569,6 @@ "deep-eql": "0.1.3" } }, - "chalk": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", - "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", - "requires": { - "ansi-styles": "^1.1.0", - "escape-string-regexp": "^1.0.0", - "has-ansi": "^0.1.0", - "strip-ansi": "^0.3.0", - "supports-color": "^0.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", - "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=" - }, - "strip-ansi": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", - "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", - "requires": { - "ansi-regex": "^0.2.1" - } - }, - "supports-color": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", - "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=" - } - } - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=" - }, "chownr": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", @@ -700,11 +599,6 @@ "integrity": "sha1-bdTeHrYveE2MjYCWdVLLpUf/2d4=", "dev": true }, - "colors": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", - "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=" - }, "combined-stream": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", @@ -786,11 +680,6 @@ "assert-plus": "^1.0.0" } }, - "dateformat": { - "version": "1.0.2-1.2.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", - "integrity": "sha1-sCIMAt6YYXQztyhRz0fePfLNvuk=" - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -997,28 +886,14 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "esprima": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", - "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=" + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, - "eventemitter2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", - "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=" - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=" - }, "express": { "version": "4.16.4", "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", @@ -1109,45 +984,6 @@ "resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz", "integrity": "sha1-WKRmaX34piBc39vzlVNri9d3pfY=" }, - "findup-sync": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", - "integrity": "sha1-fz56l7gjksZTvwZYm9hRkOk8NoM=", - "requires": { - "glob": "~3.2.9", - "lodash": "~2.4.1" - }, - "dependencies": { - "glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", - "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", - "requires": { - "inherits": "2", - "minimatch": "0.3" - } - }, - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" - }, - "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" - }, - "minimatch": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", - "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", - "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" - } - } - } - }, "follow-redirects": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", @@ -1258,16 +1094,6 @@ "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.4.2.tgz", "integrity": "sha1-kv9xllINZwg5pnMICSoSqt8valk=" }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=" - }, - "getobject": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", - "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=" - }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -1337,325 +1163,6 @@ "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=" }, - "grunt": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", - "integrity": "sha1-VpN81RlDJK3/bSB2MYMqnWuk5/A=", - "requires": { - "async": "~0.1.22", - "coffee-script": "~1.3.3", - "colors": "~0.6.2", - "dateformat": "1.0.2-1.2.3", - "eventemitter2": "~0.4.13", - "exit": "~0.1.1", - "findup-sync": "~0.1.2", - "getobject": "~0.1.0", - "glob": "~3.1.21", - "grunt-legacy-log": "~0.1.0", - "grunt-legacy-util": "~0.2.0", - "hooker": "~0.2.3", - "iconv-lite": "~0.2.11", - "js-yaml": "~2.0.5", - "lodash": "~0.9.2", - "minimatch": "~0.2.12", - "nopt": "~1.0.10", - "rimraf": "~2.2.8", - "underscore.string": "~2.2.1", - "which": "~1.0.5" - }, - "dependencies": { - "async": { - "version": "0.1.22", - "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", - "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=" - }, - "coffee-script": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz", - "integrity": "sha1-FQ1rTLUiiUNp7+1qIQHCC8f0pPQ=" - }, - "glob": { - "version": "3.1.21", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", - "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", - "requires": { - "graceful-fs": "~1.2.0", - "inherits": "1", - "minimatch": "~0.2.11" - } - }, - "graceful-fs": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", - "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=" - }, - "iconv-lite": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", - "integrity": "sha1-HOYKOleGSiktEyH/RgnKS7llrcg=" - }, - "inherits": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", - "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=" - }, - "lodash": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", - "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=" - }, - "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" - }, - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" - } - }, - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "requires": { - "abbrev": "1" - } - }, - "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=" - } - } - }, - "grunt-bunyan": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/grunt-bunyan/-/grunt-bunyan-0.5.0.tgz", - "integrity": "sha1-aCnXbgGZQ9owQTk2MaNuKsgpsWw=", - "requires": { - "lodash": "~2.4.1" - }, - "dependencies": { - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" - } - } - }, - "grunt-contrib-clean": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-0.6.0.tgz", - "integrity": "sha1-9TLbpLghJnTHwBPhRr2mY4uQSPY=", - "requires": { - "rimraf": "~2.2.1" - }, - "dependencies": { - "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=" - } - } - }, - "grunt-contrib-coffee": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/grunt-contrib-coffee/-/grunt-contrib-coffee-0.11.1.tgz", - "integrity": "sha1-+v48nuikQryNF9WlwZ/I5i2fP0U=", - "requires": { - "chalk": "~0.5.0", - "coffee-script": "~1.7.0", - "lodash": "~2.4.1" - }, - "dependencies": { - "coffee-script": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.7.1.tgz", - "integrity": "sha1-YplqhheAx15tUGnROCJyO3NAS/w=", - "requires": { - "mkdirp": "~0.3.5" - } - }, - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" - } - } - }, - "grunt-execute": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/grunt-execute/-/grunt-execute-0.2.2.tgz", - "integrity": "sha1-TpRf5XlZzA3neZCDtrQq7ZYWNQo=" - }, - "grunt-legacy-log": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", - "integrity": "sha1-7ClCboAwIa9ZAp+H0vnNczWgVTE=", - "requires": { - "colors": "~0.6.2", - "grunt-legacy-log-utils": "~0.1.1", - "hooker": "~0.2.3", - "lodash": "~2.4.1", - "underscore.string": "~2.3.3" - }, - "dependencies": { - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" - }, - "underscore.string": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", - "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=" - } - } - }, - "grunt-legacy-log-utils": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", - "integrity": "sha1-wHBrndkGThFvNvI/5OawSGcsD34=", - "requires": { - "colors": "~0.6.2", - "lodash": "~2.4.1", - "underscore.string": "~2.3.3" - }, - "dependencies": { - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" - }, - "underscore.string": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", - "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=" - } - } - }, - "grunt-legacy-util": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", - "integrity": "sha1-kzJIhNv343qf98Am3/RR2UqeVUs=", - "requires": { - "async": "~0.1.22", - "exit": "~0.1.1", - "getobject": "~0.1.0", - "hooker": "~0.2.3", - "lodash": "~0.9.2", - "underscore.string": "~2.2.1", - "which": "~1.0.5" - }, - "dependencies": { - "async": { - "version": "0.1.22", - "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", - "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=" - }, - "lodash": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", - "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=" - } - } - }, - "grunt-mocha-test": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/grunt-mocha-test/-/grunt-mocha-test-0.11.0.tgz", - "integrity": "sha1-deQboQdZDkrL0phgklwBLkQ8oyI=", - "requires": { - "fs-extra": "~0.9.1", - "hooker": "~0.2.3", - "mocha": "~1.20.0" - }, - "dependencies": { - "fs-extra": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.9.1.tgz", - "integrity": "sha1-h9v8ATg6jdzn2dVJbzYIVkiJ8VY=", - "requires": { - "jsonfile": "~1.1.0", - "mkdirp": "^0.5.0", - "ncp": "^0.5.1", - "rimraf": "^2.2.8" - } - }, - "glob": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", - "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", - "requires": { - "graceful-fs": "~2.0.0", - "inherits": "2", - "minimatch": "~0.2.11" - } - }, - "graceful-fs": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", - "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=" - }, - "jsonfile": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.1.1.tgz", - "integrity": "sha1-2k/WrXfxolUgPqY8e8Mtwx72RDM=" - }, - "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" - }, - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - }, - "mocha": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.20.1.tgz", - "integrity": "sha1-80ODLZ/gx9l8ZPxwRI9RNt+f7Vs=", - "requires": { - "commander": "2.0.0", - "debug": "*", - "diff": "1.0.7", - "glob": "3.2.3", - "growl": "1.7.x", - "jade": "0.26.3", - "mkdirp": "0.3.5" - }, - "dependencies": { - "mkdirp": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", - "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=" - } - } - }, - "ncp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.5.1.tgz", - "integrity": "sha1-dDmFMW49tFkoG1hxaehFc1oFQ58=" - } - } - }, "gtoken": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-2.3.0.tgz", @@ -1694,21 +1201,6 @@ "har-schema": "^2.0.0" } }, - "has-ansi": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", - "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", - "requires": { - "ansi-regex": "^0.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", - "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=" - } - } - }, "has-flag": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", @@ -1739,11 +1231,6 @@ "resolved": "https://registry.npmjs.org/hex2dec/-/hex2dec-1.1.1.tgz", "integrity": "sha512-E1nK1KCHaX9NvY3wkCMpZxj3oGokO5fgjcKUBaOgSkkvNogm8ngb8isKtzlxnLT37/JXLODVYXz9ti99Bxz8gg==" }, - "hooker": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", - "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=" - }, "http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", @@ -1893,15 +1380,6 @@ } } }, - "js-yaml": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", - "integrity": "sha1-olrmUJmZ6X3yeMZxnaEb0Gh3Q6g=", - "requires": { - "argparse": "~ 0.1.11", - "esprima": "~ 1.0.2" - } - }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -1962,11 +1440,6 @@ "verror": "1.10.0" } }, - "just-extend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", - "integrity": "sha1-8/R/ffyg+YnFVBCn68iFSwcQivw=" - }, "jwa": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", @@ -1999,40 +1472,21 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", "integrity": "sha1-s56mIp72B+zYniyN8SU2iRysm40=" }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" - }, "lodash.pickby": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz", "integrity": "sha1-feoh2MGNdwOifHBMFdO4SmfjOv8=" }, "logger-sharelatex": { - "version": "git+https://github.com/sharelatex/logger-sharelatex.git#e8e1b95052f62e107336053e4a983f81cdbdf589", - "from": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.9", + "version": "git+https://github.com/sharelatex/logger-sharelatex.git#8359f16a1546c481a5d1cde63de5b5b6bb569789", + "from": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.6.0", "requires": { "bunyan": "1.5.1", - "chai": "^4.2.0", "coffee-script": "1.12.4", - "grunt": "^0.4.5", - "grunt-bunyan": "^0.5.0", - "grunt-contrib-clean": "^0.6.0", - "grunt-contrib-coffee": "^0.11.0", - "grunt-execute": "^0.2.2", - "grunt-mocha-test": "^0.11.0", "raven": "^1.1.3", - "sandboxed-module": "^2.0.3", - "sinon": "^7.2.2", - "timekeeper": "^1.0.0" + "request": "^2.88.0" }, "dependencies": { - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha1-5gtrDo8wG9l+U3UhW9pAbIURjAs=" - }, "bunyan": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.5.1.tgz", @@ -2043,37 +1497,11 @@ "safe-json-stringify": "~1" } }, - "chai": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", - "integrity": "sha1-dgqnLPION5XoSxKHfODoNzeqKeU=", - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.0", - "type-detect": "^4.0.5" - } - }, "coffee-script": { "version": "1.12.4", "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.4.tgz", "integrity": "sha1-/hvO2X/h+zknuZjytFYW4GWL4f8=" }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha1-38lARACtHI/gI+faHfHBR8S0RN8=", - "requires": { - "type-detect": "^4.0.0" - } - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha1-gAwN0eCov7yVg1wgKtIg/jF+WhI=" - }, "dtrace-provider": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.6.0.tgz", @@ -2082,60 +1510,9 @@ "requires": { "nan": "^2.0.8" } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "sandboxed-module": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/sandboxed-module/-/sandboxed-module-2.0.3.tgz", - "integrity": "sha1-x+VFkzm7y6KMUwPusz9ug4e/upY=", - "requires": { - "require-like": "0.1.2", - "stack-trace": "0.0.9" - } - }, - "sinon": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.2.tgz", - "integrity": "sha1-OI7KvUL6k8WSv8cdNacIlNWgygc=", - "requires": { - "@sinonjs/commons": "^1.2.0", - "@sinonjs/formatio": "^3.1.0", - "@sinonjs/samsam": "^3.0.2", - "diff": "^3.5.0", - "lolex": "^3.0.0", - "nise": "^1.4.7", - "supports-color": "^5.5.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", - "requires": { - "has-flag": "^3.0.0" - } - }, - "timekeeper": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/timekeeper/-/timekeeper-1.0.0.tgz", - "integrity": "sha1-Lziu4elLEd1m2FgP8aqdzGoroNg=" - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha1-dkb7XxiHHPu3dJ5pvTmmOI63RQw=" } } }, - "lolex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-3.0.0.tgz", - "integrity": "sha1-8E7hqKoT9g8avXsOj0IT7HLsGT4=" - }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", @@ -2476,38 +1853,6 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" }, - "nise": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.8.tgz", - "integrity": "sha1-zpHDHobPmyxMrEnX/Nf1Z3m/1rA=", - "requires": { - "@sinonjs/formatio": "^3.1.0", - "just-extend": "^4.0.2", - "lolex": "^2.3.2", - "path-to-regexp": "^1.7.0", - "text-encoding": "^0.6.4" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "lolex": { - "version": "2.7.5", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", - "integrity": "sha1-ETAB1Wv8fgLVbjYpHMXEE9GqBzM=" - }, - "path-to-regexp": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", - "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", - "requires": { - "isarray": "0.0.1" - } - } - } - }, "node-fetch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", @@ -2672,11 +2017,6 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, - "pathval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=" - }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -2872,7 +2212,8 @@ "require-like": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", - "integrity": "sha1-rW8wwTvs15cBDEaK+ndcDAprR/o=" + "integrity": "sha1-rW8wwTvs15cBDEaK+ndcDAprR/o=", + "dev": true }, "resolve": { "version": "1.9.0", @@ -2920,7 +2261,7 @@ "safe-json-stringify": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", - "integrity": "sha1-NW5EvJjx+TzkXfFLzXwBzahuCv0=", + "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", "optional": true }, "safer-buffer": { @@ -3348,11 +2689,6 @@ "terraformer": "~1.0.5" } }, - "text-encoding": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", - "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=" - }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -3437,11 +2773,6 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", "integrity": "sha1-BtzjSg5op7q8KbNluOdLiSUgOWE=" }, - "underscore.string": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", - "integrity": "sha1-18D6KvXVoaZ/QlPa7pgTLnM/Dxk=" - }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -3526,11 +2857,6 @@ "extsprintf": "^1.2.0" } }, - "which": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", - "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=" - }, "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", diff --git a/services/clsi/package.json b/services/clsi/package.json index 4ae61cede0..b2cd6713e7 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -28,7 +28,7 @@ "fs-extra": "^0.16.3", "heapdump": "^0.3.5", "lockfile": "^1.0.3", - "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.5.9", + "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.6.0", "lynx": "0.0.11", "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v2.0.12", "mkdirp": "0.3.5", From 900e2f70b4b379693674467e60c9cc3ce2874d6a Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 22 Mar 2019 20:42:06 +0000 Subject: [PATCH 488/754] change console.log for logger.log --- services/clsi/app/coffee/DbQueue.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/DbQueue.coffee b/services/clsi/app/coffee/DbQueue.coffee index 0e025906b7..a3593fddf4 100644 --- a/services/clsi/app/coffee/DbQueue.coffee +++ b/services/clsi/app/coffee/DbQueue.coffee @@ -1,12 +1,12 @@ async = require "async" Settings = require "settings-sharelatex" - +logger = require("logger-sharelatex") queue = async.queue((task, cb)-> task(cb) , Settings.parallelSqlQueryLimit) queue.drain = ()-> - console.log('HI all items have been processed') + logger.debug('all items have been processed') module.exports = queue: queue From 5a690a5416453fa3521c3c0ad68657925d13a3a2 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Tue, 26 Mar 2019 11:50:59 +0000 Subject: [PATCH 489/754] Formalise node 10.15 update --- services/clsi/.nvmrc | 2 +- services/clsi/buildscript.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/.nvmrc b/services/clsi/.nvmrc index d36e8d82f3..f9fb144f9a 100644 --- a/services/clsi/.nvmrc +++ b/services/clsi/.nvmrc @@ -1 +1 @@ -6.15.1 +10.15.0 diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index abdf7ed9bf..2ca09a3f7b 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -1,6 +1,6 @@ --script-version=1.1.11 clsi ---node-version=6.15.1 +--node-version=10.15.0 --acceptance-creds=None --language=coffeescript --dependencies=mongo,redis From bc6e5604482c230a627cfb065b794874126c2e0e Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Mon, 1 Apr 2019 09:42:54 +0100 Subject: [PATCH 490/754] increase timeout for long-running acceptance tests --- .../clsi/test/acceptance/coffee/ExampleDocumentTests.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee index 9f96e6d6e2..f8e4a777b5 100644 --- a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee +++ b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee @@ -15,6 +15,8 @@ try catch err console.log err, fixturePath("tmp"), "unable to create fixture tmp path" +MOCHA_LATEX_TIMEOUT = 60 * 1000 + convertToPng = (pdfPath, pngPath, callback = (error) ->) -> command = "convert #{fixturePath(pdfPath)} #{fixturePath(pngPath)}" console.log "COMMAND" @@ -109,6 +111,7 @@ describe "Example Documents", -> @project_id = Client.randomId() + "_" + example_dir it "should generate the correct pdf", (done) -> + this.timeout(MOCHA_LATEX_TIMEOUT) Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => if error || body?.compile?.status is "failure" console.log "DEBUG: error", error, "body", JSON.stringify(body) @@ -116,6 +119,7 @@ describe "Example Documents", -> downloadAndComparePdf(@project_id, example_dir, pdf.url, done) it "should generate the correct pdf on the second run as well", (done) -> + this.timeout(MOCHA_LATEX_TIMEOUT) Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => if error || body?.compile?.status is "failure" console.log "DEBUG: error", error, "body", JSON.stringify(body) From 5b1481fc127362984fd726d44e326349f9c8b82d Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <das7pad@outlook.com> Date: Mon, 22 Apr 2019 01:38:24 +0200 Subject: [PATCH 491/754] [docker] add support for a different docker group id on the docker host Signed-off-by: Jakob Ackermann <das7pad@outlook.com> --- services/clsi/entrypoint.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh index ea295c7e10..71ced1412b 100755 --- a/services/clsi/entrypoint.sh +++ b/services/clsi/entrypoint.sh @@ -4,8 +4,10 @@ echo "Changing permissions of /var/run/docker.sock for sibling containers" ls -al /var/run/docker.sock docker --version cat /etc/passwd -usermod -aG docker node -chown root:docker /var/run/docker.sock + +DOCKER_GROUP=$(stat -c '%g' /var/run/docker.sock) +groupadd --non-unique --gid ${DOCKER_GROUP} dockeronhost +usermod -aG dockeronhost node mkdir -p /app/cache chown -R node:node /app/cache From a33cd6cbbfaf94f041c60fc973a9dc89d8e6421e Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Thu, 2 May 2019 15:27:21 +0100 Subject: [PATCH 492/754] Bump buildscripts from 1.1.11 to 1.1.20 --- services/clsi/Jenkinsfile | 10 ++++++++-- services/clsi/Makefile | 10 +++++++--- services/clsi/buildscript.txt | 5 ++--- services/clsi/docker-compose.ci.yml | 15 ++++++++++++++- services/clsi/docker-compose.yml | 16 +++++++++++++++- services/clsi/package.json | 2 +- 6 files changed, 47 insertions(+), 11 deletions(-) diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index d82360d48d..e5b9ce6eec 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -48,8 +48,11 @@ pipeline { } } - stage('Package and publish build') { + 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}' @@ -60,9 +63,12 @@ pipeline { } } - stage('Publish build number') { + 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") diff --git a/services/clsi/Makefile b/services/clsi/Makefile index e7a1e341df..ad83231887 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.11 +# Version: 1.1.20 BUILD_NUMBER ?= local BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) @@ -13,7 +13,6 @@ DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ MOCHA_GREP=${MOCHA_GREP} \ docker-compose ${DOCKER_COMPOSE_FLAGS} - clean: docker rmi ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) docker rmi gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) @@ -27,7 +26,9 @@ test: test_unit test_acceptance test_unit: @[ ! -d test/unit ] && echo "clsi has no unit tests" || $(DOCKER_COMPOSE) run --rm test_unit -test_acceptance: test_clean test_acceptance_pre_run # clear the database before each acceptance test run +test_acceptance: test_clean test_acceptance_pre_run test_acceptance_run + +test_acceptance_run: @[ ! -d test/acceptance ] && echo "clsi has no acceptance tests" || $(DOCKER_COMPOSE) run --rm test_acceptance test_clean: @@ -40,6 +41,9 @@ build: --tag gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ . +tar: + $(DOCKER_COMPOSE) up tar + publish: docker push $(DOCKER_REPO)/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index 2ca09a3f7b..085d8cb8c3 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -1,9 +1,8 @@ ---script-version=1.1.11 clsi +--language=coffeescript --node-version=10.15.0 --acceptance-creds=None ---language=coffeescript --dependencies=mongo,redis --docker-repos=gcr.io/overleaf-ops ---kube=false --build-target=docker +--script-version=1.1.20 diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 698c5cb1b9..c6c35dbacf 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.11 +# Version: 1.1.20 version: "2" @@ -9,6 +9,8 @@ services: test_unit: image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER command: npm run test:unit:_run + environment: + NODE_ENV: test test_acceptance: @@ -23,11 +25,22 @@ services: MONGO_HOST: mongo POSTGRES_HOST: postgres MOCHA_GREP: ${MOCHA_GREP} + NODE_ENV: test depends_on: - mongo - redis command: npm run test:acceptance:_run + + + tar: + build: . + image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER + volumes: + - ./:/tmp/build/ + command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs . + user: root + redis: image: redis diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 5c3c4c86a6..3af0f7a8be 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.11 +# Version: 1.1.20 version: "2" @@ -13,6 +13,7 @@ services: working_dir: /app environment: MOCHA_GREP: ${MOCHA_GREP} + NODE_ENV: test command: npm run test:unit test_acceptance: @@ -29,14 +30,27 @@ services: MONGO_HOST: mongo POSTGRES_HOST: postgres MOCHA_GREP: ${MOCHA_GREP} + LOG_LEVEL: ERROR + NODE_ENV: test depends_on: - mongo - redis command: npm run test:acceptance + + + tar: + build: . + image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER + volumes: + - ./:/tmp/build/ + command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs . + user: root + redis: image: redis mongo: image: mongo:3.4 + diff --git a/services/clsi/package.json b/services/clsi/package.json index b2cd6713e7..61df397988 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -9,7 +9,7 @@ "scripts": { "compile:app": "([ -e app/coffee ] && coffee -m $COFFEE_OPTIONS -o app/js -c app/coffee || echo 'No CoffeeScript folder to compile') && ( [ -e app.coffee ] && coffee -m $COFFEE_OPTIONS -c app.coffee || echo 'No CoffeeScript app to compile')", "start": "npm run compile:app && node $NODE_APP_OPTIONS app.js", - "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 60000 --exit $@ test/acceptance/js", + "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 30000 --exit $@ test/acceptance/js", "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- --grep=$MOCHA_GREP", "test:unit:_run": "mocha --recursive --reporter spec --exit $@ test/unit/js", "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- --grep=$MOCHA_GREP", From 70a1f7c11047ca01b98a39f27f44ff95184f8b40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Alby?= <tim.alby@overleaf.com> Date: Tue, 7 May 2019 16:41:17 +0100 Subject: [PATCH 493/754] Update README.md --- services/clsi/README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/services/clsi/README.md b/services/clsi/README.md index ba0a63e4ef..5ae02a866d 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -1,10 +1,8 @@ -clsi-sharelatex +overleaf/clsi =============== A web api for compiling LaTeX documents in the cloud -[![Build Status](https://travis-ci.org/sharelatex/clsi-sharelatex.png?branch=master)](https://travis-ci.org/sharelatex/clsi-sharelatex) - The Common LaTeX Service Interface (CLSI) provides a RESTful interface to traditional LaTeX tools (or, more generally, any command line tool for composing marked-up documents into a display format such as PDF or HTML). The CLSI listens on the following ports by default: * TCP/3009 - the RESTful interface @@ -32,9 +30,9 @@ The CLSI can be configured through the following environment variables: Installation ------------ -The CLSI can be installed and set up as part of the entire [ShareLaTeX stack](https://github.com/sharelatex/sharelatex) (complete with front end editor and document storage), or it can be run as a standalone service. To run is as a standalone service, first checkout this repository: +The CLSI can be installed and set up as part of the entire [Overleaf stack](https://github.com/overleaf/overleaf) (complete with front end editor and document storage), or it can be run as a standalone service. To run is as a standalone service, first checkout this repository: - $ git clone git@github.com:sharelatex/clsi-sharelatex.git + $ git clone git@github.com:overleaf/clsi.git Then install the require npm modules: @@ -116,4 +114,4 @@ License The code in this repository is released under the GNU AFFERO GENERAL PUBLIC LICENSE, version 3. A copy can be found in the `LICENSE` file. -Copyright (c) ShareLaTeX, 2014. +Copyright (c) Overleaf, 2014-2019. From 4ccaa3bf2fddf242136322ba1ef7f63c0c554f04 Mon Sep 17 00:00:00 2001 From: Tim Alby <timothee.alby@gmail.com> Date: Tue, 7 May 2019 18:31:54 +0200 Subject: [PATCH 494/754] update Git URL in Jenkinsfile --- services/clsi/Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index d82360d48d..f5b5f1df78 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -4,10 +4,10 @@ pipeline { agent any environment { - GIT_PROJECT = "clsi-sharelatex" + GIT_PROJECT = "clsi" JENKINS_WORKFLOW = "clsi-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/sharelatex/${GIT_PROJECT}/statuses/$GIT_COMMIT" + GIT_API_URL = "https://api.github.com/repos/overleaf/${GIT_PROJECT}/statuses/$GIT_COMMIT" } triggers { From 1ddf9283f2ab8316cd105b10c0e6f77f83ab6f32 Mon Sep 17 00:00:00 2001 From: Michael Mazour <michaelmazour@gmail.com> Date: Fri, 10 May 2019 16:51:40 +0100 Subject: [PATCH 495/754] Add flags option to request JSON Adds a `flags` parameter to the request JSON, appearing under the `compile.options` key (alongside such stalwarts as `compiler`, `timeout`, etc.). This is primarily to support `-file-line-error` as an option, but could have other uses as well. `flags` should be an array of strings, or absent. If supplied, the listed arguments are added to the base latexmk command. --- .../clsi/app/coffee/CompileManager.coffee | 3 +- services/clsi/app/coffee/LatexRunner.coffee | 41 ++++++++++--------- services/clsi/app/coffee/RequestParser.coffee | 8 +++- .../unit/coffee/CompileManagerTests.coffee | 18 ++++---- .../test/unit/coffee/LatexRunnerTests.coffee | 18 ++++++++ .../unit/coffee/RequestParserTests.coffee | 32 +++++++++++---- 6 files changed, 82 insertions(+), 38 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 0a98e87db4..ad924e2b1e 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -93,6 +93,7 @@ module.exports = CompileManager = compiler: request.compiler timeout: request.timeout image: request.imageName + flags: request.flags environment: env }, (error, output, stats, timings) -> # request was for validation only @@ -130,7 +131,7 @@ module.exports = CompileManager = return callback(error) if error? OutputCacheManager.saveOutputFiles outputFiles, compileDir, (error, newOutputFiles) -> callback null, newOutputFiles - + stopCompile: (project_id, user_id, callback = (error) ->) -> compileName = getCompileName(project_id, user_id) LatexRunner.killLatex compileName, callback diff --git a/services/clsi/app/coffee/LatexRunner.coffee b/services/clsi/app/coffee/LatexRunner.coffee index 3571af20bf..29433f83e3 100644 --- a/services/clsi/app/coffee/LatexRunner.coffee +++ b/services/clsi/app/coffee/LatexRunner.coffee @@ -8,27 +8,27 @@ ProcessTable = {} # table of currently running jobs (pids or docker container n module.exports = LatexRunner = runLatex: (project_id, options, callback = (error) ->) -> - {directory, mainFile, compiler, timeout, image, environment} = options + {directory, mainFile, compiler, timeout, image, environment, flags} = options compiler ||= "pdflatex" timeout ||= 60000 # milliseconds - logger.log directory: directory, compiler: compiler, timeout: timeout, mainFile: mainFile, environment: environment, "starting compile" + logger.log directory: directory, compiler: compiler, timeout: timeout, mainFile: mainFile, environment: environment, flags:flags, "starting compile" # We want to run latexmk on the tex file which we will automatically # generate from the Rtex/Rmd/md file. mainFile = mainFile.replace(/\.(Rtex|md|Rmd)$/, ".tex") if compiler == "pdflatex" - command = LatexRunner._pdflatexCommand mainFile + command = LatexRunner._pdflatexCommand mainFile, flags else if compiler == "latex" - command = LatexRunner._latexCommand mainFile + command = LatexRunner._latexCommand mainFile, flags else if compiler == "xelatex" - command = LatexRunner._xelatexCommand mainFile + command = LatexRunner._xelatexCommand mainFile, flags else if compiler == "lualatex" - command = LatexRunner._lualatexCommand mainFile + command = LatexRunner._lualatexCommand mainFile, flags else return callback new Error("unknown compiler: #{compiler}") - + if Settings.clsi?.strace command = ["strace", "-o", "strace", "-ff"].concat(command) @@ -63,31 +63,32 @@ module.exports = LatexRunner = else CommandRunner.kill ProcessTable[id], callback - _latexmkBaseCommand: (Settings?.clsi?.latexmkCommandPrefix || []).concat([ - "latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR", - "-synctex=1","-interaction=batchmode" - ]) + _latexmkBaseCommand: (flags) -> + args = ["latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR", "-synctex=1","-interaction=batchmode"] + if flags + args = args.concat(flags) + (Settings?.clsi?.latexmkCommandPrefix || []).concat(args) - _pdflatexCommand: (mainFile) -> - LatexRunner._latexmkBaseCommand.concat [ + _pdflatexCommand: (mainFile, flags) -> + LatexRunner._latexmkBaseCommand(flags).concat [ "-pdf", Path.join("$COMPILE_DIR", mainFile) ] - - _latexCommand: (mainFile) -> - LatexRunner._latexmkBaseCommand.concat [ + + _latexCommand: (mainFile, flags) -> + LatexRunner._latexmkBaseCommand(flags).concat [ "-pdfdvi", Path.join("$COMPILE_DIR", mainFile) ] - _xelatexCommand: (mainFile) -> - LatexRunner._latexmkBaseCommand.concat [ + _xelatexCommand: (mainFile, flags) -> + LatexRunner._latexmkBaseCommand(flags).concat [ "-xelatex", Path.join("$COMPILE_DIR", mainFile) ] - _lualatexCommand: (mainFile) -> - LatexRunner._latexmkBaseCommand.concat [ + _lualatexCommand: (mainFile, flags) -> + LatexRunner._latexmkBaseCommand(flags).concat [ "-lualatex", Path.join("$COMPILE_DIR", mainFile) ] diff --git a/services/clsi/app/coffee/RequestParser.coffee b/services/clsi/app/coffee/RequestParser.coffee index b887ed30b1..c1912f62d9 100644 --- a/services/clsi/app/coffee/RequestParser.coffee +++ b/services/clsi/app/coffee/RequestParser.coffee @@ -12,7 +12,7 @@ module.exports = RequestParser = compile = body.compile compile.options ||= {} - + try response.compiler = @_parseAttribute "compiler", compile.options.compiler, @@ -33,6 +33,10 @@ module.exports = RequestParser = response.check = @_parseAttribute "check", compile.options.check, type: "string" + response.flags = @_parseAttribute "flags", + compile.options.flags, + default: [], + type: "object" # The syncType specifies whether the request contains all # resources (full) or only those resources to be updated @@ -68,7 +72,7 @@ module.exports = RequestParser = originalRootResourcePath = rootResourcePath sanitizedRootResourcePath = RequestParser._sanitizePath(rootResourcePath) response.rootResourcePath = RequestParser._checkPath(sanitizedRootResourcePath) - + for resource in response.resources if resource.path == originalRootResourcePath resource.path = sanitizedRootResourcePath diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.coffee index 608a3e55a4..c4b0f85941 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.coffee +++ b/services/clsi/test/unit/coffee/CompileManagerTests.coffee @@ -13,7 +13,7 @@ describe "CompileManager", -> "./ResourceWriter": @ResourceWriter = {} "./OutputFileFinder": @OutputFileFinder = {} "./OutputCacheManager": @OutputCacheManager = {} - "settings-sharelatex": @Settings = + "settings-sharelatex": @Settings = path: compilesDir: "/compiles/dir" synctexBaseDir: -> "/compile" @@ -108,6 +108,7 @@ describe "CompileManager", -> compiler: @compiler = "pdflatex" timeout: @timeout = 42000 imageName: @image = "example.com/image" + flags: @flags = ["-file-line-error"] @env = {} @Settings.compileDir = "compiles" @compileDir = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}" @@ -117,7 +118,7 @@ describe "CompileManager", -> @OutputCacheManager.saveOutputFiles = sinon.stub().callsArgWith(2, null, @build_files) @DraftModeManager.injectDraftMode = sinon.stub().callsArg(1) @TikzManager.checkMainFile = sinon.stub().callsArg(3, false) - + describe "normally", -> beforeEach -> @CompileManager.doCompile @request, @callback @@ -135,6 +136,7 @@ describe "CompileManager", -> compiler: @compiler timeout: @timeout image: @image + flags: @flags environment: @env }) .should.equal true @@ -146,15 +148,15 @@ describe "CompileManager", -> it "should return the output files", -> @callback.calledWith(null, @build_files).should.equal true - + it "should not inject draft mode by default", -> @DraftModeManager.injectDraftMode.called.should.equal false - + describe "with draft mode", -> beforeEach -> @request.draft = true @CompileManager.doCompile @request, @callback - + it "should inject the draft mode header", -> @DraftModeManager.injectDraftMode .calledWith(@compileDir + "/" + @rootResourcePath) @@ -173,6 +175,7 @@ describe "CompileManager", -> compiler: @compiler timeout: @timeout image: @image + flags: @flags environment: {'CHKTEX_OPTIONS': '-nall -e9 -e10 -w15 -w16', 'CHKTEX_EXIT_ON_ERROR':1, 'CHKTEX_ULIMIT_OPTIONS': '-t 5 -v 64000'} }) .should.equal true @@ -191,6 +194,7 @@ describe "CompileManager", -> compiler: @compiler timeout: @timeout image: @image + flags: @flags environment: @env }) .should.equal true @@ -297,7 +301,7 @@ describe "CompileManager", -> @CommandRunner.run .calledWith( "#{@project_id}-#{@user_id}", - ['/opt/synctex', "pdf", synctex_path, @page, @h, @v], + ['/opt/synctex', "pdf", synctex_path, @page, @h, @v], "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}", @Settings.clsi.docker.image, 60000, @@ -330,7 +334,7 @@ describe "CompileManager", -> @directory = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}" @file_path = "$COMPILE_DIR/#{@file_name}" @command =[ "texcount", "-nocol", "-inc", @file_path, "-out=" + @file_path + ".wc"] - + @CommandRunner.run .calledWith("#{@project_id}-#{@user_id}", @command, @directory, @image, @timeout, {}) .should.equal true diff --git a/services/clsi/test/unit/coffee/LatexRunnerTests.coffee b/services/clsi/test/unit/coffee/LatexRunnerTests.coffee index c26fa642b2..77c6edbcc0 100644 --- a/services/clsi/test/unit/coffee/LatexRunnerTests.coffee +++ b/services/clsi/test/unit/coffee/LatexRunnerTests.coffee @@ -59,3 +59,21 @@ describe "LatexRunner", -> mainFile = command.slice(-1)[0] mainFile.should.equal "$COMPILE_DIR/main-file.tex" + describe "with a flags option", -> + beforeEach -> + @LatexRunner.runLatex @project_id, + directory: @directory + mainFile: @mainFile + compiler: @compiler + image: @image + timeout: @timeout = 42000 + flags: ["-file-line-error", "-halt-on-error"] + @callback + + it "should include the flags in the command", -> + command = @CommandRunner.run.args[0][1] + flags = command.filter (arg) -> + (arg == "-file-line-error") || (arg == "-halt-on-error") + flags.length.should.equal 2 + flags[0].should.equal "-file-line-error" + flags[1].should.equal "-halt-on-error" diff --git a/services/clsi/test/unit/coffee/RequestParserTests.coffee b/services/clsi/test/unit/coffee/RequestParserTests.coffee index f63bc55e3d..e263e49207 100644 --- a/services/clsi/test/unit/coffee/RequestParserTests.coffee +++ b/services/clsi/test/unit/coffee/RequestParserTests.coffee @@ -1,6 +1,7 @@ SandboxedModule = require('sandboxed-module') sinon = require('sinon') require('chai').should() +expect = require('chai').expect modulePath = require('path').join __dirname, '../../../app/js/RequestParser' tk = require("timekeeper") @@ -22,7 +23,7 @@ describe "RequestParser", -> resources: [] @RequestParser = SandboxedModule.require modulePath, requires: "settings-sharelatex": @settings = {} - + afterEach -> tk.reset() @@ -55,7 +56,7 @@ describe "RequestParser", -> beforeEach -> delete @validRequest.compile.options.compiler @RequestParser.parse @validRequest, (error, @data) => - + it "should set the compiler to pdflatex by default", -> @data.compiler.should.equal "pdflatex" @@ -66,6 +67,21 @@ describe "RequestParser", -> it "should set the imageName", -> @data.imageName.should.equal "basicImageName/here:2017-1" + describe "with flags set", -> + beforeEach -> + @validRequest.compile.options.flags = ["-file-line-error"] + @RequestParser.parse @validRequest, (error, @data) => + + it "should set the flags attribute", -> + expect(@data.flags).to.deep.equal ["-file-line-error"] + + describe "with flags not specified", -> + beforeEach -> + @RequestParser.parse @validRequest, (error, @data) => + + it "it should have an empty flags list", -> + expect(@data.flags).to.deep.equal [] + describe "without a timeout specified", -> beforeEach -> delete @validRequest.compile.options.timeout @@ -88,7 +104,7 @@ describe "RequestParser", -> it "should set the timeout (in milliseconds)", -> @data.timeout.should.equal @validRequest.compile.options.timeout * 1000 - + describe "with a resource without a path", -> beforeEach -> delete @validResource.path @@ -175,7 +191,7 @@ describe "RequestParser", -> it "should return the url in the parsed response", -> @data.resources[0].url.should.equal @url - + describe "with a resource with a content attribute", -> beforeEach -> @validResource.content = @content = "Hello world" @@ -185,7 +201,7 @@ describe "RequestParser", -> it "should return the content in the parsed response", -> @data.resources[0].content.should.equal @content - + describe "without a root resource path", -> beforeEach -> delete @validRequest.compile.rootResourcePath @@ -225,13 +241,13 @@ describe "RequestParser", -> } @RequestParser.parse @validRequest, @callback @data = @callback.args[0][1] - + it "should return the escaped resource", -> @data.rootResourcePath.should.equal @goodPath - + it "should also escape the resource path", -> @data.resources[0].path.should.equal @goodPath - + describe "with a root resource path that has a relative path", -> beforeEach -> @validRequest.compile.rootResourcePath = "foo/../../bar.tex" From 880ec168270fe02ebb742e39d504e7b4c1580e66 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly <shane@kilkelly.me> Date: Thu, 6 Jun 2019 16:39:16 +0100 Subject: [PATCH 496/754] Increase the hard-timeout to 10 minutes. In practice most projects will still be limited to five minutes, but this allows us to bump up the limit for some projects, especially legacy v1 projects that have been imported to v2 --- services/clsi/app.coffee | 2 +- services/clsi/app/coffee/RequestParser.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/app.coffee b/services/clsi/app.coffee index fcf67c3ca5..9bcdfebc62 100644 --- a/services/clsi/app.coffee +++ b/services/clsi/app.coffee @@ -34,7 +34,7 @@ app.use Metrics.http.monitor(logger) # Compile requests can take longer than the default two # minutes (including file download time), so bump up the # timeout a bit. -TIMEOUT = 6 * 60 * 1000 +TIMEOUT = 10 * 60 * 1000 app.use (req, res, next) -> req.setTimeout TIMEOUT res.setTimeout TIMEOUT diff --git a/services/clsi/app/coffee/RequestParser.coffee b/services/clsi/app/coffee/RequestParser.coffee index c1912f62d9..9b94712480 100644 --- a/services/clsi/app/coffee/RequestParser.coffee +++ b/services/clsi/app/coffee/RequestParser.coffee @@ -2,7 +2,7 @@ settings = require("settings-sharelatex") module.exports = RequestParser = VALID_COMPILERS: ["pdflatex", "latex", "xelatex", "lualatex"] - MAX_TIMEOUT: 300 + MAX_TIMEOUT: 600 parse: (body, callback = (error, data) ->) -> response = {} From 1edda47b0e026a026552f8c0376e5032ce1863cc Mon Sep 17 00:00:00 2001 From: Shane Kilkelly <shane@kilkelly.me> Date: Tue, 18 Jun 2019 16:29:20 +0100 Subject: [PATCH 497/754] update logger and metrics --- services/clsi/Makefile | 2 +- services/clsi/buildscript.txt | 2 +- services/clsi/docker-compose.ci.yml | 2 +- services/clsi/docker-compose.yml | 2 +- services/clsi/npm-shrinkwrap.json | 571 +++++++++++++++------------- services/clsi/package.json | 4 +- 6 files changed, 317 insertions(+), 266 deletions(-) diff --git a/services/clsi/Makefile b/services/clsi/Makefile index ad83231887..ceb2c5095a 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.20 +# Version: 1.1.21 BUILD_NUMBER ?= local BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index 085d8cb8c3..9ba22b2003 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -5,4 +5,4 @@ clsi --dependencies=mongo,redis --docker-repos=gcr.io/overleaf-ops --build-target=docker ---script-version=1.1.20 +--script-version=1.1.21 diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index c6c35dbacf..39b6f427eb 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.20 +# Version: 1.1.21 version: "2" diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 3af0f7a8be..2691b89ed4 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.20 +# Version: 1.1.21 version: "2" diff --git a/services/clsi/npm-shrinkwrap.json b/services/clsi/npm-shrinkwrap.json index 5a2c4dc936..a221d4c695 100644 --- a/services/clsi/npm-shrinkwrap.json +++ b/services/clsi/npm-shrinkwrap.json @@ -5,56 +5,60 @@ "requires": true, "dependencies": { "@google-cloud/common": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.27.0.tgz", - "integrity": "sha1-MdvVLXRy8mBt8FsZSIfbK1lb2tU=", + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.32.1.tgz", + "integrity": "sha512-bLdPzFvvBMtVkwsoBtygE9oUm3yrNmPa71gvOgucYI/GqvNP2tb6RYsDHPq98kvignhcgHGDI5wyNgxaCo8bKQ==", "requires": { - "@google-cloud/projectify": "^0.3.2", - "@google-cloud/promisify": "^0.3.0", - "@types/duplexify": "^3.5.0", - "@types/request": "^2.47.0", - "arrify": "^1.0.1", + "@google-cloud/projectify": "^0.3.3", + "@google-cloud/promisify": "^0.4.0", + "@types/request": "^2.48.1", + "arrify": "^2.0.0", "duplexify": "^3.6.0", "ent": "^2.2.0", - "extend": "^3.0.1", - "google-auth-library": "^2.0.0", - "pify": "^4.0.0", - "retry-request": "^4.0.0" + "extend": "^3.0.2", + "google-auth-library": "^3.1.1", + "pify": "^4.0.1", + "retry-request": "^4.0.0", + "teeny-request": "^3.11.3" } }, "@google-cloud/debug-agent": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/debug-agent/-/debug-agent-3.0.1.tgz", - "integrity": "sha1-jGNJQkujDbEqTVaE7v19OvpmBQ4=", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@google-cloud/debug-agent/-/debug-agent-3.2.0.tgz", + "integrity": "sha512-fP87kYbS6aeDna08BivwQ1J260mwJGchRi99XdWCgqbRwuFac8ul0OT5i2wEeDSc5QaDX8ZuWQQ0igZvh1rTyQ==", "requires": { - "@google-cloud/common": "^0.27.0", - "@sindresorhus/is": "^0.13.0", - "acorn": "^5.0.3", + "@google-cloud/common": "^0.32.0", + "@sindresorhus/is": "^0.15.0", + "acorn": "^6.0.0", "coffeescript": "^2.0.0", "console-log-level": "^1.4.0", "extend": "^3.0.1", "findit2": "^2.2.3", - "gcp-metadata": "^0.9.0", + "gcp-metadata": "^1.0.0", "lodash.pickby": "^4.6.0", - "p-limit": "^2.0.0", + "p-limit": "^2.2.0", "pify": "^4.0.1", - "semver": "^5.5.0", + "semver": "^6.0.0", "source-map": "^0.6.1", - "split": "^1.0.0", - "teeny-request": "^3.11.1" + "split": "^1.0.0" }, "dependencies": { "coffeescript": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.3.2.tgz", - "integrity": "sha1-6FSnAg3+R7fPTdQSBC4y7x4mmBA=" + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.4.1.tgz", + "integrity": "sha512-34GV1aHrsMpTaO3KfMJL40ZNuvKDR/g98THHnE9bQj8HjMaZvSrLik99WWqyMhRtbe8V5hpx5iLgdcSvM/S2wg==" + }, + "semver": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz", + "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==" } } }, "@google-cloud/profiler": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@google-cloud/profiler/-/profiler-0.2.3.tgz", - "integrity": "sha1-Fj3738Mwuug1X+RuHlvgZTV7H1w=", + "integrity": "sha512-rNvtrFtIebIxZEJ/O0t8n7HciZGIXBo8DvHxWqAmsCaeLvkTtsaL6HmPkwxrNQ1IhbYWAxF+E/DwCiHyhKmgTg==", "requires": { "@google-cloud/common": "^0.26.0", "@types/console-log-level": "^1.4.0", @@ -76,7 +80,7 @@ "@google-cloud/common": { "version": "0.26.2", "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.26.2.tgz", - "integrity": "sha1-nFTiRxqEqgMelaJIJJduCA8lVkU=", + "integrity": "sha512-xJ2M/q3MrUbnYZuFlpF01caAlEhAUoRn0NXp93Hn3pkFpfSOG8YfbKbpBAHvcKVbBOAKVIwPsleNtuyuabUwLQ==", "requires": { "@google-cloud/projectify": "^0.3.2", "@google-cloud/promisify": "^0.3.0", @@ -92,70 +96,100 @@ "through2": "^3.0.0" } }, - "through2": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.0.tgz", - "integrity": "sha1-RotGHfnNn8wXDyLr9oUuRn5Xj/I=", + "@google-cloud/promisify": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-0.3.1.tgz", + "integrity": "sha512-QzB0/IMvB0eFxFK7Eqh+bfC8NLv3E9ScjWQrPOk6GgfNroxcVITdTlT8NRsRrcp5+QQJVPLkRqKG0PUdaWXmHw==" + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + }, + "gcp-metadata": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-0.9.3.tgz", + "integrity": "sha512-caV4S84xAjENtpezLCT/GILEAF5h/bC4cNqZFmt/tjTn8t+JBtTkQrgBrJu3857YdsnlM8rxX/PMcKGtE8hUlw==", "requires": { - "readable-stream": "2 || 3", - "xtend": "~4.0.1" + "gaxios": "^1.0.2", + "json-bigint": "^0.3.0" + } + }, + "google-auth-library": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-2.0.2.tgz", + "integrity": "sha512-FURxmo1hBVmcfLauuMRKOPYAPKht3dGuI2wjeJFalDUThO0HoYVjr4yxt5cgYSFm1dgUpmN9G/poa7ceTFAIiA==", + "requires": { + "axios": "^0.18.0", + "gcp-metadata": "^0.7.0", + "gtoken": "^2.3.0", + "https-proxy-agent": "^2.2.1", + "jws": "^3.1.5", + "lru-cache": "^5.0.0", + "semver": "^5.5.0" + }, + "dependencies": { + "gcp-metadata": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-0.7.0.tgz", + "integrity": "sha512-ffjC09amcDWjh3VZdkDngIo7WoluyC5Ag9PAYxZbmQLOLNI8lvPtoKTSCyU54j2gwy5roZh6sSMTfkY2ct7K3g==", + "requires": { + "axios": "^0.18.0", + "extend": "^3.0.1", + "retry-axios": "0.3.2" + } + } + } + }, + "through2": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "requires": { + "readable-stream": "2 || 3" } } } }, "@google-cloud/projectify": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-0.3.2.tgz", - "integrity": "sha1-7VTJjK5kbcA6dC6sKIGEoT0zpMI=" + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-0.3.3.tgz", + "integrity": "sha512-7522YHQ4IhaafgSunsFF15nG0TGVmxgXidy9cITMe+256RgqfcrfWphiMufW+Ou4kqagW/u3yxwbzVEW3dk2Uw==" }, "@google-cloud/promisify": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-0.3.1.tgz", - "integrity": "sha1-9kHm2USo4KBe4MsQkd+mAIm+zbo=" + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-0.4.0.tgz", + "integrity": "sha512-4yAHDC52TEMCNcMzVC8WlqnKKKq+Ssi2lXoUg9zWWkZ6U6tq9ZBRYLHHCRdfU+EU9YJsVmivwGcKYCjRGjnf4Q==" }, "@google-cloud/trace-agent": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@google-cloud/trace-agent/-/trace-agent-3.5.0.tgz", - "integrity": "sha1-GxU16eW26wAIXbbIWptLZJTjehY=", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@google-cloud/trace-agent/-/trace-agent-3.6.1.tgz", + "integrity": "sha512-KDo85aPN4gSxJ7oEIOlKd7aGENZFXAM1kbIn1Ds+61gh/K1CQWSyepgJo3nUpAwH6D1ezDWV7Iaf8ueoITc8Uw==", "requires": { - "@google-cloud/common": "^0.28.0", + "@google-cloud/common": "^0.32.1", "builtin-modules": "^3.0.0", "console-log-level": "^1.4.0", "continuation-local-storage": "^3.2.1", "extend": "^3.0.0", - "gcp-metadata": "^0.9.0", + "gcp-metadata": "^1.0.0", "hex2dec": "^1.0.1", "is": "^3.2.0", "methods": "^1.1.1", - "require-in-the-middle": "^3.0.0", - "semver": "^5.4.1", + "require-in-the-middle": "^4.0.0", + "semver": "^6.0.0", "shimmer": "^1.2.0", - "teeny-request": "^3.11.1", "uuid": "^3.0.1" }, "dependencies": { - "@google-cloud/common": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.28.0.tgz", - "integrity": "sha1-cOnTDHkOsPH/tuUpunI+ysO3prw=", - "requires": { - "@google-cloud/projectify": "^0.3.2", - "@google-cloud/promisify": "^0.3.0", - "@types/duplexify": "^3.5.0", - "@types/request": "^2.47.0", - "arrify": "^1.0.1", - "duplexify": "^3.6.0", - "ent": "^2.2.0", - "extend": "^3.0.1", - "google-auth-library": "^2.0.0", - "pify": "^4.0.0", - "retry-request": "^4.0.0" - } + "semver": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz", + "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==" }, "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha1-G0r0lV6zB3xQHCOHL8ZROBFYcTE=" + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" } } }, @@ -167,12 +201,12 @@ "@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha1-TIVzDlm5ofHzSQR9vyQpYDS7JzU=" + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" }, "@protobufjs/codegen": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha1-fvN/DQEPsCitGtWXIuUG2SYoFcs=" + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" }, "@protobufjs/eventemitter": { "version": "1.1.0", @@ -214,27 +248,24 @@ "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, "@sindresorhus/is": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.13.0.tgz", - "integrity": "sha1-qF0GwTZY0MyVhFN99KmUI0YfEUs=", - "requires": { - "symbol-observable": "^1.2.0" - } + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.15.0.tgz", + "integrity": "sha512-lu8BpxjAtRCAo5ifytTpCPCj99LF7o/2Myn+NXyNCBqvPYn7Pjd76AMmUB5l7XF1U6t0hcWrlEM5ESufW7wAeA==" }, "@types/caseless": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz", - "integrity": "sha1-l5TGnIOF0BkqzEcaVA0fjg0WIYo=" + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" }, "@types/console-log-level": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@types/console-log-level/-/console-log-level-1.4.0.tgz", - "integrity": "sha1-7/ccQa689RyLpa2LBdfVQkviuPM=" + "integrity": "sha512-x+OscEQwcx5Biair4enH7ov9W+clcqUWaZRaxn5IkT4yNWWjRr2oiYDkY/x1uXSTVZOQ2xlbFQySaQGB+VdXGQ==" }, "@types/duplexify": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/@types/duplexify/-/duplexify-3.6.0.tgz", - "integrity": "sha1-38grZL06IWj1vSZESvFlvwI33Ng=", + "integrity": "sha512-5zOA53RUlzN74bvrSGwjudssD9F3a797sDZQkiYpUOxW+WHaXTCPz4/d5Dgi6FKnOqZ2CpaTo0DhgIfsXAOE/A==", "requires": { "@types/node": "*" } @@ -242,7 +273,7 @@ "@types/form-data": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", - "integrity": "sha1-7is7jqoRwJOCiZU2BrdFtzjFSx4=", + "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", "requires": { "@types/node": "*" } @@ -255,7 +286,7 @@ "@types/long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz", - "integrity": "sha1-cZVR0jUtMBrIuB23Mqy2vcKNve8=" + "integrity": "sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==" }, "@types/node": { "version": "10.12.15", @@ -265,7 +296,7 @@ "@types/request": { "version": "2.48.1", "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz", - "integrity": "sha1-5ALWkapmcPu/8ZV7FfEnAjCrQvo=", + "integrity": "sha512-ZgEZ1TiD+KGA9LiAAPPJL68Id2UWfeSO62ijSXZjFJArVV+2pKcsVHmrcu+1oiE3q6eDGiFiSolRc4JHoerBBg==", "requires": { "@types/caseless": "*", "@types/form-data": "*", @@ -276,12 +307,12 @@ "@types/semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz", - "integrity": "sha1-FGwqKe59O65L8vyydGNuJkyBPEU=" + "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==" }, "@types/tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha1-ghh4uBv6uXG5OiZaVh1U6mH5BZ8=" + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", + "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==" }, "JSONStream": { "version": "1.3.2", @@ -297,6 +328,14 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg=" }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, "accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", @@ -307,14 +346,14 @@ } }, "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha1-Z6ojG/iBKXS4UjWpZ3Hra9B+onk=" + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==" }, "agent-base": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", - "integrity": "sha1-2J5ZmfeXh1Z0wH2H8mD8Qeg+jKk=", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", "requires": { "es6-promisify": "^5.0.0" } @@ -355,9 +394,9 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" }, "asn1": { "version": "0.2.4", @@ -386,7 +425,7 @@ "async-listener": { "version": "0.6.10", "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.10.tgz", - "integrity": "sha1-p8l6vlcLpgLXgic8DeYKUePhfLw=", + "integrity": "sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==", "requires": { "semver": "^5.3.0", "shimmer": "^1.1.0" @@ -408,12 +447,12 @@ "integrity": "sha1-8OAD2cqef1nHpQiUXXsu+aBKVC8=" }, "axios": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz", - "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz", + "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==", "requires": { - "follow-redirects": "^1.3.0", - "is-buffer": "^1.1.5" + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" } }, "balanced-match": { @@ -421,6 +460,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -432,12 +476,15 @@ "bignumber.js": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", - "integrity": "sha1-gMBIdZ2CaACAfEv9Uh5Q7bulel8=" + "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==" }, "bindings": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.1.tgz", - "integrity": "sha1-Ifx8bWfBhRbsWqooFbFF/3eybqU=" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } }, "bintrees": { "version": "1.0.1", @@ -520,9 +567,9 @@ "integrity": "sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8=" }, "builtin-modules": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.0.0.tgz", - "integrity": "sha1-Hlh9RLAGYg2QKGzHqSOLvGEpyrE=" + "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": "0.22.3", @@ -634,9 +681,9 @@ "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "console-log-level": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/console-log-level/-/console-log-level-1.4.0.tgz", - "integrity": "sha1-QDWBi+6jflhQoMA8jUUMpfLNEhc=" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/console-log-level/-/console-log-level-1.4.1.tgz", + "integrity": "sha512-VZzbIORbP+PPcN/gg3DXClTLPLg5Slwd5fL2MIc+o1qZ4BXBvWyc6QxPk6T/Mkr6IVjRpoAGf32XxP3ZWMVRcQ==" }, "content-disposition": { "version": "0.5.2", @@ -651,7 +698,7 @@ "continuation-local-storage": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz", - "integrity": "sha1-EfYT906RT+mzTJKtLSj+auHbf/s=", + "integrity": "sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==", "requires": { "async-listener": "^0.6.0", "emitter-listener": "^1.1.1" @@ -703,9 +750,9 @@ "integrity": "sha1-xPp8lUBKF6nD6Mp+FTcxK3NjMKw=" }, "delay": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/delay/-/delay-4.1.0.tgz", - "integrity": "sha1-R0zSiAnaQdGgSKcKHYNfR6w3fNI=" + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-4.3.0.tgz", + "integrity": "sha512-Lwaf3zVFDMBop1yDuFZ19F9WyGcZcGacsbdlZtWjQmM50tOcMntm1njF/Nb/Vjij3KaSvCF+sEYGKrrjObu2NA==" }, "delayed-stream": { "version": "1.0.0", @@ -807,9 +854,9 @@ "optional": true }, "duplexify": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz", - "integrity": "sha1-saeinEq/1jlYXvrszoDWZrHjQSU=", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", "requires": { "end-of-stream": "^1.0.0", "inherits": "^2.0.1", @@ -827,9 +874,9 @@ } }, "ecdsa-sig-formatter": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", - "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "requires": { "safe-buffer": "^5.0.1" } @@ -842,7 +889,7 @@ "emitter-listener": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", - "integrity": "sha1-VrFA6PaZI3Wz18ssqxzHQy2WMug=", + "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", "requires": { "shimmer": "^1.2.0" } @@ -866,9 +913,9 @@ "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=" }, "es6-promise": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz", - "integrity": "sha1-2m0NVpLvtGHggsFIF/4kJ9j10FQ=" + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" }, "es6-promisify": { "version": "5.0.0", @@ -894,6 +941,11 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, "express": { "version": "4.16.4", "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", @@ -958,6 +1010,16 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, + "fast-text-encoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz", + "integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ==" + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, "finalhandler": { "version": "1.1.1", "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", @@ -1071,19 +1133,20 @@ } }, "gaxios": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-1.0.4.tgz", - "integrity": "sha512-5IqL40mfNrpgUQpzWkVZHLjDq62QHVn5+HmwI0Hf1haKPzAE6DftUxoGAf9pnEARwlK1A6tWmtjxLVl/kCzCFA==", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-1.8.4.tgz", + "integrity": "sha512-BoENMnu1Gav18HcpV9IleMPZ9exM+AvUjrAOV4Mzs/vfz2Lu/ABv451iEXByKiMPn2M140uul1txXCg83sAENw==", "requires": { + "abort-controller": "^3.0.0", "extend": "^3.0.2", "https-proxy-agent": "^2.2.1", - "node-fetch": "^2.2.0" + "node-fetch": "^2.3.0" } }, "gcp-metadata": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-0.9.3.tgz", - "integrity": "sha1-H510lfdGChRSZIHynhFZbdVj3SY=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-1.0.0.tgz", + "integrity": "sha512-Q6HrgfrCQeEircnNP3rCcEgiDv7eF9+1B+1MMgpE190+/+0mjQR8PxeOaRgxZWmdDAF9EIryHB9g1moPiw1SbQ==", "requires": { "gaxios": "^1.0.2", "json-bigint": "^0.3.0" @@ -1116,37 +1179,27 @@ } }, "google-auth-library": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-2.0.2.tgz", - "integrity": "sha1-ejFdIDZ0Svavyth7IQ7mY4tA9Xs=", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-3.1.2.tgz", + "integrity": "sha512-cDQMzTotwyWMrg5jRO7q0A4TL/3GWBgO7I7q5xGKNiiFf9SmGY/OJ1YsLMgI2MVHHsEGyrqYnbnmV1AE+Z6DnQ==", "requires": { - "axios": "^0.18.0", - "gcp-metadata": "^0.7.0", - "gtoken": "^2.3.0", + "base64-js": "^1.3.0", + "fast-text-encoding": "^1.0.0", + "gaxios": "^1.2.1", + "gcp-metadata": "^1.0.0", + "gtoken": "^2.3.2", "https-proxy-agent": "^2.2.1", "jws": "^3.1.5", "lru-cache": "^5.0.0", "semver": "^5.5.0" - }, - "dependencies": { - "gcp-metadata": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-0.7.0.tgz", - "integrity": "sha1-bDXbtSvaMqQnu5yY9UI33dG1QG8=", - "requires": { - "axios": "^0.18.0", - "extend": "^3.0.1", - "retry-axios": "0.3.2" - } - } } }, "google-p12-pem": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-1.0.3.tgz", - "integrity": "sha1-PYrMFAVzM5pbynsvaksga76m2Nc=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-1.0.4.tgz", + "integrity": "sha512-SwLAUJqUfTB2iS+wFfSS/G9p7bt4eWcc2LyfvmUXe7cWp6p3mpxDo6LLI29MXdU6wvPcQ/up298X7GMC5ylAlA==", "requires": { - "node-forge": "^0.7.5", + "node-forge": "^0.8.0", "pify": "^4.0.0" } }, @@ -1164,26 +1217,21 @@ "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=" }, "gtoken": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-2.3.0.tgz", - "integrity": "sha512-Jc9/8mV630cZE9FC5tIlJCZNdUjwunvlwOtCz6IDlaiB4Sz68ki29a1+q97sWTnTYroiuF9B135rod9zrQdHLw==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-2.3.3.tgz", + "integrity": "sha512-EaB49bu/TCoNeQjhCYKI/CurooBKkGxIqFHsWABW0b25fobBYVTMe84A8EBVVZhl8emiUdNypil9huMOTmyAnw==", "requires": { - "axios": "^0.18.0", + "gaxios": "^1.0.4", "google-p12-pem": "^1.0.0", - "jws": "^3.1.4", + "jws": "^3.1.5", "mime": "^2.2.0", - "pify": "^3.0.0" + "pify": "^4.0.0" }, "dependencies": { "mime": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", - "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==" - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" } } }, @@ -1227,9 +1275,9 @@ } }, "hex2dec": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/hex2dec/-/hex2dec-1.1.1.tgz", - "integrity": "sha512-E1nK1KCHaX9NvY3wkCMpZxj3oGokO5fgjcKUBaOgSkkvNogm8ngb8isKtzlxnLT37/JXLODVYXz9ti99Bxz8gg==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/hex2dec/-/hex2dec-1.1.2.tgz", + "integrity": "sha512-Yu+q/XWr2fFQ11tHxPq4p4EiNkb2y+lAacJNhAdRXVfRIcDH6gi7htWFnnlIzvqHMHoWeIsfXlNAjZInpAOJDA==" }, "http-errors": { "version": "1.6.3", @@ -1255,7 +1303,7 @@ "https-proxy-agent": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", - "integrity": "sha1-UVUpcPoE1yPgTFbQQXjD+SWSu8A=", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", "requires": { "agent-base": "^4.1.0", "debug": "^3.1.0" @@ -1264,15 +1312,15 @@ "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha1-6D0X3hbYp++3cX7b5fsQE17uYps=", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "requires": { "ms": "^2.1.1" } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -1324,7 +1372,7 @@ "is": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", - "integrity": "sha1-Yc/23TxBk9uUo9YlggcrROVkXXk=" + "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==" }, "is-bluebird": { "version": "1.0.2", @@ -1332,9 +1380,9 @@ "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=" }, "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" }, "is-fullwidth-code-point": { "version": "1.0.0", @@ -1441,21 +1489,21 @@ } }, "jwa": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", - "integrity": "sha1-hyQOdsmAjb3hh4PPImTvSSnuUOY=", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", "requires": { "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.10", + "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "jws": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", - "integrity": "sha1-gNEtBbKT0ehB58uLTmnlYa3Pg08=", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", "requires": { - "jwa": "^1.1.5", + "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, @@ -1478,37 +1526,33 @@ "integrity": "sha1-feoh2MGNdwOifHBMFdO4SmfjOv8=" }, "logger-sharelatex": { - "version": "git+https://github.com/sharelatex/logger-sharelatex.git#8359f16a1546c481a5d1cde63de5b5b6bb569789", - "from": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.6.0", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/logger-sharelatex/-/logger-sharelatex-1.7.0.tgz", + "integrity": "sha512-9sxDGPSphOMDqUqGpOu/KxFAVcpydKggWv60g9D7++FDCxGkhLLn0kmBkDdgB00d1PadgX1CBMWKzIBpptDU/Q==", "requires": { - "bunyan": "1.5.1", - "coffee-script": "1.12.4", - "raven": "^1.1.3", - "request": "^2.88.0" + "bunyan": "1.8.12", + "raven": "1.1.3", + "request": "2.88.0" }, "dependencies": { "bunyan": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.5.1.tgz", - "integrity": "sha1-X259RMQ7lS9WsPQTCeOrEjkbTi0=", + "version": "1.8.12", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz", + "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=", "requires": { - "dtrace-provider": "~0.6", + "dtrace-provider": "~0.8", + "moment": "^2.10.6", "mv": "~2", "safe-json-stringify": "~1" } }, - "coffee-script": { - "version": "1.12.4", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.4.tgz", - "integrity": "sha1-/hvO2X/h+zknuZjytFYW4GWL4f8=" - }, "dtrace-provider": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.6.0.tgz", - "integrity": "sha1-CweNVReTfYcxAUUtkUZzdVe3XlE=", + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.7.tgz", + "integrity": "sha1-3JObTT4GIM/gwc2APQ0tftBP/QQ=", "optional": true, "requires": { - "nan": "^2.0.8" + "nan": "^2.10.0" } } } @@ -1516,12 +1560,12 @@ "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha1-mntxz7fTYaGU6lVSQckvdGjVvyg=" + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha1-HaJ+ZxAnGUdpXa9oSOhH8B2EuSA=", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "requires": { "yallist": "^3.0.2" } @@ -1561,8 +1605,9 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "metrics-sharelatex": { - "version": "git+https://github.com/sharelatex/metrics-sharelatex.git#3ac1621ef049e2f2d88a83b3a41011333d609662", - "from": "git+https://github.com/sharelatex/metrics-sharelatex.git#v2.0.12", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/metrics-sharelatex/-/metrics-sharelatex-2.2.0.tgz", + "integrity": "sha512-kjj3EdkrOJrENLFW/QHiPqBr5AbGEHeti90nMbw6sjKO2TOcuPJHT2Y66m8tqgotnMPKw+kXToRs8Rc9+0xuMQ==", "requires": { "@google-cloud/debug-agent": "^3.0.0", "@google-cloud/profiler": "^0.2.3", @@ -1573,11 +1618,6 @@ "underscore": "~1.6.0" }, "dependencies": { - "coffee-script": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", - "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=" - }, "lynx": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.1.1.tgz", @@ -1854,14 +1894,14 @@ "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" }, "node-fetch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", - "integrity": "sha1-Gh2UC7+5FqHT4CGfA36J5x+MX6U=" + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" }, "node-forge": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz", - "integrity": "sha1-/fO0GK7h+U8O9kLNY0hsd8qXJKw=" + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.4.tgz", + "integrity": "sha512-UOfdpxivIYY4g5tqp5FNRNgROVNxRACUxxJREntJLFaJr1E0UEqFtUIk0F/jYx/E+Y6sVXd0KDi/m5My0yGCVw==" }, "node-pre-gyp": { "version": "0.11.0", @@ -1975,17 +2015,17 @@ } }, "p-limit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", - "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "requires": { "p-try": "^2.0.0" } }, "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha1-hQgLuHxkaI+keZb+j3376CEXYLE=" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "parse-duration": { "version": "0.1.1", @@ -1993,9 +2033,9 @@ "integrity": "sha1-ExFN3JiRwezSgANiRFVN5DZHoiY=" }, "parse-ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.0.0.tgz", - "integrity": "sha1-ezZAKVEAyvP6AQDMzrVmNbYvnWI=" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==" }, "parseurl": { "version": "1.3.2", @@ -2010,7 +2050,7 @@ "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha1-1i27VnlAXXLEc37FhgDp3c8G0kw=" + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-to-regexp": { "version": "0.1.7", @@ -2025,12 +2065,12 @@ "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha1-SyzSXFDVmHNcUCkiJP2MbfQeMjE=" + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" }, "pretty-ms": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-4.0.0.tgz", - "integrity": "sha1-Mbr0G5T9AiJwmKqgO9YmCOsNbpI=", + "integrity": "sha512-qG66ahoLCwpLXD09ZPHSCbUWYTqdosB7SMP4OffgTgL2PBKXMuUsrk5Bwg8q4qPkjTXsKBMr+YK3Ltd/6F9s/Q==", "requires": { "parse-ms": "^2.0.0" } @@ -2041,9 +2081,9 @@ "integrity": "sha1-o31zL0JxtKsa0HDTVQjoKQeI/6o=" }, "prom-client": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-11.2.0.tgz", - "integrity": "sha512-4gUAq/GR5C8q5eWxOa7tA60AtmkMpbyBd/2btCayvd3h/7HzS0p/kESKRwggJgbFrfdhTCBpOwPAwKiI01Q0VQ==", + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-11.5.1.tgz", + "integrity": "sha512-AcFuxVgzoA/4nlpeg9SkM2HkDjNU3V7g2LCLwpudXSbcSLiFpRMVfsCoCY5RYeR/d9jkQng1mCmVKj1mPHvP0Q==", "requires": { "tdigest": "^0.1.1" } @@ -2051,7 +2091,7 @@ "protobufjs": { "version": "6.8.8", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", - "integrity": "sha1-yLTxKC/XqQ5vWxCe0RyEr4KQjnw=", + "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==", "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -2107,9 +2147,9 @@ "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" }, "raven": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/raven/-/raven-1.2.1.tgz", - "integrity": "sha1-lJwTTbAooZC3u/j3kKrlQbfAIL0=", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/raven/-/raven-1.1.3.tgz", + "integrity": "sha1-QnPBrm005CMPUbLAEEGjK5Iygio=", "requires": { "cookie": "0.3.1", "json-stringify-safe": "5.0.1", @@ -2201,12 +2241,28 @@ "integrity": "sha1-hINwjnzkxt+tmItQgPl4KbktIic=" }, "require-in-the-middle": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-3.1.0.tgz", - "integrity": "sha1-+vS5R8jY0liK5PJGSH4tcmKt0aQ=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-4.0.0.tgz", + "integrity": "sha512-GX12iFhCUzzNuIqvei0dTLUbBEjZ420KTY/MmDxe2GQKPDGyH/wgfGMWFABpnM/M6sLwC3IaSg8A95U6gIb+HQ==", "requires": { + "debug": "^4.1.1", "module-details-from-path": "^1.0.3", - "resolve": "^1.5.0" + "resolve": "^1.10.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } } }, "require-like": { @@ -2216,9 +2272,9 @@ "dev": true }, "resolve": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz", - "integrity": "sha1-oUxv36j5Kn3x2ZbLcQX6dEZY6gY=", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", + "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", "requires": { "path-parse": "^1.0.6" } @@ -2235,12 +2291,12 @@ "retry-axios": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/retry-axios/-/retry-axios-0.3.2.tgz", - "integrity": "sha1-V1fID1hbTMTEmGqi/9R6YMbTXhM=" + "integrity": "sha512-jp4YlI0qyDFfXiXGhkCOliBN1G7fRH03Nqy8YdShzGqbY5/9S2x/IR6C88ls2DFkbWuL3ASkP7QD3pVrNpPgwQ==" }, "retry-request": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.0.0.tgz", - "integrity": "sha1-XDZhZiebPhDp16oTJ0RnoFy2kpA=", + "integrity": "sha512-S4HNLaWcMP6r8E4TMH52Y7/pM8uNayOcTDDQNBwsCccL1uI+Ol2TljxRDPzaNfbhOB30+XWP5NnZkB3LiJxi1w==", "requires": { "through2": "^2.0.0" } @@ -2475,12 +2531,12 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "split": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha1-YFvZvjA6pZ+zX5Ip++oN3snqB9k=", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", "requires": { "through": "2" } @@ -2583,11 +2639,6 @@ "has-flag": "^2.0.0" } }, - "symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha1-wiaIrtTqs83C3+rLtWFmBWCgCAQ=" - }, "tar": { "version": "4.4.8", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", @@ -2658,7 +2709,7 @@ "teeny-request": { "version": "3.11.3", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-3.11.3.tgz", - "integrity": "sha1-M1xin3ZF5dZZk2LfLzIwxMvCOlU=", + "integrity": "sha512-CKncqSF7sH6p4rzCgkb/z/Pcos5efl0DmolzvlqRQUNcpRIruOhY9+T1FsIlyEbfWd7MsFpodROOwHYh2BaXzw==", "requires": { "https-proxy-agent": "^2.2.1", "node-fetch": "^2.2.0", @@ -2668,7 +2719,7 @@ "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha1-G0r0lV6zB3xQHCOHL8ZROBFYcTE=" + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" } } }, @@ -2697,7 +2748,7 @@ "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha1-AcHjnrMdB8t9A6lqcIIyYLIxMs0=", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "requires": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" diff --git a/services/clsi/package.json b/services/clsi/package.json index 61df397988..d01de305e4 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -28,9 +28,9 @@ "fs-extra": "^0.16.3", "heapdump": "^0.3.5", "lockfile": "^1.0.3", - "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.6.0", + "logger-sharelatex": "^1.7.0", "lynx": "0.0.11", - "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v2.0.12", + "metrics-sharelatex": "^2.2.0", "mkdirp": "0.3.5", "mysql": "2.6.2", "request": "^2.21.0", From 68e842b12a5ff520d204331fe5c45e1fecc9fe0c Mon Sep 17 00:00:00 2001 From: Tailing Yuan <yuantailing@gmail.com> Date: Fri, 4 Oct 2019 22:34:45 +0800 Subject: [PATCH 498/754] fix CompileManager and LocalCommandRunner --- services/clsi/app/coffee/CompileManager.coffee | 2 +- services/clsi/app/coffee/LocalCommandRunner.coffee | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index ad924e2b1e..65b78aaff8 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -250,7 +250,7 @@ module.exports = CompileManager = directory = getCompileDir(project_id, user_id) timeout = 60 * 1000 # increased to allow for large projects compileName = getCompileName(project_id, user_id) - CommandRunner.run compileName, command, directory, Settings.clsi.docker.image, timeout, {}, (error, output) -> + CommandRunner.run compileName, command, directory, Settings.clsi?.docker.image, timeout, {}, (error, output) -> if error? logger.err err:error, command:command, project_id:project_id, user_id:user_id, "error running synctex" return callback(error) diff --git a/services/clsi/app/coffee/LocalCommandRunner.coffee b/services/clsi/app/coffee/LocalCommandRunner.coffee index f47af00177..c5ef3c692c 100644 --- a/services/clsi/app/coffee/LocalCommandRunner.coffee +++ b/services/clsi/app/coffee/LocalCommandRunner.coffee @@ -5,7 +5,7 @@ logger.info "using standard command runner" module.exports = CommandRunner = run: (project_id, command, directory, image, timeout, environment, callback = (error) ->) -> - command = (arg.replace('$COMPILE_DIR', directory) for arg in command) + command = (arg.toString().replace('$COMPILE_DIR', directory) for arg in command) logger.log project_id: project_id, command: command, directory: directory, "running command" logger.warn "timeouts and sandboxing are not enabled with CommandRunner" @@ -15,7 +15,11 @@ module.exports = CommandRunner = env[key] = value for key, value of environment # run command as detached process so it has its own process group (which can be killed if needed) - proc = spawn command[0], command.slice(1), stdio: "inherit", cwd: directory, detached: true, env: env + proc = spawn command[0], command.slice(1), cwd: directory, env: env + + stdout = "" + proc.stdout.on "data", (data)-> + stdout += data proc.on "error", (err)-> logger.err err:err, project_id:project_id, command: command, directory: directory, "error running command" @@ -32,7 +36,7 @@ module.exports = CommandRunner = err.code = code return callback(err) else - callback() + callback(null, {"stdout": stdout}) return proc.pid # return process id to allow job to be killed if necessary From dab6e9aa8e9c285d95903e7b897a67727106c624 Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Tue, 22 Oct 2019 15:30:14 -0400 Subject: [PATCH 499/754] Send output files on timeout The unconventional use of callbacks to return both an error and data after compilation created a subtle bug where the output files were dropped by the LockManager in case of an error such as a timeout. This prevented the frontend to show error logs when a timeout occurs, creating confusion among users. We now attach the output files to the error so that they reach the controller and are sent back to the web service. --- .../clsi/app/coffee/CompileController.coffee | 19 ++++++++++--------- .../clsi/app/coffee/CompileManager.coffee | 5 +++-- services/clsi/app/coffee/DockerRunner.coffee | 10 +++++----- .../acceptance/coffee/TimeoutTests.coffee | 5 ++++- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.coffee index 73a7fd1ae5..4952d84568 100644 --- a/services/clsi/app/coffee/CompileController.coffee +++ b/services/clsi/app/coffee/CompileController.coffee @@ -26,21 +26,19 @@ module.exports = CompileController = status = "terminated" else if error?.validate status = "validation-#{error.validate}" + else if error?.timedout + status = "timedout" + logger.log err: error, project_id: request.project_id, "timeout running compile" else if error? - if error.timedout - status = "timedout" - logger.log err: error, project_id: request.project_id, "timeout running compile" - else - status = "error" - code = 500 - logger.warn err: error, project_id: request.project_id, "error running compile" - + status = "error" + code = 500 + logger.warn err: error, project_id: request.project_id, "error running compile" else status = "failure" for file in outputFiles if file.path?.match(/output\.pdf$/) status = "success" - + if status == "failure" logger.warn project_id: request.project_id, outputFiles:outputFiles, "project failed to compile successfully, no output.pdf generated" @@ -49,6 +47,9 @@ module.exports = CompileController = if file.path is "core" logger.error project_id:request.project_id, req:req, outputFiles:outputFiles, "core file found in output" + if error? + outputFiles = error.outputFiles || [] + timer.done() res.status(code or 200).send { compile: diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.coffee index 65b78aaff8..792beb8d16 100644 --- a/services/clsi/app/coffee/CompileManager.coffee +++ b/services/clsi/app/coffee/CompileManager.coffee @@ -106,10 +106,11 @@ module.exports = CompileManager = error = new Error("compilation") error.validate = "fail" # compile was killed by user, was a validation, or a compile which failed validation - if error?.terminated or error?.validate + if error?.terminated or error?.validate or error?.timedout OutputFileFinder.findOutputFiles resourceList, compileDir, (err, outputFiles) -> return callback(err) if err? - callback(error, outputFiles) # return output files so user can check logs + error.outputFiles = outputFiles # return output files so user can check logs + callback(error) return # compile completed normally return callback(error) if error? diff --git a/services/clsi/app/coffee/DockerRunner.coffee b/services/clsi/app/coffee/DockerRunner.coffee index 3c2ed9c581..6ea929f249 100644 --- a/services/clsi/app/coffee/DockerRunner.coffee +++ b/services/clsi/app/coffee/DockerRunner.coffee @@ -78,7 +78,7 @@ module.exports = DockerRunner = _callback(args...) # Only call the callback once _callback = () -> - + name = options.name streamEnded = false @@ -115,7 +115,7 @@ module.exports = DockerRunner = _getContainerOptions: (command, image, volumes, timeout, environment) -> timeoutInSeconds = timeout / 1000 - + dockerVolumes = {} for hostVol, dockerVol of volumes dockerVolumes[dockerVol] = {} @@ -148,7 +148,7 @@ module.exports = DockerRunner = "Ulimits": [{'Name': 'cpu', 'Soft': timeoutInSeconds+5, 'Hard': timeoutInSeconds+10}] "CapDrop": "ALL" "SecurityOpt": ["no-new-privileges"] - + if Settings.path?.synctexBinHostPath? options["HostConfig"]["Binds"].push("#{Settings.path.synctexBinHostPath}:/opt/synctex:ro") @@ -276,7 +276,7 @@ module.exports = DockerRunner = logger.log container_id: containerId, "timeout reached, killing container" container.kill(() ->) , timeout - + logger.log container_id: containerId, "waiting for docker container" container.wait (error, res) -> if error? @@ -355,4 +355,4 @@ module.exports = DockerRunner = , oneHour = 60 * 60 * 1000 , randomDelay -DockerRunner.startContainerMonitor() \ No newline at end of file +DockerRunner.startContainerMonitor() diff --git a/services/clsi/test/acceptance/coffee/TimeoutTests.coffee b/services/clsi/test/acceptance/coffee/TimeoutTests.coffee index c3acd8f9f8..b274dd54ff 100644 --- a/services/clsi/test/acceptance/coffee/TimeoutTests.coffee +++ b/services/clsi/test/acceptance/coffee/TimeoutTests.coffee @@ -15,7 +15,7 @@ describe "Timed out compile", -> \\documentclass{article} \\begin{document} \\def\\x{Hello!\\par\\x} - \\x + \\x \\end{document} ''' ] @@ -29,3 +29,6 @@ describe "Timed out compile", -> it "should return a timedout status", -> @body.compile.status.should.equal "timedout" + it "should return the log output file name", -> + outputFilePaths = @body.compile.outputFiles.map((x) => x.path) + outputFilePaths.should.include('output.log') From bef93667a5f72c599f6fbe2637fff56d9bc5cf74 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Thu, 24 Oct 2019 16:57:08 +0100 Subject: [PATCH 500/754] Bump build script to 1.1.23 --- services/clsi/Makefile | 4 ++-- services/clsi/buildscript.txt | 3 ++- services/clsi/docker-compose.ci.yml | 2 +- services/clsi/docker-compose.yml | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/services/clsi/Makefile b/services/clsi/Makefile index ceb2c5095a..a8a5c90ffe 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.21 +# Version: 1.1.23 BUILD_NUMBER ?= local BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) @@ -35,7 +35,7 @@ test_clean: $(DOCKER_COMPOSE) down -v -t 0 test_acceptance_pre_run: - @[ ! -f test/acceptance/scripts/pre-run ] && echo "clsi has no pre acceptance tests task" || $(DOCKER_COMPOSE) run --rm test_acceptance test/acceptance/scripts/pre-run + @[ ! -f test/acceptance/js/scripts/pre-run ] && echo "clsi has no pre acceptance tests task" || $(DOCKER_COMPOSE) run --rm test_acceptance test/acceptance/js/scripts/pre-run build: docker build --pull --tag ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ --tag gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index 9ba22b2003..acc98e3e45 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -4,5 +4,6 @@ clsi --acceptance-creds=None --dependencies=mongo,redis --docker-repos=gcr.io/overleaf-ops +--env-pass-through= --build-target=docker ---script-version=1.1.21 +--script-version=1.1.23 diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 39b6f427eb..408d88bb24 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.21 +# Version: 1.1.23 version: "2" diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 2691b89ed4..c41d7f4c58 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.21 +# Version: 1.1.23 version: "2" From e00b4e0c6a9e441802e1bc112688691af0b166dd Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Thu, 24 Oct 2019 16:58:14 +0100 Subject: [PATCH 501/754] Pass through TEXLIVE_IMAGE --- services/clsi/buildscript.txt | 2 +- services/clsi/docker-compose.ci.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index acc98e3e45..c065222480 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -4,6 +4,6 @@ clsi --acceptance-creds=None --dependencies=mongo,redis --docker-repos=gcr.io/overleaf-ops ---env-pass-through= +--env-pass-through=TEXLIVE_IMAGE --build-target=docker --script-version=1.1.23 diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 408d88bb24..55894f781f 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -26,6 +26,7 @@ services: POSTGRES_HOST: postgres MOCHA_GREP: ${MOCHA_GREP} NODE_ENV: test + TEXLIVE_IMAGE: depends_on: - mongo - redis From d194beb16501dfbbdc1f7743ca38d53b9b3efa67 Mon Sep 17 00:00:00 2001 From: Nate Stemen <nathanielstemen@gmail.com> Date: Fri, 25 Oct 2019 11:01:37 -0400 Subject: [PATCH 502/754] add public link to contributing docs --- services/clsi/.github/PULL_REQUEST_TEMPLATE.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/clsi/.github/PULL_REQUEST_TEMPLATE.md b/services/clsi/.github/PULL_REQUEST_TEMPLATE.md index ed25ee83c1..12bb2eeb3f 100644 --- a/services/clsi/.github/PULL_REQUEST_TEMPLATE.md +++ b/services/clsi/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,7 @@ -<!-- Please review https://github.com/overleaf/write_latex/blob/master/.github/CONTRIBUTING.md for guidance on what is expected in each section. --> + +<!-- ** This is an Overleaf public repository ** --> + +<!-- Please review https://github.com/overleaf/overleaf/blob/master/CONTRIBUTING.md for guidance on what is expected of a contribution. --> ### Description From 1982dfc1cba4e89596bdaef121bfcdd64f07ab65 Mon Sep 17 00:00:00 2001 From: Nate Stemen <nathanielstemen@gmail.com> Date: Fri, 25 Oct 2019 11:03:45 -0400 Subject: [PATCH 503/754] bump build script to 1.1.24 --- services/clsi/Makefile | 2 +- services/clsi/buildscript.txt | 3 ++- services/clsi/docker-compose.ci.yml | 2 +- services/clsi/docker-compose.yml | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/services/clsi/Makefile b/services/clsi/Makefile index a8a5c90ffe..ef3a7940eb 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.23 +# Version: 1.1.24 BUILD_NUMBER ?= local BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index c065222480..94c14b9004 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -6,4 +6,5 @@ clsi --docker-repos=gcr.io/overleaf-ops --env-pass-through=TEXLIVE_IMAGE --build-target=docker ---script-version=1.1.23 +--script-version=1.1.24 +--public-repo=True diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 55894f781f..8808d69171 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.23 +# Version: 1.1.24 version: "2" diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index c41d7f4c58..964f268c66 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.23 +# Version: 1.1.24 version: "2" From 7b4f7b4fb008d172cfe1c23866b52151c24537cf Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Mon, 28 Oct 2019 09:31:57 -0400 Subject: [PATCH 504/754] Upgrade logging and metrics modules The new versions add the ability to send logs directly to Stackdriver. --- services/clsi/npm-shrinkwrap.json | 945 +++++++++++++++++++++++++----- services/clsi/package.json | 4 +- 2 files changed, 810 insertions(+), 139 deletions(-) diff --git a/services/clsi/npm-shrinkwrap.json b/services/clsi/npm-shrinkwrap.json index a221d4c695..ac2f706b95 100644 --- a/services/clsi/npm-shrinkwrap.json +++ b/services/clsi/npm-shrinkwrap.json @@ -5,21 +5,19 @@ "requires": true, "dependencies": { "@google-cloud/common": { - "version": "0.32.1", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.32.1.tgz", - "integrity": "sha512-bLdPzFvvBMtVkwsoBtygE9oUm3yrNmPa71gvOgucYI/GqvNP2tb6RYsDHPq98kvignhcgHGDI5wyNgxaCo8bKQ==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-2.2.3.tgz", + "integrity": "sha512-lvw54mGKn8VqVIy2NzAk0l5fntBFX4UwQhHk6HaqkyCQ7WBl5oz4XhzKMtMilozF/3ObPcDogqwuyEWyZ6rnQQ==", "requires": { - "@google-cloud/projectify": "^0.3.3", - "@google-cloud/promisify": "^0.4.0", - "@types/request": "^2.48.1", + "@google-cloud/projectify": "^1.0.0", + "@google-cloud/promisify": "^1.0.0", "arrify": "^2.0.0", "duplexify": "^3.6.0", "ent": "^2.2.0", "extend": "^3.0.2", - "google-auth-library": "^3.1.1", - "pify": "^4.0.1", + "google-auth-library": "^5.5.0", "retry-request": "^4.0.0", - "teeny-request": "^3.11.3" + "teeny-request": "^5.2.1" } }, "@google-cloud/debug-agent": { @@ -43,18 +41,196 @@ "split": "^1.0.0" }, "dependencies": { + "@google-cloud/common": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.32.1.tgz", + "integrity": "sha512-bLdPzFvvBMtVkwsoBtygE9oUm3yrNmPa71gvOgucYI/GqvNP2tb6RYsDHPq98kvignhcgHGDI5wyNgxaCo8bKQ==", + "requires": { + "@google-cloud/projectify": "^0.3.3", + "@google-cloud/promisify": "^0.4.0", + "@types/request": "^2.48.1", + "arrify": "^2.0.0", + "duplexify": "^3.6.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "google-auth-library": "^3.1.1", + "pify": "^4.0.1", + "retry-request": "^4.0.0", + "teeny-request": "^3.11.3" + } + }, + "@google-cloud/projectify": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-0.3.3.tgz", + "integrity": "sha512-7522YHQ4IhaafgSunsFF15nG0TGVmxgXidy9cITMe+256RgqfcrfWphiMufW+Ou4kqagW/u3yxwbzVEW3dk2Uw==" + }, + "@google-cloud/promisify": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-0.4.0.tgz", + "integrity": "sha512-4yAHDC52TEMCNcMzVC8WlqnKKKq+Ssi2lXoUg9zWWkZ6U6tq9ZBRYLHHCRdfU+EU9YJsVmivwGcKYCjRGjnf4Q==" + }, "coffeescript": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.4.1.tgz", "integrity": "sha512-34GV1aHrsMpTaO3KfMJL40ZNuvKDR/g98THHnE9bQj8HjMaZvSrLik99WWqyMhRtbe8V5hpx5iLgdcSvM/S2wg==" }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "gaxios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-1.8.4.tgz", + "integrity": "sha512-BoENMnu1Gav18HcpV9IleMPZ9exM+AvUjrAOV4Mzs/vfz2Lu/ABv451iEXByKiMPn2M140uul1txXCg83sAENw==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^2.2.1", + "node-fetch": "^2.3.0" + } + }, + "gcp-metadata": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-1.0.0.tgz", + "integrity": "sha512-Q6HrgfrCQeEircnNP3rCcEgiDv7eF9+1B+1MMgpE190+/+0mjQR8PxeOaRgxZWmdDAF9EIryHB9g1moPiw1SbQ==", + "requires": { + "gaxios": "^1.0.2", + "json-bigint": "^0.3.0" + } + }, + "google-auth-library": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-3.1.2.tgz", + "integrity": "sha512-cDQMzTotwyWMrg5jRO7q0A4TL/3GWBgO7I7q5xGKNiiFf9SmGY/OJ1YsLMgI2MVHHsEGyrqYnbnmV1AE+Z6DnQ==", + "requires": { + "base64-js": "^1.3.0", + "fast-text-encoding": "^1.0.0", + "gaxios": "^1.2.1", + "gcp-metadata": "^1.0.0", + "gtoken": "^2.3.2", + "https-proxy-agent": "^2.2.1", + "jws": "^3.1.5", + "lru-cache": "^5.0.0", + "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "google-p12-pem": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-1.0.4.tgz", + "integrity": "sha512-SwLAUJqUfTB2iS+wFfSS/G9p7bt4eWcc2LyfvmUXe7cWp6p3mpxDo6LLI29MXdU6wvPcQ/up298X7GMC5ylAlA==", + "requires": { + "node-forge": "^0.8.0", + "pify": "^4.0.0" + } + }, + "gtoken": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-2.3.3.tgz", + "integrity": "sha512-EaB49bu/TCoNeQjhCYKI/CurooBKkGxIqFHsWABW0b25fobBYVTMe84A8EBVVZhl8emiUdNypil9huMOTmyAnw==", + "requires": { + "gaxios": "^1.0.4", + "google-p12-pem": "^1.0.0", + "jws": "^3.1.5", + "mime": "^2.2.0", + "pify": "^4.0.0" + } + }, + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + }, + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node-forge": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.5.tgz", + "integrity": "sha512-vFMQIWt+J/7FLNyKouZ9TazT74PRV3wgv9UT4cRjC8BffxFbKXkgIWR42URCPSnHm/QDz6BOlb2Q0U4+VQT67Q==" + }, "semver": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz", - "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==" + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "teeny-request": { + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-3.11.3.tgz", + "integrity": "sha512-CKncqSF7sH6p4rzCgkb/z/Pcos5efl0DmolzvlqRQUNcpRIruOhY9+T1FsIlyEbfWd7MsFpodROOwHYh2BaXzw==", + "requires": { + "https-proxy-agent": "^2.2.1", + "node-fetch": "^2.2.0", + "uuid": "^3.3.2" + } } } }, + "@google-cloud/logging": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/@google-cloud/logging/-/logging-5.5.3.tgz", + "integrity": "sha512-TZ/DzHod4icaC7wEEBm0PHYfbhvg0CbCVzKLsdAwj11xSD/egGNOsG5optEQcbAQEPrO1B5xBXfsE0wIBBYjpQ==", + "requires": { + "@google-cloud/common": "^2.2.2", + "@google-cloud/paginator": "^2.0.0", + "@google-cloud/projectify": "^1.0.0", + "@google-cloud/promisify": "^1.0.0", + "@opencensus/propagation-stackdriver": "0.0.18", + "arrify": "^2.0.0", + "dot-prop": "^5.1.0", + "eventid": "^0.1.2", + "extend": "^3.0.2", + "gcp-metadata": "^3.1.0", + "google-gax": "^1.7.5", + "is": "^3.3.0", + "on-finished": "^2.3.0", + "protobufjs": "^6.8.8", + "pumpify": "^2.0.0", + "snakecase-keys": "^3.0.0", + "stream-events": "^1.0.4", + "through2": "^3.0.0", + "type-fest": "^0.8.0" + } + }, + "@google-cloud/logging-bunyan": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/logging-bunyan/-/logging-bunyan-2.0.0.tgz", + "integrity": "sha512-9W9B8GQNMlBdQSV+c0492+sMMknn4/428EdSO1xv5Hn07P32N/e4T25y4Gnl9jlrItuZHIXRwYPVSHqUyGb1Zg==", + "requires": { + "@google-cloud/logging": "^5.5.2", + "google-auth-library": "^5.0.0" + } + }, + "@google-cloud/paginator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-2.0.1.tgz", + "integrity": "sha512-HZ6UTGY/gHGNriD7OCikYWL/Eu0sTEur2qqse2w6OVsz+57se3nTkqH14JIPxtf0vlEJ8IJN5w3BdZ22pjCB8g==", + "requires": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + } + }, "@google-cloud/profiler": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@google-cloud/profiler/-/profiler-0.2.3.tgz", @@ -96,6 +272,11 @@ "through2": "^3.0.0" } }, + "@google-cloud/projectify": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-0.3.3.tgz", + "integrity": "sha512-7522YHQ4IhaafgSunsFF15nG0TGVmxgXidy9cITMe+256RgqfcrfWphiMufW+Ou4kqagW/u3yxwbzVEW3dk2Uw==" + }, "@google-cloud/promisify": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-0.3.1.tgz", @@ -106,6 +287,25 @@ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "gaxios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-1.8.4.tgz", + "integrity": "sha512-BoENMnu1Gav18HcpV9IleMPZ9exM+AvUjrAOV4Mzs/vfz2Lu/ABv451iEXByKiMPn2M140uul1txXCg83sAENw==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^2.2.1", + "node-fetch": "^2.3.0" + } + }, "gcp-metadata": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-0.9.3.tgz", @@ -141,25 +341,72 @@ } } }, - "through2": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", - "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "google-p12-pem": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-1.0.4.tgz", + "integrity": "sha512-SwLAUJqUfTB2iS+wFfSS/G9p7bt4eWcc2LyfvmUXe7cWp6p3mpxDo6LLI29MXdU6wvPcQ/up298X7GMC5ylAlA==", "requires": { - "readable-stream": "2 || 3" + "node-forge": "^0.8.0", + "pify": "^4.0.0" + } + }, + "gtoken": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-2.3.3.tgz", + "integrity": "sha512-EaB49bu/TCoNeQjhCYKI/CurooBKkGxIqFHsWABW0b25fobBYVTMe84A8EBVVZhl8emiUdNypil9huMOTmyAnw==", + "requires": { + "gaxios": "^1.0.4", + "google-p12-pem": "^1.0.0", + "jws": "^3.1.5", + "mime": "^2.2.0", + "pify": "^4.0.0" + } + }, + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + }, + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node-forge": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.5.tgz", + "integrity": "sha512-vFMQIWt+J/7FLNyKouZ9TazT74PRV3wgv9UT4cRjC8BffxFbKXkgIWR42URCPSnHm/QDz6BOlb2Q0U4+VQT67Q==" + }, + "teeny-request": { + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-3.11.3.tgz", + "integrity": "sha512-CKncqSF7sH6p4rzCgkb/z/Pcos5efl0DmolzvlqRQUNcpRIruOhY9+T1FsIlyEbfWd7MsFpodROOwHYh2BaXzw==", + "requires": { + "https-proxy-agent": "^2.2.1", + "node-fetch": "^2.2.0", + "uuid": "^3.3.2" } } } }, "@google-cloud/projectify": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-0.3.3.tgz", - "integrity": "sha512-7522YHQ4IhaafgSunsFF15nG0TGVmxgXidy9cITMe+256RgqfcrfWphiMufW+Ou4kqagW/u3yxwbzVEW3dk2Uw==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-1.0.1.tgz", + "integrity": "sha512-xknDOmsMgOYHksKc1GPbwDLsdej8aRNIA17SlSZgQdyrcC0lx0OGo4VZgYfwoEU1YS8oUxF9Y+6EzDOb0eB7Xg==" }, "@google-cloud/promisify": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-0.4.0.tgz", - "integrity": "sha512-4yAHDC52TEMCNcMzVC8WlqnKKKq+Ssi2lXoUg9zWWkZ6U6tq9ZBRYLHHCRdfU+EU9YJsVmivwGcKYCjRGjnf4Q==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-1.0.2.tgz", + "integrity": "sha512-7WfV4R/3YV5T30WRZW0lqmvZy9hE2/p9MvpI34WuKa2Wz62mLu5XplGTFEMK6uTbJCLWUxTcZ4J4IyClKucE5g==" }, "@google-cloud/trace-agent": { "version": "3.6.1", @@ -181,18 +428,205 @@ "uuid": "^3.0.1" }, "dependencies": { - "semver": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz", - "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==" + "@google-cloud/common": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.32.1.tgz", + "integrity": "sha512-bLdPzFvvBMtVkwsoBtygE9oUm3yrNmPa71gvOgucYI/GqvNP2tb6RYsDHPq98kvignhcgHGDI5wyNgxaCo8bKQ==", + "requires": { + "@google-cloud/projectify": "^0.3.3", + "@google-cloud/promisify": "^0.4.0", + "@types/request": "^2.48.1", + "arrify": "^2.0.0", + "duplexify": "^3.6.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "google-auth-library": "^3.1.1", + "pify": "^4.0.1", + "retry-request": "^4.0.0", + "teeny-request": "^3.11.3" + } }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + "@google-cloud/projectify": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-0.3.3.tgz", + "integrity": "sha512-7522YHQ4IhaafgSunsFF15nG0TGVmxgXidy9cITMe+256RgqfcrfWphiMufW+Ou4kqagW/u3yxwbzVEW3dk2Uw==" + }, + "@google-cloud/promisify": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-0.4.0.tgz", + "integrity": "sha512-4yAHDC52TEMCNcMzVC8WlqnKKKq+Ssi2lXoUg9zWWkZ6U6tq9ZBRYLHHCRdfU+EU9YJsVmivwGcKYCjRGjnf4Q==" + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "gaxios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-1.8.4.tgz", + "integrity": "sha512-BoENMnu1Gav18HcpV9IleMPZ9exM+AvUjrAOV4Mzs/vfz2Lu/ABv451iEXByKiMPn2M140uul1txXCg83sAENw==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^2.2.1", + "node-fetch": "^2.3.0" + } + }, + "gcp-metadata": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-1.0.0.tgz", + "integrity": "sha512-Q6HrgfrCQeEircnNP3rCcEgiDv7eF9+1B+1MMgpE190+/+0mjQR8PxeOaRgxZWmdDAF9EIryHB9g1moPiw1SbQ==", + "requires": { + "gaxios": "^1.0.2", + "json-bigint": "^0.3.0" + } + }, + "google-auth-library": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-3.1.2.tgz", + "integrity": "sha512-cDQMzTotwyWMrg5jRO7q0A4TL/3GWBgO7I7q5xGKNiiFf9SmGY/OJ1YsLMgI2MVHHsEGyrqYnbnmV1AE+Z6DnQ==", + "requires": { + "base64-js": "^1.3.0", + "fast-text-encoding": "^1.0.0", + "gaxios": "^1.2.1", + "gcp-metadata": "^1.0.0", + "gtoken": "^2.3.2", + "https-proxy-agent": "^2.2.1", + "jws": "^3.1.5", + "lru-cache": "^5.0.0", + "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "google-p12-pem": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-1.0.4.tgz", + "integrity": "sha512-SwLAUJqUfTB2iS+wFfSS/G9p7bt4eWcc2LyfvmUXe7cWp6p3mpxDo6LLI29MXdU6wvPcQ/up298X7GMC5ylAlA==", + "requires": { + "node-forge": "^0.8.0", + "pify": "^4.0.0" + } + }, + "gtoken": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-2.3.3.tgz", + "integrity": "sha512-EaB49bu/TCoNeQjhCYKI/CurooBKkGxIqFHsWABW0b25fobBYVTMe84A8EBVVZhl8emiUdNypil9huMOTmyAnw==", + "requires": { + "gaxios": "^1.0.4", + "google-p12-pem": "^1.0.0", + "jws": "^3.1.5", + "mime": "^2.2.0", + "pify": "^4.0.0" + } + }, + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + }, + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node-forge": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.5.tgz", + "integrity": "sha512-vFMQIWt+J/7FLNyKouZ9TazT74PRV3wgv9UT4cRjC8BffxFbKXkgIWR42URCPSnHm/QDz6BOlb2Q0U4+VQT67Q==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "teeny-request": { + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-3.11.3.tgz", + "integrity": "sha512-CKncqSF7sH6p4rzCgkb/z/Pcos5efl0DmolzvlqRQUNcpRIruOhY9+T1FsIlyEbfWd7MsFpodROOwHYh2BaXzw==", + "requires": { + "https-proxy-agent": "^2.2.1", + "node-fetch": "^2.2.0", + "uuid": "^3.3.2" + } } } }, + "@grpc/grpc-js": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-0.6.9.tgz", + "integrity": "sha512-r1nDOEEiYmAsVYBaS4DPPqdwPOXPw7YhVOnnpPdWhlNtKbYzPash6DqWTTza9gBiYMA5d2Wiq6HzrPqsRaP4yA==", + "requires": { + "semver": "^6.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@grpc/proto-loader": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.2.tgz", + "integrity": "sha512-eBKD/FPxQoY1x6QONW2nBd54QUEyzcFP9FenujmoeDPy1rutVSHki1s/wR68F6O1QfCNDx+ayBH1O2CVNMzyyw==", + "requires": { + "lodash.camelcase": "^4.3.0", + "protobufjs": "^6.8.6" + } + }, + "@opencensus/core": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@opencensus/core/-/core-0.0.18.tgz", + "integrity": "sha512-PgRQXLyb3bLi8Z6pQct9erYFRdnYAZNQXAEVPf6Xq6IMkZaH20wiOTNNPxEckjI31mq5utgstAbwOn4gJiPjBQ==", + "requires": { + "continuation-local-storage": "^3.2.1", + "log-driver": "^1.2.7", + "semver": "^6.0.0", + "shimmer": "^1.2.0", + "uuid": "^3.2.1" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@opencensus/propagation-stackdriver": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@opencensus/propagation-stackdriver/-/propagation-stackdriver-0.0.18.tgz", + "integrity": "sha512-BLwfszIGAfqN2mqGf/atfEu84cWeoLM/YuXGfXDO1iDN2k5GXz4QFyhS8sz5l63HtsYuQqFuV+Ze7ZM0NvJp2A==", + "requires": { + "@opencensus/core": "^0.0.18", + "hex2dec": "^1.0.1", + "uuid": "^3.2.1" + } + }, + "@overleaf/o-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@overleaf/o-error/-/o-error-2.1.0.tgz", + "integrity": "sha512-Zd9sks9LrLw8ErHt/cXeWIkyxWAqNAvNGn7wIjLQJH6TTEEW835PWOhpch+hQwwWsTxWIx/JDj+IpZ3ouw925g==" + }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -270,14 +704,6 @@ "@types/node": "*" } }, - "@types/form-data": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", - "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", - "requires": { - "@types/node": "*" - } - }, "@types/geojson": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.6.tgz", @@ -294,14 +720,26 @@ "integrity": "sha1-IOhWUbYv2GZW5Xycm8dxqxVwvFk=" }, "@types/request": { - "version": "2.48.1", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz", - "integrity": "sha512-ZgEZ1TiD+KGA9LiAAPPJL68Id2UWfeSO62ijSXZjFJArVV+2pKcsVHmrcu+1oiE3q6eDGiFiSolRc4JHoerBBg==", + "version": "2.48.3", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.3.tgz", + "integrity": "sha512-3Wo2jNYwqgXcIz/rrq18AdOZUQB8cQ34CXZo+LUwPJNpvRAL86+Kc2wwI8mqpz9Cr1V+enIox5v+WZhy/p3h8w==", "requires": { "@types/caseless": "*", - "@types/form-data": "*", "@types/node": "*", - "@types/tough-cookie": "*" + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + }, + "dependencies": { + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + } } }, "@types/semver": { @@ -346,9 +784,9 @@ } }, "acorn": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", - "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==" + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz", + "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==" }, "agent-base": { "version": "4.3.0", @@ -461,9 +899,9 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base64-js": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" }, "bcrypt-pbkdf": { "version": "1.0.2", @@ -719,6 +1157,11 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "d64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d64/-/d64-1.0.0.tgz", + "integrity": "sha1-QAKofoUMv8n52XBrYPymE6MzbpA=" + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -841,6 +1284,14 @@ "tar-fs": "~1.16.3" } }, + "dot-prop": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.1.1.tgz", + "integrity": "sha512-QCHI6Lkf+9fJMpwfAFsTvbiSh6ujoPmhCLiDvD/n4dGtLvHfhuBwPdN6z2x4YSOwwtTcLoO/LP70xELWGF/JVA==", + "requires": { + "is-obj": "^2.0.0" + } + }, "dottie": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.1.tgz", @@ -946,6 +1397,15 @@ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" }, + "eventid": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/eventid/-/eventid-0.1.2.tgz", + "integrity": "sha1-CyMtPiROpbHVKJhBQOpprH7IkhU=", + "requires": { + "d64": "^1.0.0", + "uuid": "^3.0.1" + } + }, "express": { "version": "4.16.4", "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", @@ -1133,22 +1593,23 @@ } }, "gaxios": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-1.8.4.tgz", - "integrity": "sha512-BoENMnu1Gav18HcpV9IleMPZ9exM+AvUjrAOV4Mzs/vfz2Lu/ABv451iEXByKiMPn2M140uul1txXCg83sAENw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.1.0.tgz", + "integrity": "sha512-Gtpb5sdQmb82sgVkT2GnS2n+Kx4dlFwbeMYcDlD395aEvsLCSQXJJcHt7oJ2LrGxDEAeiOkK79Zv2A8Pzt6CFg==", "requires": { "abort-controller": "^3.0.0", "extend": "^3.0.2", - "https-proxy-agent": "^2.2.1", + "https-proxy-agent": "^3.0.0", + "is-stream": "^2.0.0", "node-fetch": "^2.3.0" } }, "gcp-metadata": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-1.0.0.tgz", - "integrity": "sha512-Q6HrgfrCQeEircnNP3rCcEgiDv7eF9+1B+1MMgpE190+/+0mjQR8PxeOaRgxZWmdDAF9EIryHB9g1moPiw1SbQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-3.2.0.tgz", + "integrity": "sha512-ympv+yQ6k5QuWCuwQqnGEvFGS7MBKdcQdj1i188v3bW9QLFIchTGaBCEZxSQapT0jffdn1vdt8oJhB5VBWQO1Q==", "requires": { - "gaxios": "^1.0.2", + "gaxios": "^2.0.1", "json-bigint": "^0.3.0" } }, @@ -1179,28 +1640,53 @@ } }, "google-auth-library": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-3.1.2.tgz", - "integrity": "sha512-cDQMzTotwyWMrg5jRO7q0A4TL/3GWBgO7I7q5xGKNiiFf9SmGY/OJ1YsLMgI2MVHHsEGyrqYnbnmV1AE+Z6DnQ==", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.5.1.tgz", + "integrity": "sha512-zCtjQccWS/EHYyFdXRbfeSGM/gW+d7uMAcVnvXRnjBXON5ijo6s0nsObP0ifqileIDSbZjTlLtgo+UoN8IFJcg==", "requires": { + "arrify": "^2.0.0", "base64-js": "^1.3.0", "fast-text-encoding": "^1.0.0", - "gaxios": "^1.2.1", - "gcp-metadata": "^1.0.0", - "gtoken": "^2.3.2", - "https-proxy-agent": "^2.2.1", + "gaxios": "^2.1.0", + "gcp-metadata": "^3.2.0", + "gtoken": "^4.1.0", "jws": "^3.1.5", - "lru-cache": "^5.0.0", - "semver": "^5.5.0" + "lru-cache": "^5.0.0" + } + }, + "google-gax": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-1.7.5.tgz", + "integrity": "sha512-Tz2DFs8umzDcCBTi2W1cY4vEgAKaYRj70g6Hh/MiiZaJizrly7PgyxsIYUGi7sOpEuAbARQymYKvy5mNi8hEbg==", + "requires": { + "@grpc/grpc-js": "0.6.9", + "@grpc/proto-loader": "^0.5.1", + "abort-controller": "^3.0.0", + "duplexify": "^3.6.0", + "google-auth-library": "^5.0.0", + "is-stream-ended": "^0.1.4", + "lodash.at": "^4.6.0", + "lodash.has": "^4.5.2", + "node-fetch": "^2.6.0", + "protobufjs": "^6.8.8", + "retry-request": "^4.0.0", + "semver": "^6.0.0", + "walkdir": "^0.4.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } } }, "google-p12-pem": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-1.0.4.tgz", - "integrity": "sha512-SwLAUJqUfTB2iS+wFfSS/G9p7bt4eWcc2LyfvmUXe7cWp6p3mpxDo6LLI29MXdU6wvPcQ/up298X7GMC5ylAlA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.2.tgz", + "integrity": "sha512-UfnEARfJKI6pbmC1hfFFm+UAcZxeIwTiEcHfqKe/drMsXD/ilnVjF7zgOGpHXyhuvX6jNJK3S8A0hOQjwtFxEw==", "requires": { - "node-forge": "^0.8.0", - "pify": "^4.0.0" + "node-forge": "^0.9.0" } }, "graceful-fs": { @@ -1217,15 +1703,14 @@ "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=" }, "gtoken": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-2.3.3.tgz", - "integrity": "sha512-EaB49bu/TCoNeQjhCYKI/CurooBKkGxIqFHsWABW0b25fobBYVTMe84A8EBVVZhl8emiUdNypil9huMOTmyAnw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.1.0.tgz", + "integrity": "sha512-wqyn2gf5buzEZN4QNmmiiW2i2JkEdZnL7Z/9p44RtZqgt4077m4khRgAYNuu8cBwHWCc6MsP6eDUn/KkF6jFIw==", "requires": { - "gaxios": "^1.0.4", - "google-p12-pem": "^1.0.0", + "gaxios": "^2.0.0", + "google-p12-pem": "^2.0.0", "jws": "^3.1.5", - "mime": "^2.2.0", - "pify": "^4.0.0" + "mime": "^2.2.0" }, "dependencies": { "mime": { @@ -1290,6 +1775,25 @@ "statuses": ">= 1.4.0 < 2" } }, + "http-proxy-agent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "requires": { + "agent-base": "4", + "debug": "3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -1301,11 +1805,11 @@ } }, "https-proxy-agent": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", - "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz", + "integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==", "requires": { - "agent-base": "^4.1.0", + "agent-base": "^4.3.0", "debug": "^3.1.0" }, "dependencies": { @@ -1380,9 +1884,9 @@ "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=" }, "is-buffer": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", - "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" }, "is-fullwidth-code-point": { "version": "1.0.0", @@ -1392,6 +1896,21 @@ "number-is-nan": "^1.0.0" } }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" + }, + "is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==" + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -1520,19 +2039,42 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", "integrity": "sha1-s56mIp72B+zYniyN8SU2iRysm40=" }, + "lodash.at": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.at/-/lodash.at-4.6.0.tgz", + "integrity": "sha1-k83OZk8KGZTqM9181A4jr9EbD/g=" + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + }, + "lodash.has": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", + "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=" + }, "lodash.pickby": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz", "integrity": "sha1-feoh2MGNdwOifHBMFdO4SmfjOv8=" }, + "log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==" + }, "logger-sharelatex": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/logger-sharelatex/-/logger-sharelatex-1.7.0.tgz", - "integrity": "sha512-9sxDGPSphOMDqUqGpOu/KxFAVcpydKggWv60g9D7++FDCxGkhLLn0kmBkDdgB00d1PadgX1CBMWKzIBpptDU/Q==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/logger-sharelatex/-/logger-sharelatex-1.9.0.tgz", + "integrity": "sha512-yVTuha82047IiMOQLgQHCZGKkJo6I2+2KtiFKpgkIooR2yZaoTEvAeoMwBesSDSpGUpvUJ/+9UI+PmRyc+PQKQ==", "requires": { + "@google-cloud/logging-bunyan": "^2.0.0", + "@overleaf/o-error": "^2.0.0", "bunyan": "1.8.12", "raven": "1.1.3", - "request": "2.88.0" + "request": "2.88.0", + "yn": "^3.1.1" }, "dependencies": { "bunyan": { @@ -1547,13 +2089,19 @@ } }, "dtrace-provider": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.7.tgz", - "integrity": "sha1-3JObTT4GIM/gwc2APQ0tftBP/QQ=", + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", + "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", "optional": true, "requires": { - "nan": "^2.10.0" + "nan": "^2.14.0" } + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "optional": true } } }, @@ -1584,6 +2132,11 @@ "statsd-parser": "~0.0.4" } }, + "map-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", + "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==" + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1605,9 +2158,9 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "metrics-sharelatex": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/metrics-sharelatex/-/metrics-sharelatex-2.2.0.tgz", - "integrity": "sha512-kjj3EdkrOJrENLFW/QHiPqBr5AbGEHeti90nMbw6sjKO2TOcuPJHT2Y66m8tqgotnMPKw+kXToRs8Rc9+0xuMQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/metrics-sharelatex/-/metrics-sharelatex-2.3.0.tgz", + "integrity": "sha512-qQv4UhI0Pn89WtIEUkysy4fgaCxmIw2S7+2pUB5b15Q4dzleHNplop5peTEOf8FIcURFshjPPJiLOGCJAYph7Q==", "requires": { "@google-cloud/debug-agent": "^3.0.0", "@google-cloud/profiler": "^0.2.3", @@ -1615,7 +2168,8 @@ "coffee-script": "1.6.0", "lynx": "~0.1.1", "prom-client": "^11.1.3", - "underscore": "~1.6.0" + "underscore": "~1.6.0", + "yn": "^3.1.1" }, "dependencies": { "lynx": { @@ -1899,9 +2453,9 @@ "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" }, "node-forge": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.4.tgz", - "integrity": "sha512-UOfdpxivIYY4g5tqp5FNRNgROVNxRACUxxJREntJLFaJr1E0UEqFtUIk0F/jYx/E+Y6sVXd0KDi/m5My0yGCVw==" + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.1.tgz", + "integrity": "sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==" }, "node-pre-gyp": { "version": "0.11.0", @@ -2015,9 +2569,9 @@ } }, "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", "requires": { "p-try": "^2.0.0" } @@ -2081,9 +2635,9 @@ "integrity": "sha1-o31zL0JxtKsa0HDTVQjoKQeI/6o=" }, "prom-client": { - "version": "11.5.1", - "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-11.5.1.tgz", - "integrity": "sha512-AcFuxVgzoA/4nlpeg9SkM2HkDjNU3V7g2LCLwpudXSbcSLiFpRMVfsCoCY5RYeR/d9jkQng1mCmVKj1mPHvP0Q==", + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-11.5.3.tgz", + "integrity": "sha512-iz22FmTbtkyL2vt0MdDFY+kWof+S9UB/NACxSn2aJcewtw+EERsen0urSkZ2WrHseNdydsvcxCTAnPcSMZZv4Q==", "requires": { "tdigest": "^0.1.1" } @@ -2131,6 +2685,48 @@ "once": "^1.3.1" } }, + "pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "requires": { + "duplexify": "^4.1.1", + "inherits": "^2.0.3", + "pump": "^3.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" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -2156,6 +2752,13 @@ "lsmod": "1.0.0", "stack-trace": "0.0.9", "uuid": "3.0.0" + }, + "dependencies": { + "uuid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz", + "integrity": "sha1-Zyj8BFnEUNeWqZwxg3VpvfZy1yg=" + } } }, "raw-body": { @@ -2241,13 +2844,13 @@ "integrity": "sha1-hINwjnzkxt+tmItQgPl4KbktIic=" }, "require-in-the-middle": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-4.0.0.tgz", - "integrity": "sha512-GX12iFhCUzzNuIqvei0dTLUbBEjZ420KTY/MmDxe2GQKPDGyH/wgfGMWFABpnM/M6sLwC3IaSg8A95U6gIb+HQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-4.0.1.tgz", + "integrity": "sha512-EfkM2zANyGkrfIExsECMeNn/uzjvHrE9h36yLXSavmrDiH4tgDNvltAmEKnt4PNLbqKPHZz+uszW2wTKrLUX0w==", "requires": { "debug": "^4.1.1", "module-details-from-path": "^1.0.3", - "resolve": "^1.10.0" + "resolve": "^1.12.0" }, "dependencies": { "debug": { @@ -2272,9 +2875,9 @@ "dev": true }, "resolve": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", - "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", "requires": { "path-parse": "^1.0.6" } @@ -2294,11 +2897,27 @@ "integrity": "sha512-jp4YlI0qyDFfXiXGhkCOliBN1G7fRH03Nqy8YdShzGqbY5/9S2x/IR6C88ls2DFkbWuL3ASkP7QD3pVrNpPgwQ==" }, "retry-request": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.0.0.tgz", - "integrity": "sha512-S4HNLaWcMP6r8E4TMH52Y7/pM8uNayOcTDDQNBwsCccL1uI+Ol2TljxRDPzaNfbhOB30+XWP5NnZkB3LiJxi1w==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.1.tgz", + "integrity": "sha512-BINDzVtLI2BDukjWmjAIRZ0oglnCAkpP2vQjM3jdLhmT62h0xnQgciPwBRDAvHqpkPT2Wo1XuUyLyn6nbGrZQQ==", "requires": { - "through2": "^2.0.0" + "debug": "^4.1.1", + "through2": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } } }, "rimraf": { @@ -2528,6 +3147,15 @@ } } }, + "snakecase-keys": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/snakecase-keys/-/snakecase-keys-3.1.0.tgz", + "integrity": "sha512-QM038drLbhdOY5HcRQVjO1ZJ1WR7yV5D5TIBzcOB/g3f5HURHhfpYEnvOyzXet8K+MQsgeIUA7O7vn90nAX6EA==", + "requires": { + "map-obj": "^4.0.0", + "to-snake-case": "^1.0.0" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2594,6 +3222,14 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "requires": { + "stubs": "^3.0.0" + } + }, "stream-shift": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", @@ -2630,6 +3266,11 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, + "stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=" + }, "supports-color": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", @@ -2707,20 +3348,15 @@ } }, "teeny-request": { - "version": "3.11.3", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-3.11.3.tgz", - "integrity": "sha512-CKncqSF7sH6p4rzCgkb/z/Pcos5efl0DmolzvlqRQUNcpRIruOhY9+T1FsIlyEbfWd7MsFpodROOwHYh2BaXzw==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-5.3.0.tgz", + "integrity": "sha512-sN9E3JvEBe2CFqB/jpJpw1erWD1C7MxyYCxogHFCQSyZfkHYcdf4wzVQSw7FZxbwcfnS+FP0W9BS0mp6SEOKjg==", "requires": { - "https-proxy-agent": "^2.2.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^3.0.0", "node-fetch": "^2.2.0", + "stream-events": "^1.0.5", "uuid": "^3.3.2" - }, - "dependencies": { - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - } } }, "terraformer": { @@ -2746,12 +3382,11 @@ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "readable-stream": "2 || 3" } }, "timekeeper": { @@ -2765,6 +3400,27 @@ "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=" }, + "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=" + }, + "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=", + "requires": { + "to-space-case": "^1.0.0" + } + }, + "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=", + "requires": { + "to-no-case": "^1.0.0" + } + }, "toposort-class": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", @@ -2805,6 +3461,11 @@ "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", "dev": true }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + }, "type-is": { "version": "1.6.16", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", @@ -2848,9 +3509,9 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz", - "integrity": "sha1-Zyj8BFnEUNeWqZwxg3VpvfZy1yg=" + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" }, "v8-profiler-node8": { "version": "6.0.1", @@ -2908,6 +3569,11 @@ "extsprintf": "^1.2.0" } }, + "walkdir": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", + "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==" + }, "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", @@ -2943,6 +3609,11 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", "integrity": "sha1-tLBJ4xS+VF486AIjbWzSLNkcPek=" + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" } } } diff --git a/services/clsi/package.json b/services/clsi/package.json index d01de305e4..24984d1f50 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -28,9 +28,9 @@ "fs-extra": "^0.16.3", "heapdump": "^0.3.5", "lockfile": "^1.0.3", - "logger-sharelatex": "^1.7.0", + "logger-sharelatex": "^1.9.0", "lynx": "0.0.11", - "metrics-sharelatex": "^2.2.0", + "metrics-sharelatex": "^2.3.0", "mkdirp": "0.3.5", "mysql": "2.6.2", "request": "^2.21.0", From 58bc71a0d2bc8341fa235405fff6cfc96779ae21 Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Thu, 7 Nov 2019 08:27:24 -0500 Subject: [PATCH 505/754] Show output files in subfolders This fixes a tiny regexp bug that prevents output files in subfolders from being shown in the "Other logs & files" panel. We also downgrade the corresponding log because it's very noisy and does not indicate a problem. --- services/clsi/app/coffee/OutputCacheManager.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee index 23e179c425..5ef92ec602 100644 --- a/services/clsi/app/coffee/OutputCacheManager.coffee +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -65,7 +65,7 @@ module.exports = OutputCacheManager = async.mapSeries outputFiles, (file, cb) -> # don't send dot files as output, express doesn't serve them if OutputCacheManager._fileIsHidden(file.path) - logger.warn compileDir: compileDir, path: file.path, "ignoring dotfile in output" + logger.debug compileDir: compileDir, path: file.path, "ignoring dotfile in output" return cb() # copy other files into cache directory if valid newFile = _.clone(file) @@ -150,7 +150,7 @@ module.exports = OutputCacheManager = , callback _fileIsHidden: (path) -> - return path?.match(/^\.|\/./)? + return path?.match(/^\.|\/\./)? _checkFileIsSafe: (src, callback = (error, isSafe) ->) -> # check if we have a valid file to copy into the cache From e0ba485081ca92c896e4b17bba1e3a77197ac5bb Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Tue, 4 Feb 2020 10:50:38 -0500 Subject: [PATCH 506/754] Upgrade to local node:10.18.1 image --- services/clsi/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index d2bcb1efe2..3499f1789e 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -1,4 +1,4 @@ -FROM node:10.15.0 as app +FROM gcr.io/overleaf-ops/node:10.18.1 as app WORKDIR /app @@ -12,7 +12,7 @@ COPY . /app RUN npm run compile:all -FROM node:10.15.0 +FROM gcr.io/overleaf-ops/node:10.18.1 COPY --from=app /app /app From 55ccb1b191028cd290bd5ba6b48e1bc40f5fcdd0 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Thu, 6 Feb 2020 03:32:28 +0000 Subject: [PATCH 507/754] update to gcr.io/overleaf-ops/node:10.19.0 --- services/clsi/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 3499f1789e..d9b6c69145 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -1,4 +1,4 @@ -FROM gcr.io/overleaf-ops/node:10.18.1 as app +FROM gcr.io/overleaf-ops/node:10.19.0 as app WORKDIR /app @@ -12,7 +12,7 @@ COPY . /app RUN npm run compile:all -FROM gcr.io/overleaf-ops/node:10.18.1 +FROM gcr.io/overleaf-ops/node:10.19.0 COPY --from=app /app /app From 0ab94f96c1c6ca2c53e02f7ea52b8f8d3a33047f Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <das7pad@outlook.com> Date: Fri, 7 Feb 2020 14:46:24 +0100 Subject: [PATCH 508/754] [misc] use node:10.19.0 as base image Also adjust the node version in the other build-script files. --- services/clsi/.nvmrc | 2 +- services/clsi/Dockerfile | 4 ++-- services/clsi/buildscript.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/clsi/.nvmrc b/services/clsi/.nvmrc index f9fb144f9a..5b7269c0a9 100644 --- a/services/clsi/.nvmrc +++ b/services/clsi/.nvmrc @@ -1 +1 @@ -10.15.0 +10.19.0 diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index d9b6c69145..ecca5f4f5e 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -1,4 +1,4 @@ -FROM gcr.io/overleaf-ops/node:10.19.0 as app +FROM node:10.19.0 as app WORKDIR /app @@ -12,7 +12,7 @@ COPY . /app RUN npm run compile:all -FROM gcr.io/overleaf-ops/node:10.19.0 +FROM node:10.19.0 COPY --from=app /app /app diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index 94c14b9004..fefbb77ef5 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -1,6 +1,6 @@ clsi --language=coffeescript ---node-version=10.15.0 +--node-version=10.19.0 --acceptance-creds=None --dependencies=mongo,redis --docker-repos=gcr.io/overleaf-ops From 659cd4437763fac9bf5740dcdf8ce34ed24edc74 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Thu, 6 Feb 2020 14:46:30 +0000 Subject: [PATCH 509/754] support other runtimes --- services/clsi/app/coffee/DockerRunner.coffee | 4 +++- services/clsi/config/settings.defaults.coffee | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/coffee/DockerRunner.coffee b/services/clsi/app/coffee/DockerRunner.coffee index 6ea929f249..8b49410e4b 100644 --- a/services/clsi/app/coffee/DockerRunner.coffee +++ b/services/clsi/app/coffee/DockerRunner.coffee @@ -149,10 +149,12 @@ module.exports = DockerRunner = "CapDrop": "ALL" "SecurityOpt": ["no-new-privileges"] - if Settings.path?.synctexBinHostPath? options["HostConfig"]["Binds"].push("#{Settings.path.synctexBinHostPath}:/opt/synctex:ro") + if Settings.clsi.docker.runtime? + options["HostConfig"]["Runtime"] = Settings.clsi.docker.runtime + if Settings.clsi.docker.seccomp_profile? options.HostConfig.SecurityOpt.push "seccomp=#{Settings.clsi.docker.seccomp_profile}" diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.coffee index ad3f04d8bd..60075b78de 100644 --- a/services/clsi/config/settings.defaults.coffee +++ b/services/clsi/config/settings.defaults.coffee @@ -50,6 +50,7 @@ if process.env["DOCKER_RUNNER"] module.exports.clsi = dockerRunner: process.env["DOCKER_RUNNER"] == "true" docker: + runtime: process.env["DOCKER_RUNTIME"] image: process.env["TEXLIVE_IMAGE"] or "quay.io/sharelatex/texlive-full:2017.1" env: HOME: "/tmp" From b064fede63032b9456970e0b5ce7d03de926c11b Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <das7pad@outlook.com> Date: Mon, 10 Feb 2020 17:10:44 +0100 Subject: [PATCH 510/754] [misc] update the build scripts to 1.3.5 --- services/clsi/Dockerfile | 19 ++++++++++----- services/clsi/Jenkinsfile | 1 + services/clsi/Makefile | 9 ++++++- services/clsi/buildscript.txt | 6 ++--- services/clsi/docker-compose-config.yml | 2 +- services/clsi/docker-compose.ci.yml | 14 +++++------ services/clsi/docker-compose.yml | 31 +++++++++++-------------- 7 files changed, 46 insertions(+), 36 deletions(-) diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index ecca5f4f5e..9dcf70c3f9 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -1,6 +1,17 @@ -FROM node:10.19.0 as app +# This file was auto-generated, do not edit it directly. +# Instead run bin/update_build_scripts from +# https://github.com/sharelatex/sharelatex-dev-environment +# Version: 1.3.5 + +FROM node:10.19.0 as base WORKDIR /app +COPY install_deps.sh /app +RUN chmod 0755 ./install_deps.sh && ./install_deps.sh +ENTRYPOINT ["/bin/sh", "entrypoint.sh"] +COPY entrypoint.sh /app + +FROM base as app #wildcard as some files may not be in all repos COPY package*.json npm-shrink*.json /app/ @@ -12,12 +23,8 @@ COPY . /app RUN npm run compile:all -FROM node:10.19.0 +FROM base COPY --from=app /app /app -WORKDIR /app -RUN chmod 0755 ./install_deps.sh && ./install_deps.sh -ENTRYPOINT ["/bin/sh", "entrypoint.sh"] - CMD ["node", "--expose-gc", "app.js"] diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index 82bd9736de..47a663829d 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -16,6 +16,7 @@ pipeline { } stages { + stage('Install') { steps { withCredentials([usernamePassword(credentialsId: 'GITHUB_INTEGRATION', usernameVariable: 'GH_AUTH_USERNAME', passwordVariable: 'GH_AUTH_PASSWORD')]) { diff --git a/services/clsi/Makefile b/services/clsi/Makefile index ef3a7940eb..13785bc22e 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -1,7 +1,7 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.24 +# Version: 1.3.5 BUILD_NUMBER ?= local BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) @@ -28,14 +28,20 @@ test_unit: test_acceptance: test_clean test_acceptance_pre_run test_acceptance_run +test_acceptance_debug: test_clean test_acceptance_pre_run test_acceptance_run_debug + test_acceptance_run: @[ ! -d test/acceptance ] && echo "clsi has no acceptance tests" || $(DOCKER_COMPOSE) run --rm test_acceptance +test_acceptance_run_debug: + @[ ! -d test/acceptance ] && echo "clsi has no acceptance tests" || $(DOCKER_COMPOSE) run -p 127.0.0.9:19999:19999 --rm test_acceptance npm run test:acceptance -- --inspect=0.0.0.0:19999 --inspect-brk + test_clean: $(DOCKER_COMPOSE) down -v -t 0 test_acceptance_pre_run: @[ ! -f test/acceptance/js/scripts/pre-run ] && echo "clsi has no pre acceptance tests task" || $(DOCKER_COMPOSE) run --rm test_acceptance test/acceptance/js/scripts/pre-run + build: docker build --pull --tag ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ --tag gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ @@ -48,4 +54,5 @@ publish: docker push $(DOCKER_REPO)/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) + .PHONY: clean test test_unit test_acceptance test_clean build publish diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index fefbb77ef5..e6e202ca70 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -1,10 +1,10 @@ clsi +--public-repo=True --language=coffeescript +--env-add= --node-version=10.19.0 --acceptance-creds=None --dependencies=mongo,redis --docker-repos=gcr.io/overleaf-ops --env-pass-through=TEXLIVE_IMAGE ---build-target=docker ---script-version=1.1.24 ---public-repo=True +--script-version=1.3.5 diff --git a/services/clsi/docker-compose-config.yml b/services/clsi/docker-compose-config.yml index c8b7dcc274..392f8feae9 100644 --- a/services/clsi/docker-compose-config.yml +++ b/services/clsi/docker-compose-config.yml @@ -1,4 +1,4 @@ -version: "2" +version: "2.3" services: dev: diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 8808d69171..d80a4ce160 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -1,9 +1,9 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.24 +# Version: 1.3.5 -version: "2" +version: "2.3" services: test_unit: @@ -28,12 +28,13 @@ services: NODE_ENV: test TEXLIVE_IMAGE: depends_on: - - mongo - - redis + mongo: + condition: service_healthy + redis: + condition: service_healthy command: npm run test:acceptance:_run - tar: build: . image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER @@ -41,9 +42,8 @@ services: - ./:/tmp/build/ command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs . user: root - redis: image: redis mongo: - image: mongo:3.4 + image: mongo:3.6 diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 964f268c66..8cc5973301 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -1,13 +1,15 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.1.24 +# Version: 1.3.5 -version: "2" +version: "2.3" services: test_unit: - build: . + build: + context: . + target: base volumes: - .:/app working_dir: /app @@ -17,7 +19,9 @@ services: command: npm run test:unit test_acceptance: - build: . + build: + context: . + target: base volumes: - .:/app working_dir: /app @@ -33,24 +37,15 @@ services: LOG_LEVEL: ERROR NODE_ENV: test depends_on: - - mongo - - redis + mongo: + condition: service_healthy + redis: + condition: service_healthy command: npm run test:acceptance - - - tar: - build: . - image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER - volumes: - - ./:/tmp/build/ - command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs . - user: root - redis: image: redis mongo: - image: mongo:3.4 - + image: mongo:3.6 From 5a28cbad29de4e6e8696959105d3e31b2c2f6eee Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Wed, 12 Feb 2020 12:37:00 +0000 Subject: [PATCH 511/754] remove unused .travis.yml file --- services/clsi/.travis.yml | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 services/clsi/.travis.yml diff --git a/services/clsi/.travis.yml b/services/clsi/.travis.yml deleted file mode 100644 index b6fb0e91be..0000000000 --- a/services/clsi/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: node_js - -before_install: - - npm install -g grunt-cli - -install: - - npm install - - grunt install - -script: - - grunt test:unit - -services: - - redis-server - - mongodb From e37c261bb089298cb6b494bf044be70cf71124ab Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:06:09 +0100 Subject: [PATCH 512/754] removed unneeded default function arg preventing from decaffeination --- services/clsi/app/coffee/DockerLockManager.coffee | 2 +- services/clsi/app/coffee/LockManager.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/coffee/DockerLockManager.coffee b/services/clsi/app/coffee/DockerLockManager.coffee index 739f2cd19f..bf90f02345 100644 --- a/services/clsi/app/coffee/DockerLockManager.coffee +++ b/services/clsi/app/coffee/DockerLockManager.coffee @@ -46,7 +46,7 @@ module.exports = LockManager = logger.error {key:key, lock: existingLock}, "tried to release lock that has gone" callback() - runWithLock: (key, runner = ( (releaseLock = (error) ->) -> ), callback = ( (error) -> )) -> + runWithLock: (key, runner, callback = ( (error) -> )) -> LockManager.getLock key, (error, lockValue) -> return callback(error) if error? runner (error1, args...) -> diff --git a/services/clsi/app/coffee/LockManager.coffee b/services/clsi/app/coffee/LockManager.coffee index afa3cca971..5d9fe26a06 100644 --- a/services/clsi/app/coffee/LockManager.coffee +++ b/services/clsi/app/coffee/LockManager.coffee @@ -9,7 +9,7 @@ module.exports = LockManager = MAX_LOCK_WAIT_TIME: 15000 # 10s maximum time to spend trying to get the lock LOCK_STALE: 5*60*1000 # 5 mins time until lock auto expires - runWithLock: (path, runner = ((releaseLock = (error) ->) ->), callback = ((error) ->)) -> + runWithLock: (path, runner, callback = ((error) ->)) -> lockOpts = wait: @MAX_LOCK_WAIT_TIME pollPeriod: @LOCK_TEST_INTERVAL From 564707b1d17d36ef251c23654b05e7e816f3f551 Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:10:00 +0100 Subject: [PATCH 513/754] decaffeinate: update build scripts to es --- services/clsi/.dockerignore | 2 -- services/clsi/.eslintrc | 65 +++++++++++++++++++++++++++++++++++ services/clsi/.prettierrc | 8 +++++ services/clsi/Dockerfile | 1 - services/clsi/Jenkinsfile | 7 ++++ services/clsi/Makefile | 15 +++++--- services/clsi/buildscript.txt | 2 +- services/clsi/nodemon.json | 7 ++-- services/clsi/package.json | 18 +++++----- 9 files changed, 102 insertions(+), 23 deletions(-) create mode 100644 services/clsi/.eslintrc create mode 100644 services/clsi/.prettierrc diff --git a/services/clsi/.dockerignore b/services/clsi/.dockerignore index 386f26df30..ba1c3442de 100644 --- a/services/clsi/.dockerignore +++ b/services/clsi/.dockerignore @@ -5,5 +5,3 @@ gitrev .npm .nvmrc nodemon.json -app.js -**/js/* diff --git a/services/clsi/.eslintrc b/services/clsi/.eslintrc new file mode 100644 index 0000000000..42a4b5cace --- /dev/null +++ b/services/clsi/.eslintrc @@ -0,0 +1,65 @@ +// this file was auto-generated, do not edit it directly. +// instead run bin/update_build_scripts from +// https://github.com/sharelatex/sharelatex-dev-environment +// Version: 1.3.5 +{ + "extends": [ + "standard", + "prettier", + "prettier/standard" + ], + "parserOptions": { + "ecmaVersion": 2017 + }, + "plugins": [ + "mocha", + "chai-expect", + "chai-friendly" + ], + "env": { + "node": true, + "mocha": true + }, + "rules": { + // Swap the no-unused-expressions rule with a more chai-friendly one + "no-unused-expressions": 0, + "chai-friendly/no-unused-expressions": "error" + }, + "overrides": [ + { + // Test specific rules + "files": ["test/**/*.js"], + "globals": { + "expect": true + }, + "rules": { + // mocha-specific rules + "mocha/handle-done-callback": "error", + "mocha/no-exclusive-tests": "error", + "mocha/no-global-tests": "error", + "mocha/no-identical-title": "error", + "mocha/no-nested-tests": "error", + "mocha/no-pending-tests": "error", + "mocha/no-skipped-tests": "error", + "mocha/no-mocha-arrows": "error", + + // chai-specific rules + "chai-expect/missing-assertion": "error", + "chai-expect/terminating-properties": "error", + + // prefer-arrow-callback applies to all callbacks, not just ones in mocha tests. + // we don't enforce this at the top-level - just in tests to manage `this` scope + // based on mocha's context mechanism + "mocha/prefer-arrow-callback": "error" + } + }, + { + // Backend specific rules + "files": ["app/**/*.js", "app.js", "index.js"], + "rules": { + // don't allow console.log in backend code + "no-console": "error" + } + } + ] +} diff --git a/services/clsi/.prettierrc b/services/clsi/.prettierrc new file mode 100644 index 0000000000..5845b82113 --- /dev/null +++ b/services/clsi/.prettierrc @@ -0,0 +1,8 @@ +# This file was auto-generated, do not edit it directly. +# Instead run bin/update_build_scripts from +# https://github.com/sharelatex/sharelatex-dev-environment +# Version: 1.3.5 +{ + "semi": false, + "singleQuote": true +} diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 9dcf70c3f9..9faccd4957 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -21,7 +21,6 @@ RUN npm install --quiet COPY . /app -RUN npm run compile:all FROM base diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile index 47a663829d..c7b961eb19 100644 --- a/services/clsi/Jenkinsfile +++ b/services/clsi/Jenkinsfile @@ -37,6 +37,13 @@ pipeline { } } + stage('Linting') { + steps { + sh 'DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" make format' + sh 'DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" make lint' + } + } + stage('Unit Tests') { steps { sh 'DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" make test_unit' diff --git a/services/clsi/Makefile b/services/clsi/Makefile index 13785bc22e..88234f2a23 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -16,12 +16,17 @@ DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ clean: docker rmi ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) docker rmi gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) - rm -f app.js - rm -rf app/js - rm -rf test/unit/js - rm -rf test/acceptance/js -test: test_unit test_acceptance +format: + $(DOCKER_COMPOSE) run --rm test_unit npm run format + +format_fix: + $(DOCKER_COMPOSE) run --rm test_unit npm run format:fix + +lint: + $(DOCKER_COMPOSE) run --rm test_unit npm run lint + +test: format lint test_unit test_acceptance test_unit: @[ ! -d test/unit ] && echo "clsi has no unit tests" || $(DOCKER_COMPOSE) run --rm test_unit diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index e6e202ca70..9cccf33aa7 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -1,6 +1,6 @@ clsi --public-repo=True ---language=coffeescript +--language=es --env-add= --node-version=10.19.0 --acceptance-creds=None diff --git a/services/clsi/nodemon.json b/services/clsi/nodemon.json index 98db38d71b..5826281b84 100644 --- a/services/clsi/nodemon.json +++ b/services/clsi/nodemon.json @@ -10,10 +10,9 @@ }, "watch": [ - "app/coffee/", - "app.coffee", + "app/js/", + "app.js", "config/" ], - "ext": "coffee" - + "ext": "js" } diff --git a/services/clsi/package.json b/services/clsi/package.json index 24984d1f50..55395380c5 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -7,17 +7,15 @@ "url": "https://github.com/sharelatex/clsi-sharelatex.git" }, "scripts": { - "compile:app": "([ -e app/coffee ] && coffee -m $COFFEE_OPTIONS -o app/js -c app/coffee || echo 'No CoffeeScript folder to compile') && ( [ -e app.coffee ] && coffee -m $COFFEE_OPTIONS -c app.coffee || echo 'No CoffeeScript app to compile')", - "start": "npm run compile:app && node $NODE_APP_OPTIONS app.js", - "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 30000 --exit $@ test/acceptance/js", - "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- --grep=$MOCHA_GREP", - "test:unit:_run": "mocha --recursive --reporter spec --exit $@ test/unit/js", - "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- --grep=$MOCHA_GREP", - "compile:unit_tests": "[ ! -e test/unit/coffee ] && echo 'No unit tests to compile' || coffee -o test/unit/js -c test/unit/coffee", - "compile:acceptance_tests": "[ ! -e test/acceptance/coffee ] && echo 'No acceptance tests to compile' || coffee -o test/acceptance/js -c test/acceptance/coffee", - "compile:all": "npm run compile:app && npm run compile:unit_tests && npm run compile:acceptance_tests && npm run compile:smoke_tests", + "start": "node $NODE_APP_OPTIONS app.js", + "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", + "test:acceptance": "npm run test:acceptance:_run -- --grep=$MOCHA_GREP", + "test:unit:_run": "mocha --recursive --reporter spec $@ test/unit/js", + "test:unit": "npm run test:unit:_run -- --grep=$MOCHA_GREP", "nodemon": "nodemon --config nodemon.json", - "compile:smoke_tests": "[ ! -e test/smoke/coffee ] && echo 'No smoke tests to compile' || coffee -o test/smoke/js -c test/smoke/coffee" + "lint": "node_modules/.bin/eslint .", + "format": "node_modules/.bin/prettier-eslint '**/*.js' --list-different", + "format:fix": "node_modules/.bin/prettier-eslint '**/*.js' --write" }, "author": "James Allen <james@sharelatex.com>", "dependencies": { From 2d4ea9febfe4a64dd88dbd1d256911c467fc1b1c Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:11:31 +0100 Subject: [PATCH 514/754] decaffeinate: update .gitignore --- services/clsi/.gitignore | 6 ------ 1 file changed, 6 deletions(-) diff --git a/services/clsi/.gitignore b/services/clsi/.gitignore index 048a75b9a8..912e380116 100644 --- a/services/clsi/.gitignore +++ b/services/clsi/.gitignore @@ -1,13 +1,7 @@ **.swp node_modules -app/js -test/unit/js -test/smoke/js -test/acceptance/js test/acceptance/fixtures/tmp compiles -app.js -**/*.map .DS_Store *~ cache From 66ffcb626f4fa9b487e0c43f17aa93239e833d37 Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:13:44 +0100 Subject: [PATCH 515/754] decaffeinate: add eslint and prettier packages --- services/clsi/npm-shrinkwrap.json | 3088 +++++++++++++++++++++++++++++ services/clsi/package.json | 18 + 2 files changed, 3106 insertions(+) diff --git a/services/clsi/npm-shrinkwrap.json b/services/clsi/npm-shrinkwrap.json index ac2f706b95..40fed3c24f 100644 --- a/services/clsi/npm-shrinkwrap.json +++ b/services/clsi/npm-shrinkwrap.json @@ -4,6 +4,166 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/generator": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", + "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-function-name": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", + "dev": true + }, + "@babel/runtime": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", + "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "@babel/template": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", + "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/traverse": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", + "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.4", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.8.4", + "@babel/types": "^7.8.3", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } + } + }, "@google-cloud/common": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-2.2.3.tgz", @@ -704,11 +864,23 @@ "@types/node": "*" } }, + "@types/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", + "dev": true + }, "@types/geojson": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.6.tgz", "integrity": "sha1-PgKXJyjGkkjCrwjWCkjLuGgP/98=" }, + "@types/json-schema": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", + "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==", + "dev": true + }, "@types/long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz", @@ -752,6 +924,59 @@ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==" }, + "@typescript-eslint/experimental-utils": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz", + "integrity": "sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "1.13.0", + "eslint-scope": "^4.0.0" + }, + "dependencies": { + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + } + } + }, + "@typescript-eslint/parser": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.13.0.tgz", + "integrity": "sha512-ITMBs52PCPgLb2nGPoeT4iU3HdQZHcPaZVw+7CsFagRJHUhyeTgorEwHXhFf3e7Evzi8oujKNpHc8TONth8AdQ==", + "dev": true, + "requires": { + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "1.13.0", + "@typescript-eslint/typescript-estree": "1.13.0", + "eslint-visitor-keys": "^1.0.0" + } + }, + "@typescript-eslint/typescript-estree": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz", + "integrity": "sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==", + "dev": true, + "requires": { + "lodash.unescape": "4.0.1", + "semver": "5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true + } + } + }, "JSONStream": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz", @@ -788,6 +1013,12 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz", "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==" }, + "acorn-jsx": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", + "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", + "dev": true + }, "agent-base": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", @@ -807,11 +1038,29 @@ "uri-js": "^4.2.2" } }, + "ansi-escapes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", + "integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", @@ -826,11 +1075,59 @@ "readable-stream": "^2.0.6" } }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "aria-query": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", + "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7", + "commander": "^2.11.0" + }, + "dependencies": { + "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 + } + } + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "array-includes": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", + "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "is-string": "^1.0.5" + } + }, + "array.prototype.flat": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", + "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, "arrify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", @@ -855,6 +1152,18 @@ "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", "dev": true }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, "async": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/async/-/async-0.2.9.tgz", @@ -893,6 +1202,26 @@ "is-buffer": "^2.0.2" } }, + "axobject-query": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.1.2.tgz", + "integrity": "sha512-ICt34ZmrVt8UQnvPl6TVyDTkmhXmAyAT4Jh5ugfGUX4MOrZ+U/ZY6/sdylRw3qGNr9Ub5AJsaHeDMzNLehRdOQ==", + "dev": true + }, + "babel-eslint": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.3.tgz", + "integrity": "sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -960,6 +1289,12 @@ "type-is": "~1.6.16" } }, + "boolify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/boolify/-/boolify-1.0.1.tgz", + "integrity": "sha1-tcCeF8rNET0Rt7s+04TMASmU2Gs=", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1039,6 +1374,29 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "camelcase-keys": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.1.2.tgz", + "integrity": "sha512-QfFrU0CIw2oltVvpndW32kuJ/9YOJwUnmWrjlXt1nnJZHCaS9i6bfOpg9R4Lw8aZjStkJWM+jc0cdXjWBgVJSw==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + } + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -1054,11 +1412,111 @@ "deep-eql": "0.1.3" } }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, "chownr": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", "integrity": "sha1-VHJri4//TfBTxCGH6AH7RBLfFJQ=" }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "cls-bluebird": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz", @@ -1084,6 +1542,21 @@ "integrity": "sha1-bdTeHrYveE2MjYCWdVLLpUf/2d4=", "dev": true }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "combined-stream": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", @@ -1097,6 +1570,12 @@ "resolved": "http://registry.npmjs.org/commander/-/commander-2.0.0.tgz", "integrity": "sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg=" }, + "common-tags": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", + "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1123,6 +1602,12 @@ "resolved": "https://registry.npmjs.org/console-log-level/-/console-log-level-1.4.1.tgz", "integrity": "sha512-VZzbIORbP+PPcN/gg3DXClTLPLg5Slwd5fL2MIc+o1qZ4BXBvWyc6QxPk6T/Mkr6IVjRpoAGf32XxP3ZWMVRcQ==" }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, "content-disposition": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", @@ -1152,16 +1637,41 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "core-js": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", + "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==", + "dev": true + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, "d64": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/d64/-/d64-1.0.0.tgz", "integrity": "sha1-QAKofoUMv8n52XBrYPymE6MzbpA=" }, + "damerau-levenshtein": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", + "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", + "dev": true + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -1178,6 +1688,12 @@ "ms": "2.0.0" } }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, "deep-eql": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", @@ -1192,6 +1708,21 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha1-xPp8lUBKF6nD6Mp+FTcxK3NjMKw=" }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, "delay": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/delay/-/delay-4.3.0.tgz", @@ -1227,6 +1758,12 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", "integrity": "sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ=" }, + "dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, "docker-modem": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-1.0.7.tgz", @@ -1284,6 +1821,15 @@ "tar-fs": "~1.16.3" } }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "dot-prop": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.1.1.tgz", @@ -1345,6 +1891,12 @@ "shimmer": "^1.2.0" } }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -1363,6 +1915,45 @@ "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=" }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", @@ -1387,6 +1978,470 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "eslint": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.6.0.tgz", + "integrity": "sha512-PpEBq7b6qY/qrOmpYQ/jTMDYfuQMELR4g4WI1M/NaSDDD/bdcMb+dj4Hgks7p41kW2caXsPsEZAEAyAgjVVC0g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "ajv": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz", + "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", + "dev": true + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true + } + } + }, + "eslint-config-prettier": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.10.0.tgz", + "integrity": "sha512-AtndijGte1rPILInUdHjvKEGbIV06NuvPrqlIEaEaWtbtvJh464mDeyGMdZEQMsGvC0ZVkiex1fSNcC4HAbRGg==", + "dev": true, + "requires": { + "get-stdin": "^6.0.0" + } + }, + "eslint-config-standard": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-14.1.0.tgz", + "integrity": "sha512-EF6XkrrGVbvv8hL/kYa/m6vnvmUT+K82pJJc4JJVMM6+Qgqh0pnwprSxdduDLB9p/7bIxD+YV5O0wfb8lmcPbA==", + "dev": true + }, + "eslint-config-standard-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-8.1.0.tgz", + "integrity": "sha512-ULVC8qH8qCqbU792ZOO6DaiaZyHNS/5CZt3hKqHkEhVlhPEPN3nfBqqxJCyp59XrjIBZPu1chMYe9T2DXZ7TMw==", + "dev": true + }, + "eslint-config-standard-react": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-react/-/eslint-config-standard-react-9.2.0.tgz", + "integrity": "sha512-u+KRP2uCtthZ/W4DlLWCC59GZNV/y9k9yicWWammgTs/Omh8ZUUPF3EnYm81MAcbkYQq2Wg0oxutAhi/FQ8mIw==", + "dev": true, + "requires": { + "eslint-config-standard-jsx": "^8.0.0" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz", + "integrity": "sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.13.1" + }, + "dependencies": { + "resolve": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "eslint-module-utils": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.5.2.tgz", + "integrity": "sha512-LGScZ/JSlqGKiT8OC+cYRxseMjyqt6QO54nl281CK93unD89ijSeRV6An8Ci/2nvWVKe8K/Tqdm75RQoIOCr+Q==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "pkg-dir": "^2.0.0" + } + }, + "eslint-plugin-chai-expect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-chai-expect/-/eslint-plugin-chai-expect-2.1.0.tgz", + "integrity": "sha512-rd0/4mjMV6c3i0o4DKkWI4uaFN9DK707kW+/fDphaDI6HVgxXnhML9Xgt5vHnTXmSSnDhupuCFBgsEAEpchXmQ==", + "dev": true + }, + "eslint-plugin-chai-friendly": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-0.5.0.tgz", + "integrity": "sha512-Pxe6z8C9fP0pn2X2nGFU/b3GBOCM/5FVus1hsMwJsXP3R7RiXFl7g0ksJbsc0GxiLyidTW4mEFk77qsNn7Tk7g==", + "dev": true + }, + "eslint-plugin-es": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.0.tgz", + "integrity": "sha512-6/Jb/J/ZvSebydwbBJO1R9E5ky7YeElfK56Veh7e4QGFHCXoIXGH9HhVz+ibJLM3XJ1XjP+T7rKBLUa/Y7eIng==", + "dev": true, + "requires": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", + "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "regexpp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz", + "integrity": "sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==", + "dev": true + } + } + }, + "eslint-plugin-import": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz", + "integrity": "sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "array.prototype.flat": "^1.2.1", + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.2", + "eslint-module-utils": "^2.4.1", + "has": "^1.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.0", + "read-pkg-up": "^2.0.0", + "resolve": "^1.12.0" + }, + "dependencies": { + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + } + } + }, + "eslint-plugin-jsx-a11y": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz", + "integrity": "sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.4.5", + "aria-query": "^3.0.0", + "array-includes": "^3.0.3", + "ast-types-flow": "^0.0.7", + "axobject-query": "^2.0.2", + "damerau-levenshtein": "^1.0.4", + "emoji-regex": "^7.0.2", + "has": "^1.0.3", + "jsx-ast-utils": "^2.2.1" + }, + "dependencies": { + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + } + } + }, + "eslint-plugin-mocha": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-6.2.2.tgz", + "integrity": "sha512-oNhPzfkT6Q6CJ0HMVJ2KLxEWG97VWGTmuHOoRcDLE0U88ugUyFNV9wrT2XIt5cGtqc5W9k38m4xTN34L09KhBA==", + "dev": true, + "requires": { + "ramda": "^0.26.1" + } + }, + "eslint-plugin-node": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.0.0.tgz", + "integrity": "sha512-chUs/NVID+sknFiJzxoN9lM7uKSOEta8GC8365hw1nDfwIPIjjpRSwwPvQanWv8dt/pDe9EV4anmVSwdiSndNg==", + "dev": true, + "requires": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", + "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "ignore": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-plugin-prettier": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz", + "integrity": "sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-plugin-promise": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz", + "integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==", + "dev": true + }, + "eslint-plugin-react": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.18.3.tgz", + "integrity": "sha512-Bt56LNHAQCoou88s8ViKRjMB2+36XRejCQ1VoLj716KI1MoE99HpTVvIThJ0rvFmG4E4Gsq+UgToEjn+j044Bg==", + "dev": true, + "requires": { + "array-includes": "^3.1.1", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.2.3", + "object.entries": "^1.1.1", + "object.fromentries": "^2.0.2", + "object.values": "^1.1.1", + "prop-types": "^15.7.2", + "resolve": "^1.14.2", + "string.prototype.matchall": "^4.0.2" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "resolve": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "eslint-plugin-standard": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz", + "integrity": "sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ==", + "dev": true + }, + "eslint-scope": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "dev": true + }, + "espree": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz", + "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==", + "dev": true, + "requires": { + "acorn": "^7.1.0", + "acorn-jsx": "^5.1.0", + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "acorn": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", + "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.1.0.tgz", + "integrity": "sha512-MxYW9xKmROWF672KqjO75sszsA8Mxhw06YFeS5VHlB98KDHbOSurm3ArsjO60Eaf3QmGMCP1yn+0JQkNLo/97Q==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -1455,6 +2510,28 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo=" }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -1465,16 +2542,46 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, "fast-text-encoding": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz", "integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ==" }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -1501,11 +2608,48 @@ } } }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, "findit2": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz", "integrity": "sha1-WKRmaX34piBc39vzlVNri9d3pfY=" }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + }, + "dependencies": { + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "flatted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", + "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "dev": true + }, "follow-redirects": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", @@ -1577,6 +2721,18 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -1618,6 +2774,18 @@ "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.4.2.tgz", "integrity": "sha1-kv9xllINZwg5pnMICSoSqt8valk=" }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -1639,6 +2807,21 @@ "path-is-absolute": "^1.0.0" } }, + "glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, "google-auth-library": { "version": "5.5.1", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.5.1.tgz", @@ -1734,12 +2917,36 @@ "har-schema": "^2.0.0" } }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, "has-flag": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", "dev": true }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", @@ -1764,6 +2971,12 @@ "resolved": "https://registry.npmjs.org/hex2dec/-/hex2dec-1.1.2.tgz", "integrity": "sha512-Yu+q/XWr2fFQ11tHxPq4p4EiNkb2y+lAacJNhAdRXVfRIcDH6gi7htWFnnlIzvqHMHoWeIsfXlNAjZInpAOJDA==" }, + "hosted-git-info": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", + "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", + "dev": true + }, "http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", @@ -1836,6 +3049,12 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, "ignore-walk": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", @@ -1844,6 +3063,28 @@ "minimatch": "^3.0.4" } }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, "inflection": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", @@ -1868,6 +3109,97 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=" }, + "inquirer": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.4.tgz", + "integrity": "sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^2.4.2", + "cli-cursor": "^3.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.15", + "mute-stream": "0.0.8", + "run-async": "^2.2.0", + "rxjs": "^6.5.3", + "string-width": "^4.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } + } + } + } + }, + "internal-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz", + "integrity": "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==", + "dev": true, + "requires": { + "es-abstract": "^1.17.0-next.1", + "has": "^1.0.3", + "side-channel": "^1.0.2" + } + }, "ipaddr.js": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", @@ -1878,6 +3210,12 @@ "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==" }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, "is-bluebird": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", @@ -1888,6 +3226,24 @@ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", "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==", + "dev": true + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", @@ -1896,11 +3252,35 @@ "number-is-nan": "^1.0.0" } }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, "is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", @@ -1911,6 +3291,21 @@ "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==" }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -1921,6 +3316,12 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -1947,11 +3348,33 @@ } } }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, "json-bigint": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz", @@ -1970,6 +3393,12 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=" }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -2007,6 +3436,16 @@ "verror": "1.10.0" } }, + "jsx-ast-utils": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz", + "integrity": "sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "object.assign": "^4.1.0" + } + }, "jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -2026,6 +3465,52 @@ "safe-buffer": "^5.0.1" } }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, "lockfile": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz", @@ -2054,11 +3539,29 @@ "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=" }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "lodash.pickby": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz", "integrity": "sha1-feoh2MGNdwOifHBMFdO4SmfjOv8=" }, + "lodash.unescape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", + "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=", + "dev": true + }, "log-driver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", @@ -2105,11 +3608,63 @@ } } }, + "loglevel": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz", + "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==", + "dev": true + }, + "loglevel-colored-level-prefix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz", + "integrity": "sha1-akAhj9x64V/HbD0PPmdsRlOIYD4=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "loglevel": "^1.4.1" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -2132,6 +3687,24 @@ "statsd-parser": "~0.0.4" } }, + "make-plural": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-4.3.0.tgz", + "integrity": "sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true, + "optional": true + } + } + }, "map-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", @@ -2152,6 +3725,29 @@ "resolved": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz", "integrity": "sha1-QB/ex+whzbngPNPTAhOY2iGycIU=" }, + "messageformat": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/messageformat/-/messageformat-2.3.0.tgz", + "integrity": "sha512-uTzvsv0lTeQxYI2y1NPa1lItL5VRI8Gb93Y2K2ue5gBPyrbJxfDi/EYWxh2PKv5yO42AJeeqblS9MJSh/IEk4w==", + "dev": true, + "requires": { + "make-plural": "^4.3.0", + "messageformat-formatters": "^2.0.1", + "messageformat-parser": "^4.1.2" + } + }, + "messageformat-formatters": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/messageformat-formatters/-/messageformat-formatters-2.0.1.tgz", + "integrity": "sha512-E/lQRXhtHwGuiQjI7qxkLp8AHbMD5r2217XNe/SREbBlSawe0lOqsFb7rflZJmlQFSULNLIqlcjjsCPlB3m3Mg==", + "dev": true + }, + "messageformat-parser": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-4.1.2.tgz", + "integrity": "sha512-7dWuifeyldz7vhEuL96Kwq1fhZXBW+TUfbnHN4UCrCxoXQTYjHnR78eI66Gk9LaLLsAvzPNVJBaa66DRfFNaiA==", + "dev": true + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -2206,6 +3802,12 @@ "mime-db": "~1.37.0" } }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -2334,6 +3936,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, "mv": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", @@ -2426,6 +4034,12 @@ "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz", "integrity": "sha1-pgO0pJirdxc2ErnqGs3sTZgPALs=" }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, "ncp": { "version": "2.0.0", "resolved": "http://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", @@ -2447,6 +4061,12 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, "node-fetch": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", @@ -2493,6 +4113,18 @@ "osenv": "^0.1.4" } }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, "npm-bundled": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz", @@ -2533,6 +4165,66 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.entries": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz", + "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.fromentries": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz", + "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -2549,6 +4241,29 @@ "wrappy": "1" } }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", @@ -2576,16 +4291,60 @@ "p-try": "^2.0.0" } }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + }, + "dependencies": { + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + } + } + }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, "parse-duration": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-0.1.1.tgz", "integrity": "sha1-ExFN3JiRwezSgANiRFVN5DZHoiY=" }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, "parse-ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", @@ -2596,11 +4355,29 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", @@ -2611,6 +4388,23 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -2621,6 +4415,646 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "dev": true + }, + "prettier-eslint": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-9.0.1.tgz", + "integrity": "sha512-KZT65QTosSAqBBqmrC+RpXbsMRe7Os2YSR9cAfFbDlyPAopzA/S5bioiZ3rpziNQNSJaOxmtXSx07EQ+o2Dlug==", + "dev": true, + "requires": { + "@typescript-eslint/parser": "^1.10.2", + "common-tags": "^1.4.0", + "core-js": "^3.1.4", + "dlv": "^1.1.0", + "eslint": "^5.0.0", + "indent-string": "^4.0.0", + "lodash.merge": "^4.6.0", + "loglevel-colored-level-prefix": "^1.0.0", + "prettier": "^1.7.0", + "pretty-format": "^23.0.1", + "require-relative": "^0.8.7", + "typescript": "^3.2.1", + "vue-eslint-parser": "^2.0.2" + }, + "dependencies": { + "ajv": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz", + "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "eslint": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", + "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.9.1", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^4.0.3", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.2.2", + "js-yaml": "^3.13.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0" + } + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "espree": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", + "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", + "dev": true, + "requires": { + "acorn": "^6.0.7", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + } + }, + "fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "inquirer": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "prettier-eslint-cli": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/prettier-eslint-cli/-/prettier-eslint-cli-5.0.0.tgz", + "integrity": "sha512-cei9UbN1aTrz3sQs88CWpvY/10PYTevzd76zoG1tdJ164OhmNTFRKPTOZrutVvscoQWzbnLKkviS3gu5JXwvZg==", + "dev": true, + "requires": { + "arrify": "^2.0.1", + "boolify": "^1.0.0", + "camelcase-keys": "^6.0.0", + "chalk": "^2.4.2", + "common-tags": "^1.8.0", + "core-js": "^3.1.4", + "eslint": "^5.0.0", + "find-up": "^4.1.0", + "get-stdin": "^7.0.0", + "glob": "^7.1.4", + "ignore": "^5.1.2", + "lodash.memoize": "^4.1.2", + "loglevel-colored-level-prefix": "^1.0.0", + "messageformat": "^2.2.1", + "prettier-eslint": "^9.0.0", + "rxjs": "^6.5.2", + "yargs": "^13.2.4" + }, + "dependencies": { + "ajv": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz", + "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "eslint": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", + "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.9.1", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^4.0.3", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.2.2", + "js-yaml": "^3.13.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0" + }, + "dependencies": { + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + } + } + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "espree": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", + "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", + "dev": true, + "requires": { + "acorn": "^6.0.7", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + } + }, + "fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "get-stdin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", + "integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "ignore": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "dev": true + }, + "inquirer": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, + "pretty-format": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", + "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0", + "ansi-styles": "^3.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + } + } + }, "pretty-ms": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-4.0.0.tgz", @@ -2634,6 +5068,12 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", "integrity": "sha1-o31zL0JxtKsa0HDTVQjoKQeI/6o=" }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, "prom-client": { "version": "11.5.3", "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-11.5.3.tgz", @@ -2642,6 +5082,17 @@ "tdigest": "^0.1.1" } }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, "protobufjs": { "version": "6.8.8", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", @@ -2737,6 +5188,18 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha1-yzroBuh0BERYTvFUzo7pjUA/PjY=" }, + "quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true + }, + "ramda": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", + "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==", + "dev": true + }, "range-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", @@ -2790,6 +5253,33 @@ } } }, + "react-is": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", + "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==", + "dev": true + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", @@ -2804,6 +5294,28 @@ "util-deprecate": "~1.0.1" } }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", + "dev": true + }, + "regexp.prototype.flags": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, "request": { "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", @@ -2843,6 +5355,12 @@ "resolved": "https://registry.npmjs.org/require-all/-/require-all-1.0.0.tgz", "integrity": "sha1-hINwjnzkxt+tmItQgPl4KbktIic=" }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, "require-in-the-middle": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-4.0.1.tgz", @@ -2874,6 +5392,18 @@ "integrity": "sha1-rW8wwTvs15cBDEaK+ndcDAprR/o=", "dev": true }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "require-relative": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", + "integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=", + "dev": true + }, "resolve": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", @@ -2882,6 +5412,22 @@ "path-parse": "^1.0.6" } }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, "retry-as-promised": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-2.3.2.tgz", @@ -2928,6 +5474,24 @@ "glob": "^7.0.5" } }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "rxjs": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", + "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -3071,11 +5635,36 @@ "coffee-script": "1.6.0" } }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, "shimmer": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.0.tgz", "integrity": "sha1-+Wb3VVeJdj502IQRk2haXnhzZmU=" }, + "side-channel": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz", + "integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==", + "dev": true, + "requires": { + "es-abstract": "^1.17.0-next.1", + "object-inspect": "^1.7.0" + } + }, "sigmund": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", @@ -3095,6 +5684,25 @@ "buster-format": "~0.5" } }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + } + } + }, "smoke-test-sharelatex": { "version": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#bc3e93d18ccee219c0d99e8b02c984ccdd842e1c", "from": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", @@ -3161,6 +5769,38 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, "split": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", @@ -3174,6 +5814,12 @@ "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", "integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY=" }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, "sqlite3": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.0.6.tgz", @@ -3245,6 +5891,40 @@ "strip-ansi": "^3.0.0" } }, + "string.prototype.matchall": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz", + "integrity": "sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "has-symbols": "^1.0.1", + "internal-slot": "^1.0.2", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.2" + } + }, + "string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -3261,6 +5941,12 @@ "ansi-regex": "^2.0.0" } }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -3280,6 +5966,82 @@ "has-flag": "^2.0.0" } }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz", + "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "tar": { "version": "4.4.8", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", @@ -3376,6 +6138,12 @@ "terraformer": "~1.0.5" } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -3395,11 +6163,26 @@ "integrity": "sha1-kNt58X2Ni1NiFUOJSSuXJ2LP0nY=", "dev": true }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, "to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=" }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, "to-no-case": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/to-no-case/-/to-no-case-1.0.2.tgz", @@ -3442,6 +6225,12 @@ } } }, + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -3455,6 +6244,15 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, "type-detect": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", @@ -3480,6 +6278,12 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "typescript": { + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", + "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", + "dev": true + }, "underscore": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", @@ -3513,6 +6317,12 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" }, + "v8-compile-cache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", + "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "dev": true + }, "v8-profiler-node8": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/v8-profiler-node8/-/v8-profiler-node8-6.0.1.tgz", @@ -3549,6 +6359,16 @@ } } }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "validator": { "version": "10.9.0", "resolved": "https://registry.npmjs.org/validator/-/validator-10.9.0.tgz", @@ -3569,11 +6389,100 @@ "extsprintf": "^1.2.0" } }, + "vue-eslint-parser": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-2.0.3.tgz", + "integrity": "sha512-ZezcU71Owm84xVF6gfurBQUGg8WQ+WZGxgDEQu1IHFBZNx7BFZg3L1yHxrCBNNwbwFtE1GuvfJKMtb6Xuwc/Bw==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "eslint-scope": "^3.7.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^3.5.2", + "esquery": "^1.0.0", + "lodash": "^4.17.4" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "eslint-scope": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "walkdir": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==" }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", @@ -3590,6 +6499,63 @@ "@types/node": "*" } }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -3600,16 +6566,138 @@ "resolved": "https://registry.npmjs.org/wrench/-/wrench-1.5.9.tgz", "integrity": "sha1-QRaRxjqbJTGxcAJnJ5veyiOyFCo=" }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + } + } + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, "yallist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", "integrity": "sha1-tLBJ4xS+VF486AIjbWzSLNkcPek=" }, + "yargs": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", + "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/services/clsi/package.json b/services/clsi/package.json index 55395380c5..c38621aa23 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -41,10 +41,28 @@ "wrench": "~1.5.4" }, "devDependencies": { + "babel-eslint": "^10.0.3", "bunyan": "^0.22.1", "chai": "~1.8.1", "coffeescript": "1.6.0", + "eslint": "^6.6.0", + "eslint-config-prettier": "^6.10.0", + "eslint-config-standard": "^14.1.0", + "eslint-config-standard-jsx": "^8.1.0", + "eslint-config-standard-react": "^9.2.0", + "eslint-plugin-chai-expect": "^2.1.0", + "eslint-plugin-chai-friendly": "^0.5.0", + "eslint-plugin-import": "^2.20.1", + "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-mocha": "^6.2.2", + "eslint-plugin-node": "^11.0.0", + "eslint-plugin-prettier": "^3.1.2", + "eslint-plugin-promise": "^4.2.1", + "eslint-plugin-react": "^7.18.3", + "eslint-plugin-standard": "^4.0.1", "mocha": "^4.0.1", + "prettier": "^1.19.1", + "prettier-eslint-cli": "^5.0.0", "sandboxed-module": "~0.3.0", "sinon": "~1.7.3", "timekeeper": "0.0.4" From 2d07bab23db4cf314c79296575ece6a91b9d6f0d Mon Sep 17 00:00:00 2001 From: decaffeinate <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:14:01 +0100 Subject: [PATCH 516/754] decaffeinate: Rename CommandRunner.coffee and 25 other files from .coffee to .js --- .../clsi/app/coffee/{CommandRunner.coffee => CommandRunner.js} | 0 .../app/coffee/{CompileController.coffee => CompileController.js} | 0 .../clsi/app/coffee/{CompileManager.coffee => CompileManager.js} | 0 .../app/coffee/{ContentTypeMapper.coffee => ContentTypeMapper.js} | 0 services/clsi/app/coffee/{DbQueue.coffee => DbQueue.js} | 0 .../app/coffee/{DockerLockManager.coffee => DockerLockManager.js} | 0 services/clsi/app/coffee/{DockerRunner.coffee => DockerRunner.js} | 0 .../app/coffee/{DraftModeManager.coffee => DraftModeManager.js} | 0 services/clsi/app/coffee/{Errors.coffee => Errors.js} | 0 services/clsi/app/coffee/{LatexRunner.coffee => LatexRunner.js} | 0 .../coffee/{LocalCommandRunner.coffee => LocalCommandRunner.js} | 0 services/clsi/app/coffee/{LockManager.coffee => LockManager.js} | 0 services/clsi/app/coffee/{Metrics.coffee => Metrics.js} | 0 .../coffee/{OutputCacheManager.coffee => OutputCacheManager.js} | 0 .../app/coffee/{OutputFileFinder.coffee => OutputFileFinder.js} | 0 .../coffee/{OutputFileOptimiser.coffee => OutputFileOptimiser.js} | 0 ...jectPersistenceManager.coffee => ProjectPersistenceManager.js} | 0 .../clsi/app/coffee/{RequestParser.coffee => RequestParser.js} | 0 .../{ResourceStateManager.coffee => ResourceStateManager.js} | 0 .../clsi/app/coffee/{ResourceWriter.coffee => ResourceWriter.js} | 0 services/clsi/app/coffee/{SafeReader.coffee => SafeReader.js} | 0 ...cServerForbidSymlinks.coffee => StaticServerForbidSymlinks.js} | 0 services/clsi/app/coffee/{TikzManager.coffee => TikzManager.js} | 0 services/clsi/app/coffee/{UrlCache.coffee => UrlCache.js} | 0 services/clsi/app/coffee/{UrlFetcher.coffee => UrlFetcher.js} | 0 services/clsi/app/coffee/{db.coffee => db.js} | 0 26 files changed, 0 insertions(+), 0 deletions(-) rename services/clsi/app/coffee/{CommandRunner.coffee => CommandRunner.js} (100%) rename services/clsi/app/coffee/{CompileController.coffee => CompileController.js} (100%) rename services/clsi/app/coffee/{CompileManager.coffee => CompileManager.js} (100%) rename services/clsi/app/coffee/{ContentTypeMapper.coffee => ContentTypeMapper.js} (100%) rename services/clsi/app/coffee/{DbQueue.coffee => DbQueue.js} (100%) rename services/clsi/app/coffee/{DockerLockManager.coffee => DockerLockManager.js} (100%) rename services/clsi/app/coffee/{DockerRunner.coffee => DockerRunner.js} (100%) rename services/clsi/app/coffee/{DraftModeManager.coffee => DraftModeManager.js} (100%) rename services/clsi/app/coffee/{Errors.coffee => Errors.js} (100%) rename services/clsi/app/coffee/{LatexRunner.coffee => LatexRunner.js} (100%) rename services/clsi/app/coffee/{LocalCommandRunner.coffee => LocalCommandRunner.js} (100%) rename services/clsi/app/coffee/{LockManager.coffee => LockManager.js} (100%) rename services/clsi/app/coffee/{Metrics.coffee => Metrics.js} (100%) rename services/clsi/app/coffee/{OutputCacheManager.coffee => OutputCacheManager.js} (100%) rename services/clsi/app/coffee/{OutputFileFinder.coffee => OutputFileFinder.js} (100%) rename services/clsi/app/coffee/{OutputFileOptimiser.coffee => OutputFileOptimiser.js} (100%) rename services/clsi/app/coffee/{ProjectPersistenceManager.coffee => ProjectPersistenceManager.js} (100%) rename services/clsi/app/coffee/{RequestParser.coffee => RequestParser.js} (100%) rename services/clsi/app/coffee/{ResourceStateManager.coffee => ResourceStateManager.js} (100%) rename services/clsi/app/coffee/{ResourceWriter.coffee => ResourceWriter.js} (100%) rename services/clsi/app/coffee/{SafeReader.coffee => SafeReader.js} (100%) rename services/clsi/app/coffee/{StaticServerForbidSymlinks.coffee => StaticServerForbidSymlinks.js} (100%) rename services/clsi/app/coffee/{TikzManager.coffee => TikzManager.js} (100%) rename services/clsi/app/coffee/{UrlCache.coffee => UrlCache.js} (100%) rename services/clsi/app/coffee/{UrlFetcher.coffee => UrlFetcher.js} (100%) rename services/clsi/app/coffee/{db.coffee => db.js} (100%) diff --git a/services/clsi/app/coffee/CommandRunner.coffee b/services/clsi/app/coffee/CommandRunner.js similarity index 100% rename from services/clsi/app/coffee/CommandRunner.coffee rename to services/clsi/app/coffee/CommandRunner.js diff --git a/services/clsi/app/coffee/CompileController.coffee b/services/clsi/app/coffee/CompileController.js similarity index 100% rename from services/clsi/app/coffee/CompileController.coffee rename to services/clsi/app/coffee/CompileController.js diff --git a/services/clsi/app/coffee/CompileManager.coffee b/services/clsi/app/coffee/CompileManager.js similarity index 100% rename from services/clsi/app/coffee/CompileManager.coffee rename to services/clsi/app/coffee/CompileManager.js diff --git a/services/clsi/app/coffee/ContentTypeMapper.coffee b/services/clsi/app/coffee/ContentTypeMapper.js similarity index 100% rename from services/clsi/app/coffee/ContentTypeMapper.coffee rename to services/clsi/app/coffee/ContentTypeMapper.js diff --git a/services/clsi/app/coffee/DbQueue.coffee b/services/clsi/app/coffee/DbQueue.js similarity index 100% rename from services/clsi/app/coffee/DbQueue.coffee rename to services/clsi/app/coffee/DbQueue.js diff --git a/services/clsi/app/coffee/DockerLockManager.coffee b/services/clsi/app/coffee/DockerLockManager.js similarity index 100% rename from services/clsi/app/coffee/DockerLockManager.coffee rename to services/clsi/app/coffee/DockerLockManager.js diff --git a/services/clsi/app/coffee/DockerRunner.coffee b/services/clsi/app/coffee/DockerRunner.js similarity index 100% rename from services/clsi/app/coffee/DockerRunner.coffee rename to services/clsi/app/coffee/DockerRunner.js diff --git a/services/clsi/app/coffee/DraftModeManager.coffee b/services/clsi/app/coffee/DraftModeManager.js similarity index 100% rename from services/clsi/app/coffee/DraftModeManager.coffee rename to services/clsi/app/coffee/DraftModeManager.js diff --git a/services/clsi/app/coffee/Errors.coffee b/services/clsi/app/coffee/Errors.js similarity index 100% rename from services/clsi/app/coffee/Errors.coffee rename to services/clsi/app/coffee/Errors.js diff --git a/services/clsi/app/coffee/LatexRunner.coffee b/services/clsi/app/coffee/LatexRunner.js similarity index 100% rename from services/clsi/app/coffee/LatexRunner.coffee rename to services/clsi/app/coffee/LatexRunner.js diff --git a/services/clsi/app/coffee/LocalCommandRunner.coffee b/services/clsi/app/coffee/LocalCommandRunner.js similarity index 100% rename from services/clsi/app/coffee/LocalCommandRunner.coffee rename to services/clsi/app/coffee/LocalCommandRunner.js diff --git a/services/clsi/app/coffee/LockManager.coffee b/services/clsi/app/coffee/LockManager.js similarity index 100% rename from services/clsi/app/coffee/LockManager.coffee rename to services/clsi/app/coffee/LockManager.js diff --git a/services/clsi/app/coffee/Metrics.coffee b/services/clsi/app/coffee/Metrics.js similarity index 100% rename from services/clsi/app/coffee/Metrics.coffee rename to services/clsi/app/coffee/Metrics.js diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.js similarity index 100% rename from services/clsi/app/coffee/OutputCacheManager.coffee rename to services/clsi/app/coffee/OutputCacheManager.js diff --git a/services/clsi/app/coffee/OutputFileFinder.coffee b/services/clsi/app/coffee/OutputFileFinder.js similarity index 100% rename from services/clsi/app/coffee/OutputFileFinder.coffee rename to services/clsi/app/coffee/OutputFileFinder.js diff --git a/services/clsi/app/coffee/OutputFileOptimiser.coffee b/services/clsi/app/coffee/OutputFileOptimiser.js similarity index 100% rename from services/clsi/app/coffee/OutputFileOptimiser.coffee rename to services/clsi/app/coffee/OutputFileOptimiser.js diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.coffee b/services/clsi/app/coffee/ProjectPersistenceManager.js similarity index 100% rename from services/clsi/app/coffee/ProjectPersistenceManager.coffee rename to services/clsi/app/coffee/ProjectPersistenceManager.js diff --git a/services/clsi/app/coffee/RequestParser.coffee b/services/clsi/app/coffee/RequestParser.js similarity index 100% rename from services/clsi/app/coffee/RequestParser.coffee rename to services/clsi/app/coffee/RequestParser.js diff --git a/services/clsi/app/coffee/ResourceStateManager.coffee b/services/clsi/app/coffee/ResourceStateManager.js similarity index 100% rename from services/clsi/app/coffee/ResourceStateManager.coffee rename to services/clsi/app/coffee/ResourceStateManager.js diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.js similarity index 100% rename from services/clsi/app/coffee/ResourceWriter.coffee rename to services/clsi/app/coffee/ResourceWriter.js diff --git a/services/clsi/app/coffee/SafeReader.coffee b/services/clsi/app/coffee/SafeReader.js similarity index 100% rename from services/clsi/app/coffee/SafeReader.coffee rename to services/clsi/app/coffee/SafeReader.js diff --git a/services/clsi/app/coffee/StaticServerForbidSymlinks.coffee b/services/clsi/app/coffee/StaticServerForbidSymlinks.js similarity index 100% rename from services/clsi/app/coffee/StaticServerForbidSymlinks.coffee rename to services/clsi/app/coffee/StaticServerForbidSymlinks.js diff --git a/services/clsi/app/coffee/TikzManager.coffee b/services/clsi/app/coffee/TikzManager.js similarity index 100% rename from services/clsi/app/coffee/TikzManager.coffee rename to services/clsi/app/coffee/TikzManager.js diff --git a/services/clsi/app/coffee/UrlCache.coffee b/services/clsi/app/coffee/UrlCache.js similarity index 100% rename from services/clsi/app/coffee/UrlCache.coffee rename to services/clsi/app/coffee/UrlCache.js diff --git a/services/clsi/app/coffee/UrlFetcher.coffee b/services/clsi/app/coffee/UrlFetcher.js similarity index 100% rename from services/clsi/app/coffee/UrlFetcher.coffee rename to services/clsi/app/coffee/UrlFetcher.js diff --git a/services/clsi/app/coffee/db.coffee b/services/clsi/app/coffee/db.js similarity index 100% rename from services/clsi/app/coffee/db.coffee rename to services/clsi/app/coffee/db.js From 4eab36e3d5ca39506ee083905fbe717919d68e97 Mon Sep 17 00:00:00 2001 From: decaffeinate <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:14:14 +0100 Subject: [PATCH 517/754] decaffeinate: Convert CommandRunner.coffee and 25 other files to JS --- services/clsi/app/coffee/CommandRunner.js | 25 +- services/clsi/app/coffee/CompileController.js | 252 +++--- services/clsi/app/coffee/CompileManager.js | 737 +++++++++-------- services/clsi/app/coffee/ContentTypeMapper.js | 50 +- services/clsi/app/coffee/DbQueue.js | 21 +- services/clsi/app/coffee/DockerLockManager.js | 126 +-- services/clsi/app/coffee/DockerRunner.js | 761 ++++++++++-------- services/clsi/app/coffee/DraftModeManager.js | 51 +- services/clsi/app/coffee/Errors.js | 49 +- services/clsi/app/coffee/LatexRunner.js | 172 ++-- .../clsi/app/coffee/LocalCommandRunner.js | 96 ++- services/clsi/app/coffee/LockManager.js | 79 +- services/clsi/app/coffee/Metrics.js | 2 +- .../clsi/app/coffee/OutputCacheManager.js | 429 ++++++---- services/clsi/app/coffee/OutputFileFinder.js | 116 ++- .../clsi/app/coffee/OutputFileOptimiser.js | 122 +-- .../app/coffee/ProjectPersistenceManager.js | 171 ++-- services/clsi/app/coffee/RequestParser.js | 296 ++++--- .../clsi/app/coffee/ResourceStateManager.js | 168 ++-- services/clsi/app/coffee/ResourceWriter.js | 322 +++++--- services/clsi/app/coffee/SafeReader.js | 55 +- .../app/coffee/StaticServerForbidSymlinks.js | 103 ++- services/clsi/app/coffee/TikzManager.js | 85 +- services/clsi/app/coffee/UrlCache.js | 281 ++++--- services/clsi/app/coffee/UrlFetcher.js | 136 ++-- services/clsi/app/coffee/db.js | 60 +- 26 files changed, 2801 insertions(+), 1964 deletions(-) diff --git a/services/clsi/app/coffee/CommandRunner.js b/services/clsi/app/coffee/CommandRunner.js index 2d1c3a94c9..dd7210a300 100644 --- a/services/clsi/app/coffee/CommandRunner.js +++ b/services/clsi/app/coffee/CommandRunner.js @@ -1,11 +1,18 @@ -Settings = require "settings-sharelatex" -logger = require "logger-sharelatex" +/* + * decaffeinate suggestions: + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let commandRunnerPath; +const Settings = require("settings-sharelatex"); +const logger = require("logger-sharelatex"); -if Settings.clsi?.dockerRunner == true - commandRunnerPath = "./DockerRunner" -else - commandRunnerPath = "./LocalCommandRunner" -logger.info commandRunnerPath:commandRunnerPath, "selecting command runner for clsi" -CommandRunner = require(commandRunnerPath) +if ((Settings.clsi != null ? Settings.clsi.dockerRunner : undefined) === true) { + commandRunnerPath = "./DockerRunner"; +} else { + commandRunnerPath = "./LocalCommandRunner"; +} +logger.info({commandRunnerPath}, "selecting command runner for clsi"); +const CommandRunner = require(commandRunnerPath); -module.exports = CommandRunner +module.exports = CommandRunner; diff --git a/services/clsi/app/coffee/CompileController.js b/services/clsi/app/coffee/CompileController.js index 4952d84568..cfdbcfe8d6 100644 --- a/services/clsi/app/coffee/CompileController.js +++ b/services/clsi/app/coffee/CompileController.js @@ -1,119 +1,163 @@ -RequestParser = require "./RequestParser" -CompileManager = require "./CompileManager" -Settings = require "settings-sharelatex" -Metrics = require "./Metrics" -ProjectPersistenceManager = require "./ProjectPersistenceManager" -logger = require "logger-sharelatex" -Errors = require "./Errors" +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let CompileController; +const RequestParser = require("./RequestParser"); +const CompileManager = require("./CompileManager"); +const Settings = require("settings-sharelatex"); +const Metrics = require("./Metrics"); +const ProjectPersistenceManager = require("./ProjectPersistenceManager"); +const logger = require("logger-sharelatex"); +const Errors = require("./Errors"); -module.exports = CompileController = - compile: (req, res, next = (error) ->) -> - timer = new Metrics.Timer("compile-request") - RequestParser.parse req.body, (error, request) -> - return next(error) if error? - request.project_id = req.params.project_id - request.user_id = req.params.user_id if req.params.user_id? - ProjectPersistenceManager.markProjectAsJustAccessed request.project_id, (error) -> - return next(error) if error? - CompileManager.doCompileWithLock request, (error, outputFiles = []) -> - if error instanceof Errors.AlreadyCompilingError - code = 423 # Http 423 Locked - status = "compile-in-progress" - else if error instanceof Errors.FilesOutOfSyncError - code = 409 # Http 409 Conflict - status = "retry" - else if error?.terminated - status = "terminated" - else if error?.validate - status = "validation-#{error.validate}" - else if error?.timedout - status = "timedout" - logger.log err: error, project_id: request.project_id, "timeout running compile" - else if error? - status = "error" - code = 500 - logger.warn err: error, project_id: request.project_id, "error running compile" - else - status = "failure" - for file in outputFiles - if file.path?.match(/output\.pdf$/) - status = "success" +module.exports = (CompileController = { + compile(req, res, next) { + if (next == null) { next = function(error) {}; } + const timer = new Metrics.Timer("compile-request"); + return RequestParser.parse(req.body, function(error, request) { + if (error != null) { return next(error); } + request.project_id = req.params.project_id; + if (req.params.user_id != null) { request.user_id = req.params.user_id; } + return ProjectPersistenceManager.markProjectAsJustAccessed(request.project_id, function(error) { + if (error != null) { return next(error); } + return CompileManager.doCompileWithLock(request, function(error, outputFiles) { + let code, status; + if (outputFiles == null) { outputFiles = []; } + if (error instanceof Errors.AlreadyCompilingError) { + code = 423; // Http 423 Locked + status = "compile-in-progress"; + } else if (error instanceof Errors.FilesOutOfSyncError) { + code = 409; // Http 409 Conflict + status = "retry"; + } else if (error != null ? error.terminated : undefined) { + status = "terminated"; + } else if (error != null ? error.validate : undefined) { + status = `validation-${error.validate}`; + } else if (error != null ? error.timedout : undefined) { + status = "timedout"; + logger.log({err: error, project_id: request.project_id}, "timeout running compile"); + } else if (error != null) { + status = "error"; + code = 500; + logger.warn({err: error, project_id: request.project_id}, "error running compile"); + } else { + let file; + status = "failure"; + for (file of Array.from(outputFiles)) { + if (file.path != null ? file.path.match(/output\.pdf$/) : undefined) { + status = "success"; + } + } - if status == "failure" - logger.warn project_id: request.project_id, outputFiles:outputFiles, "project failed to compile successfully, no output.pdf generated" + if (status === "failure") { + logger.warn({project_id: request.project_id, outputFiles}, "project failed to compile successfully, no output.pdf generated"); + } - # log an error if any core files are found - for file in outputFiles - if file.path is "core" - logger.error project_id:request.project_id, req:req, outputFiles:outputFiles, "core file found in output" - - if error? - outputFiles = error.outputFiles || [] - - timer.done() - res.status(code or 200).send { - compile: - status: status - error: error?.message or error - outputFiles: outputFiles.map (file) -> - url: - "#{Settings.apis.clsi.url}/project/#{request.project_id}" + - (if request.user_id? then "/user/#{request.user_id}" else "") + - (if file.build? then "/build/#{file.build}" else "") + - "/output/#{file.path}" - path: file.path - type: file.type - build: file.build + // log an error if any core files are found + for (file of Array.from(outputFiles)) { + if (file.path === "core") { + logger.error({project_id:request.project_id, req, outputFiles}, "core file found in output"); + } + } } - stopCompile: (req, res, next) -> - {project_id, user_id} = req.params - CompileManager.stopCompile project_id, user_id, (error) -> - return next(error) if error? - res.sendStatus(204) + if (error != null) { + outputFiles = error.outputFiles || []; + } - clearCache: (req, res, next = (error) ->) -> - ProjectPersistenceManager.clearProject req.params.project_id, req.params.user_id, (error) -> - return next(error) if error? - res.sendStatus(204) # No content + timer.done(); + return res.status(code || 200).send({ + compile: { + status, + error: (error != null ? error.message : undefined) || error, + outputFiles: outputFiles.map(file => + ({ + url: + `${Settings.apis.clsi.url}/project/${request.project_id}` + + ((request.user_id != null) ? `/user/${request.user_id}` : "") + + ((file.build != null) ? `/build/${file.build}` : "") + + `/output/${file.path}`, + path: file.path, + type: file.type, + build: file.build + }) + ) + } + }); + }); + }); + }); + }, - syncFromCode: (req, res, next = (error) ->) -> - file = req.query.file - line = parseInt(req.query.line, 10) - column = parseInt(req.query.column, 10) - project_id = req.params.project_id - user_id = req.params.user_id - CompileManager.syncFromCode project_id, user_id, file, line, column, (error, pdfPositions) -> - return next(error) if error? - res.json { + stopCompile(req, res, next) { + const {project_id, user_id} = req.params; + return CompileManager.stopCompile(project_id, user_id, function(error) { + if (error != null) { return next(error); } + return res.sendStatus(204); + }); + }, + + clearCache(req, res, next) { + if (next == null) { next = function(error) {}; } + return ProjectPersistenceManager.clearProject(req.params.project_id, req.params.user_id, function(error) { + if (error != null) { return next(error); } + return res.sendStatus(204); + }); + }, // No content + + syncFromCode(req, res, next) { + if (next == null) { next = function(error) {}; } + const { file } = req.query; + const line = parseInt(req.query.line, 10); + const column = parseInt(req.query.column, 10); + const { project_id } = req.params; + const { user_id } = req.params; + return CompileManager.syncFromCode(project_id, user_id, file, line, column, function(error, pdfPositions) { + if (error != null) { return next(error); } + return res.json({ pdf: pdfPositions - } + }); + }); + }, - syncFromPdf: (req, res, next = (error) ->) -> - page = parseInt(req.query.page, 10) - h = parseFloat(req.query.h) - v = parseFloat(req.query.v) - project_id = req.params.project_id - user_id = req.params.user_id - CompileManager.syncFromPdf project_id, user_id, page, h, v, (error, codePositions) -> - return next(error) if error? - res.json { + syncFromPdf(req, res, next) { + if (next == null) { next = function(error) {}; } + const page = parseInt(req.query.page, 10); + const h = parseFloat(req.query.h); + const v = parseFloat(req.query.v); + const { project_id } = req.params; + const { user_id } = req.params; + return CompileManager.syncFromPdf(project_id, user_id, page, h, v, function(error, codePositions) { + if (error != null) { return next(error); } + return res.json({ code: codePositions - } + }); + }); + }, - wordcount: (req, res, next = (error) ->) -> - file = req.query.file || "main.tex" - project_id = req.params.project_id - user_id = req.params.user_id - image = req.query.image - logger.log {image, file, project_id}, "word count request" + wordcount(req, res, next) { + if (next == null) { next = function(error) {}; } + const file = req.query.file || "main.tex"; + const { project_id } = req.params; + const { user_id } = req.params; + const { image } = req.query; + logger.log({image, file, project_id}, "word count request"); - CompileManager.wordcount project_id, user_id, file, image, (error, result) -> - return next(error) if error? - res.json { + return CompileManager.wordcount(project_id, user_id, file, image, function(error, result) { + if (error != null) { return next(error); } + return res.json({ texcount: result - } + }); + }); + }, - status: (req, res, next = (error)-> )-> - res.send("OK") + status(req, res, next ){ + if (next == null) { next = function(error){}; } + return res.send("OK"); + } +}); diff --git a/services/clsi/app/coffee/CompileManager.js b/services/clsi/app/coffee/CompileManager.js index 792beb8d16..82dafd12a7 100644 --- a/services/clsi/app/coffee/CompileManager.js +++ b/services/clsi/app/coffee/CompileManager.js @@ -1,345 +1,454 @@ -ResourceWriter = require "./ResourceWriter" -LatexRunner = require "./LatexRunner" -OutputFileFinder = require "./OutputFileFinder" -OutputCacheManager = require "./OutputCacheManager" -Settings = require("settings-sharelatex") -Path = require "path" -logger = require "logger-sharelatex" -Metrics = require "./Metrics" -child_process = require "child_process" -DraftModeManager = require "./DraftModeManager" -TikzManager = require "./TikzManager" -LockManager = require "./LockManager" -fs = require("fs") -fse = require "fs-extra" -os = require("os") -async = require "async" -Errors = require './Errors' -CommandRunner = require "./CommandRunner" +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__ + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let CompileManager; +const ResourceWriter = require("./ResourceWriter"); +const LatexRunner = require("./LatexRunner"); +const OutputFileFinder = require("./OutputFileFinder"); +const OutputCacheManager = require("./OutputCacheManager"); +const Settings = require("settings-sharelatex"); +const Path = require("path"); +const logger = require("logger-sharelatex"); +const Metrics = require("./Metrics"); +const child_process = require("child_process"); +const DraftModeManager = require("./DraftModeManager"); +const TikzManager = require("./TikzManager"); +const LockManager = require("./LockManager"); +const fs = require("fs"); +const fse = require("fs-extra"); +const os = require("os"); +const async = require("async"); +const Errors = require('./Errors'); +const CommandRunner = require("./CommandRunner"); -getCompileName = (project_id, user_id) -> - if user_id? then "#{project_id}-#{user_id}" else project_id +const getCompileName = function(project_id, user_id) { + if (user_id != null) { return `${project_id}-${user_id}`; } else { return project_id; } +}; -getCompileDir = (project_id, user_id) -> - Path.join(Settings.path.compilesDir, getCompileName(project_id, user_id)) +const getCompileDir = (project_id, user_id) => Path.join(Settings.path.compilesDir, getCompileName(project_id, user_id)); -module.exports = CompileManager = +module.exports = (CompileManager = { - doCompileWithLock: (request, callback = (error, outputFiles) ->) -> - compileDir = getCompileDir(request.project_id, request.user_id) - lockFile = Path.join(compileDir, ".project-lock") - # use a .project-lock file in the compile directory to prevent - # simultaneous compiles - fse.ensureDir compileDir, (error) -> - return callback(error) if error? - LockManager.runWithLock lockFile, (releaseLock) -> - CompileManager.doCompile(request, releaseLock) - , callback + doCompileWithLock(request, callback) { + if (callback == null) { callback = function(error, outputFiles) {}; } + const compileDir = getCompileDir(request.project_id, request.user_id); + const lockFile = Path.join(compileDir, ".project-lock"); + // use a .project-lock file in the compile directory to prevent + // simultaneous compiles + return fse.ensureDir(compileDir, function(error) { + if (error != null) { return callback(error); } + return LockManager.runWithLock(lockFile, releaseLock => CompileManager.doCompile(request, releaseLock) + , callback); + }); + }, - doCompile: (request, callback = (error, outputFiles) ->) -> - compileDir = getCompileDir(request.project_id, request.user_id) - timer = new Metrics.Timer("write-to-disk") - logger.log project_id: request.project_id, user_id: request.user_id, "syncing resources to disk" - ResourceWriter.syncResourcesToDisk request, compileDir, (error, resourceList) -> - # NOTE: resourceList is insecure, it should only be used to exclude files from the output list - if error? and error instanceof Errors.FilesOutOfSyncError - logger.warn project_id: request.project_id, user_id: request.user_id, "files out of sync, please retry" - return callback(error) - else if error? - logger.err err:error, project_id: request.project_id, user_id: request.user_id, "error writing resources to disk" - return callback(error) - logger.log project_id: request.project_id, user_id: request.user_id, time_taken: Date.now() - timer.start, "written files to disk" - timer.done() + doCompile(request, callback) { + if (callback == null) { callback = function(error, outputFiles) {}; } + const compileDir = getCompileDir(request.project_id, request.user_id); + let timer = new Metrics.Timer("write-to-disk"); + logger.log({project_id: request.project_id, user_id: request.user_id}, "syncing resources to disk"); + return ResourceWriter.syncResourcesToDisk(request, compileDir, function(error, resourceList) { + // NOTE: resourceList is insecure, it should only be used to exclude files from the output list + if ((error != null) && error instanceof Errors.FilesOutOfSyncError) { + logger.warn({project_id: request.project_id, user_id: request.user_id}, "files out of sync, please retry"); + return callback(error); + } else if (error != null) { + logger.err({err:error, project_id: request.project_id, user_id: request.user_id}, "error writing resources to disk"); + return callback(error); + } + logger.log({project_id: request.project_id, user_id: request.user_id, time_taken: Date.now() - timer.start}, "written files to disk"); + timer.done(); - injectDraftModeIfRequired = (callback) -> - if request.draft - DraftModeManager.injectDraftMode Path.join(compileDir, request.rootResourcePath), callback - else - callback() + const injectDraftModeIfRequired = function(callback) { + if (request.draft) { + return DraftModeManager.injectDraftMode(Path.join(compileDir, request.rootResourcePath), callback); + } else { + return callback(); + } + }; - createTikzFileIfRequired = (callback) -> - TikzManager.checkMainFile compileDir, request.rootResourcePath, resourceList, (error, needsMainFile) -> - return callback(error) if error? - if needsMainFile - TikzManager.injectOutputFile compileDir, request.rootResourcePath, callback - else - callback() + const createTikzFileIfRequired = callback => + TikzManager.checkMainFile(compileDir, request.rootResourcePath, resourceList, function(error, needsMainFile) { + if (error != null) { return callback(error); } + if (needsMainFile) { + return TikzManager.injectOutputFile(compileDir, request.rootResourcePath, callback); + } else { + return callback(); + } + }) + ; - # set up environment variables for chktex - env = {} - # only run chktex on LaTeX files (not knitr .Rtex files or any others) - isLaTeXFile = request.rootResourcePath?.match(/\.tex$/i) - if request.check? and isLaTeXFile - env['CHKTEX_OPTIONS'] = '-nall -e9 -e10 -w15 -w16' - env['CHKTEX_ULIMIT_OPTIONS'] = '-t 5 -v 64000' - if request.check is 'error' - env['CHKTEX_EXIT_ON_ERROR'] = 1 - if request.check is 'validate' - env['CHKTEX_VALIDATE'] = 1 + // set up environment variables for chktex + const env = {}; + // only run chktex on LaTeX files (not knitr .Rtex files or any others) + const isLaTeXFile = request.rootResourcePath != null ? request.rootResourcePath.match(/\.tex$/i) : undefined; + if ((request.check != null) && isLaTeXFile) { + env['CHKTEX_OPTIONS'] = '-nall -e9 -e10 -w15 -w16'; + env['CHKTEX_ULIMIT_OPTIONS'] = '-t 5 -v 64000'; + if (request.check === 'error') { + env['CHKTEX_EXIT_ON_ERROR'] = 1; + } + if (request.check === 'validate') { + env['CHKTEX_VALIDATE'] = 1; + } + } - # apply a series of file modifications/creations for draft mode and tikz - async.series [injectDraftModeIfRequired, createTikzFileIfRequired], (error) -> - return callback(error) if error? - timer = new Metrics.Timer("run-compile") - # find the image tag to log it as a metric, e.g. 2015.1 (convert . to - for graphite) - tag = request.imageName?.match(/:(.*)/)?[1]?.replace(/\./g,'-') or "default" - tag = "other" if not request.project_id.match(/^[0-9a-f]{24}$/) # exclude smoke test - Metrics.inc("compiles") - Metrics.inc("compiles-with-image.#{tag}") - compileName = getCompileName(request.project_id, request.user_id) - LatexRunner.runLatex compileName, { - directory: compileDir - mainFile: request.rootResourcePath - compiler: request.compiler - timeout: request.timeout - image: request.imageName - flags: request.flags + // apply a series of file modifications/creations for draft mode and tikz + return async.series([injectDraftModeIfRequired, createTikzFileIfRequired], function(error) { + if (error != null) { return callback(error); } + timer = new Metrics.Timer("run-compile"); + // find the image tag to log it as a metric, e.g. 2015.1 (convert . to - for graphite) + let tag = __guard__(__guard__(request.imageName != null ? request.imageName.match(/:(.*)/) : undefined, x1 => x1[1]), x => x.replace(/\./g,'-')) || "default"; + if (!request.project_id.match(/^[0-9a-f]{24}$/)) { tag = "other"; } // exclude smoke test + Metrics.inc("compiles"); + Metrics.inc(`compiles-with-image.${tag}`); + const compileName = getCompileName(request.project_id, request.user_id); + return LatexRunner.runLatex(compileName, { + directory: compileDir, + mainFile: request.rootResourcePath, + compiler: request.compiler, + timeout: request.timeout, + image: request.imageName, + flags: request.flags, environment: env - }, (error, output, stats, timings) -> - # request was for validation only - if request.check is "validate" - result = if error?.code then "fail" else "pass" - error = new Error("validation") - error.validate = result - # request was for compile, and failed on validation - if request.check is "error" and error?.message is 'exited' - error = new Error("compilation") - error.validate = "fail" - # compile was killed by user, was a validation, or a compile which failed validation - if error?.terminated or error?.validate or error?.timedout - OutputFileFinder.findOutputFiles resourceList, compileDir, (err, outputFiles) -> - return callback(err) if err? - error.outputFiles = outputFiles # return output files so user can check logs - callback(error) - return - # compile completed normally - return callback(error) if error? - Metrics.inc("compiles-succeeded") - for metric_key, metric_value of stats or {} - Metrics.count(metric_key, metric_value) - for metric_key, metric_value of timings or {} - Metrics.timing(metric_key, metric_value) - loadavg = os.loadavg?() - Metrics.gauge("load-avg", loadavg[0]) if loadavg? - ts = timer.done() - logger.log {project_id: request.project_id, user_id: request.user_id, time_taken: ts, stats:stats, timings:timings, loadavg:loadavg}, "done compile" - if stats?["latex-runs"] > 0 - Metrics.timing("run-compile-per-pass", ts / stats["latex-runs"]) - if stats?["latex-runs"] > 0 and timings?["cpu-time"] > 0 - Metrics.timing("run-compile-cpu-time-per-pass", timings["cpu-time"] / stats["latex-runs"]) + }, function(error, output, stats, timings) { + // request was for validation only + let metric_key, metric_value; + if (request.check === "validate") { + const result = (error != null ? error.code : undefined) ? "fail" : "pass"; + error = new Error("validation"); + error.validate = result; + } + // request was for compile, and failed on validation + if ((request.check === "error") && ((error != null ? error.message : undefined) === 'exited')) { + error = new Error("compilation"); + error.validate = "fail"; + } + // compile was killed by user, was a validation, or a compile which failed validation + if ((error != null ? error.terminated : undefined) || (error != null ? error.validate : undefined) || (error != null ? error.timedout : undefined)) { + OutputFileFinder.findOutputFiles(resourceList, compileDir, function(err, outputFiles) { + if (err != null) { return callback(err); } + error.outputFiles = outputFiles; // return output files so user can check logs + return callback(error); + }); + return; + } + // compile completed normally + if (error != null) { return callback(error); } + Metrics.inc("compiles-succeeded"); + const object = stats || {}; + for (metric_key in object) { + metric_value = object[metric_key]; + Metrics.count(metric_key, metric_value); + } + const object1 = timings || {}; + for (metric_key in object1) { + metric_value = object1[metric_key]; + Metrics.timing(metric_key, metric_value); + } + const loadavg = typeof os.loadavg === 'function' ? os.loadavg() : undefined; + if (loadavg != null) { Metrics.gauge("load-avg", loadavg[0]); } + const ts = timer.done(); + logger.log({project_id: request.project_id, user_id: request.user_id, time_taken: ts, stats, timings, loadavg}, "done compile"); + if ((stats != null ? stats["latex-runs"] : undefined) > 0) { + Metrics.timing("run-compile-per-pass", ts / stats["latex-runs"]); + } + if (((stats != null ? stats["latex-runs"] : undefined) > 0) && ((timings != null ? timings["cpu-time"] : undefined) > 0)) { + Metrics.timing("run-compile-cpu-time-per-pass", timings["cpu-time"] / stats["latex-runs"]); + } - OutputFileFinder.findOutputFiles resourceList, compileDir, (error, outputFiles) -> - return callback(error) if error? - OutputCacheManager.saveOutputFiles outputFiles, compileDir, (error, newOutputFiles) -> - callback null, newOutputFiles + return OutputFileFinder.findOutputFiles(resourceList, compileDir, function(error, outputFiles) { + if (error != null) { return callback(error); } + return OutputCacheManager.saveOutputFiles(outputFiles, compileDir, (error, newOutputFiles) => callback(null, newOutputFiles)); + }); + }); + }); + }); + }, - stopCompile: (project_id, user_id, callback = (error) ->) -> - compileName = getCompileName(project_id, user_id) - LatexRunner.killLatex compileName, callback + stopCompile(project_id, user_id, callback) { + if (callback == null) { callback = function(error) {}; } + const compileName = getCompileName(project_id, user_id); + return LatexRunner.killLatex(compileName, callback); + }, - clearProject: (project_id, user_id, _callback = (error) ->) -> - callback = (error) -> - _callback(error) - _callback = () -> + clearProject(project_id, user_id, _callback) { + if (_callback == null) { _callback = function(error) {}; } + const callback = function(error) { + _callback(error); + return _callback = function() {}; + }; - compileDir = getCompileDir(project_id, user_id) + const compileDir = getCompileDir(project_id, user_id); - CompileManager._checkDirectory compileDir, (err, exists) -> - return callback(err) if err? - return callback() if not exists # skip removal if no directory present + return CompileManager._checkDirectory(compileDir, function(err, exists) { + if (err != null) { return callback(err); } + if (!exists) { return callback(); } // skip removal if no directory present - proc = child_process.spawn "rm", ["-r", compileDir] + const proc = child_process.spawn("rm", ["-r", compileDir]); - proc.on "error", callback + proc.on("error", callback); - stderr = "" - proc.stderr.on "data", (chunk) -> stderr += chunk.toString() + let stderr = ""; + proc.stderr.on("data", chunk => stderr += chunk.toString()); - proc.on "close", (code) -> - if code == 0 - return callback(null) - else - return callback(new Error("rm -r #{compileDir} failed: #{stderr}")) + return proc.on("close", function(code) { + if (code === 0) { + return callback(null); + } else { + return callback(new Error(`rm -r ${compileDir} failed: ${stderr}`)); + } + }); + }); + }, - _findAllDirs: (callback = (error, allDirs) ->) -> - root = Settings.path.compilesDir - fs.readdir root, (err, files) -> - return callback(err) if err? - allDirs = (Path.join(root, file) for file in files) - callback(null, allDirs) + _findAllDirs(callback) { + if (callback == null) { callback = function(error, allDirs) {}; } + const root = Settings.path.compilesDir; + return fs.readdir(root, function(err, files) { + if (err != null) { return callback(err); } + const allDirs = (Array.from(files).map((file) => Path.join(root, file))); + return callback(null, allDirs); + }); + }, - clearExpiredProjects: (max_cache_age_ms, callback = (error) ->) -> - now = Date.now() - # action for each directory - expireIfNeeded = (checkDir, cb) -> - fs.stat checkDir, (err, stats) -> - return cb() if err? # ignore errors checking directory - age = now - stats.mtime - hasExpired = (age > max_cache_age_ms) - if hasExpired then fse.remove(checkDir, cb) else cb() - # iterate over all project directories - CompileManager._findAllDirs (error, allDirs) -> - return callback() if error? - async.eachSeries allDirs, expireIfNeeded, callback + clearExpiredProjects(max_cache_age_ms, callback) { + if (callback == null) { callback = function(error) {}; } + const now = Date.now(); + // action for each directory + const expireIfNeeded = (checkDir, cb) => + fs.stat(checkDir, function(err, stats) { + if (err != null) { return cb(); } // ignore errors checking directory + const age = now - stats.mtime; + const hasExpired = (age > max_cache_age_ms); + if (hasExpired) { return fse.remove(checkDir, cb); } else { return cb(); } + }) + ; + // iterate over all project directories + return CompileManager._findAllDirs(function(error, allDirs) { + if (error != null) { return callback(); } + return async.eachSeries(allDirs, expireIfNeeded, callback); + }); + }, - _checkDirectory: (compileDir, callback = (error, exists) ->) -> - fs.lstat compileDir, (err, stats) -> - if err?.code is 'ENOENT' - return callback(null, false) # directory does not exist - else if err? - logger.err {dir: compileDir, err:err}, "error on stat of project directory for removal" - return callback(err) - else if not stats?.isDirectory() - logger.err {dir: compileDir, stats:stats}, "bad project directory for removal" - return callback new Error("project directory is not directory") - else - callback(null, true) # directory exists + _checkDirectory(compileDir, callback) { + if (callback == null) { callback = function(error, exists) {}; } + return fs.lstat(compileDir, function(err, stats) { + if ((err != null ? err.code : undefined) === 'ENOENT') { + return callback(null, false); // directory does not exist + } else if (err != null) { + logger.err({dir: compileDir, err}, "error on stat of project directory for removal"); + return callback(err); + } else if (!(stats != null ? stats.isDirectory() : undefined)) { + logger.err({dir: compileDir, stats}, "bad project directory for removal"); + return callback(new Error("project directory is not directory")); + } else { + return callback(null, true); + } + }); + }, // directory exists - syncFromCode: (project_id, user_id, file_name, line, column, callback = (error, pdfPositions) ->) -> - # If LaTeX was run in a virtual environment, the file path that synctex expects - # might not match the file path on the host. The .synctex.gz file however, will be accessed - # wherever it is on the host. - compileName = getCompileName(project_id, user_id) - base_dir = Settings.path.synctexBaseDir(compileName) - file_path = base_dir + "/" + file_name - compileDir = getCompileDir(project_id, user_id) - synctex_path = "#{base_dir}/output.pdf" - command = ["code", synctex_path, file_path, line, column] - fse.ensureDir compileDir, (error) -> - if error? - logger.err {error, project_id, user_id, file_name}, "error ensuring dir for sync from code" - return callback(error) - CompileManager._runSynctex project_id, user_id, command, (error, stdout) -> - return callback(error) if error? - logger.log project_id: project_id, user_id:user_id, file_name: file_name, line: line, column: column, command:command, stdout: stdout, "synctex code output" - callback null, CompileManager._parseSynctexFromCodeOutput(stdout) + syncFromCode(project_id, user_id, file_name, line, column, callback) { + // If LaTeX was run in a virtual environment, the file path that synctex expects + // might not match the file path on the host. The .synctex.gz file however, will be accessed + // wherever it is on the host. + if (callback == null) { callback = function(error, pdfPositions) {}; } + const compileName = getCompileName(project_id, user_id); + const base_dir = Settings.path.synctexBaseDir(compileName); + const file_path = base_dir + "/" + file_name; + const compileDir = getCompileDir(project_id, user_id); + const synctex_path = `${base_dir}/output.pdf`; + const command = ["code", synctex_path, file_path, line, column]; + return fse.ensureDir(compileDir, function(error) { + if (error != null) { + logger.err({error, project_id, user_id, file_name}, "error ensuring dir for sync from code"); + return callback(error); + } + return CompileManager._runSynctex(project_id, user_id, command, function(error, stdout) { + if (error != null) { return callback(error); } + logger.log({project_id, user_id, file_name, line, column, command, stdout}, "synctex code output"); + return callback(null, CompileManager._parseSynctexFromCodeOutput(stdout)); + }); + }); + }, - syncFromPdf: (project_id, user_id, page, h, v, callback = (error, filePositions) ->) -> - compileName = getCompileName(project_id, user_id) - compileDir = getCompileDir(project_id, user_id) - base_dir = Settings.path.synctexBaseDir(compileName) - synctex_path = "#{base_dir}/output.pdf" - command = ["pdf", synctex_path, page, h, v] - fse.ensureDir compileDir, (error) -> - if error? - logger.err {error, project_id, user_id, file_name}, "error ensuring dir for sync to code" - return callback(error) - CompileManager._runSynctex project_id, user_id, command, (error, stdout) -> - return callback(error) if error? - logger.log project_id: project_id, user_id:user_id, page: page, h: h, v:v, stdout: stdout, "synctex pdf output" - callback null, CompileManager._parseSynctexFromPdfOutput(stdout, base_dir) + syncFromPdf(project_id, user_id, page, h, v, callback) { + if (callback == null) { callback = function(error, filePositions) {}; } + const compileName = getCompileName(project_id, user_id); + const compileDir = getCompileDir(project_id, user_id); + const base_dir = Settings.path.synctexBaseDir(compileName); + const synctex_path = `${base_dir}/output.pdf`; + const command = ["pdf", synctex_path, page, h, v]; + return fse.ensureDir(compileDir, function(error) { + if (error != null) { + logger.err({error, project_id, user_id, file_name}, "error ensuring dir for sync to code"); + return callback(error); + } + return CompileManager._runSynctex(project_id, user_id, command, function(error, stdout) { + if (error != null) { return callback(error); } + logger.log({project_id, user_id, page, h, v, stdout}, "synctex pdf output"); + return callback(null, CompileManager._parseSynctexFromPdfOutput(stdout, base_dir)); + }); + }); + }, - _checkFileExists: (path, callback = (error) ->) -> - synctexDir = Path.dirname(path) - synctexFile = Path.join(synctexDir, "output.synctex.gz") - fs.stat synctexDir, (error, stats) -> - if error?.code is 'ENOENT' - return callback(new Errors.NotFoundError("called synctex with no output directory")) - return callback(error) if error? - fs.stat synctexFile, (error, stats) -> - if error?.code is 'ENOENT' - return callback(new Errors.NotFoundError("called synctex with no output file")) - return callback(error) if error? - return callback(new Error("not a file")) if not stats?.isFile() - callback() + _checkFileExists(path, callback) { + if (callback == null) { callback = function(error) {}; } + const synctexDir = Path.dirname(path); + const synctexFile = Path.join(synctexDir, "output.synctex.gz"); + return fs.stat(synctexDir, function(error, stats) { + if ((error != null ? error.code : undefined) === 'ENOENT') { + return callback(new Errors.NotFoundError("called synctex with no output directory")); + } + if (error != null) { return callback(error); } + return fs.stat(synctexFile, function(error, stats) { + if ((error != null ? error.code : undefined) === 'ENOENT') { + return callback(new Errors.NotFoundError("called synctex with no output file")); + } + if (error != null) { return callback(error); } + if (!(stats != null ? stats.isFile() : undefined)) { return callback(new Error("not a file")); } + return callback(); + }); + }); + }, - _runSynctex: (project_id, user_id, command, callback = (error, stdout) ->) -> - seconds = 1000 + _runSynctex(project_id, user_id, command, callback) { + if (callback == null) { callback = function(error, stdout) {}; } + const seconds = 1000; - command.unshift("/opt/synctex") + command.unshift("/opt/synctex"); - directory = getCompileDir(project_id, user_id) - timeout = 60 * 1000 # increased to allow for large projects - compileName = getCompileName(project_id, user_id) - CommandRunner.run compileName, command, directory, Settings.clsi?.docker.image, timeout, {}, (error, output) -> - if error? - logger.err err:error, command:command, project_id:project_id, user_id:user_id, "error running synctex" - return callback(error) - callback(null, output.stdout) + const directory = getCompileDir(project_id, user_id); + const timeout = 60 * 1000; // increased to allow for large projects + const compileName = getCompileName(project_id, user_id); + return CommandRunner.run(compileName, command, directory, Settings.clsi != null ? Settings.clsi.docker.image : undefined, timeout, {}, function(error, output) { + if (error != null) { + logger.err({err:error, command, project_id, user_id}, "error running synctex"); + return callback(error); + } + return callback(null, output.stdout); + }); + }, - _parseSynctexFromCodeOutput: (output) -> - results = [] - for line in output.split("\n") - [node, page, h, v, width, height] = line.split("\t") - if node == "NODE" - results.push { - page: parseInt(page, 10) - h: parseFloat(h) - v: parseFloat(v) - height: parseFloat(height) + _parseSynctexFromCodeOutput(output) { + const results = []; + for (let line of Array.from(output.split("\n"))) { + const [node, page, h, v, width, height] = Array.from(line.split("\t")); + if (node === "NODE") { + results.push({ + page: parseInt(page, 10), + h: parseFloat(h), + v: parseFloat(v), + height: parseFloat(height), width: parseFloat(width) - } - return results - - _parseSynctexFromPdfOutput: (output, base_dir) -> - results = [] - for line in output.split("\n") - [node, file_path, line, column] = line.split("\t") - if node == "NODE" - file = file_path.slice(base_dir.length + 1) - results.push { - file: file - line: parseInt(line, 10) - column: parseInt(column, 10) - } - return results - - - wordcount: (project_id, user_id, file_name, image, callback = (error, pdfPositions) ->) -> - logger.log project_id:project_id, user_id:user_id, file_name:file_name, image:image, "running wordcount" - file_path = "$COMPILE_DIR/" + file_name - command = [ "texcount", '-nocol', '-inc', file_path, "-out=" + file_path + ".wc"] - compileDir = getCompileDir(project_id, user_id) - timeout = 60 * 1000 - compileName = getCompileName(project_id, user_id) - fse.ensureDir compileDir, (error) -> - if error? - logger.err {error, project_id, user_id, file_name}, "error ensuring dir for sync from code" - return callback(error) - CommandRunner.run compileName, command, compileDir, image, timeout, {}, (error) -> - return callback(error) if error? - fs.readFile compileDir + "/" + file_name + ".wc", "utf-8", (err, stdout) -> - if err? - #call it node_err so sentry doesn't use random path error as unique id so it can't be ignored - logger.err node_err:err, command:command, compileDir:compileDir, project_id:project_id, user_id:user_id, "error reading word count output" - return callback(err) - results = CompileManager._parseWordcountFromOutput(stdout) - logger.log project_id:project_id, user_id:user_id, wordcount: results, "word count results" - callback null, results - - _parseWordcountFromOutput: (output) -> - results = { - encode: "" - textWords: 0 - headWords: 0 - outside: 0 - headers: 0 - elements: 0 - mathInline: 0 - mathDisplay: 0 - errors: 0 - messages: "" + }); + } } - for line in output.split("\n") - [data, info] = line.split(":") - if data.indexOf("Encoding") > -1 - results['encode'] = info.trim() - if data.indexOf("in text") > -1 - results['textWords'] = parseInt(info, 10) - if data.indexOf("in head") > -1 - results['headWords'] = parseInt(info, 10) - if data.indexOf("outside") > -1 - results['outside'] = parseInt(info, 10) - if data.indexOf("of head") > -1 - results['headers'] = parseInt(info, 10) - if data.indexOf("Number of floats/tables/figures") > -1 - results['elements'] = parseInt(info, 10) - if data.indexOf("Number of math inlines") > -1 - results['mathInline'] = parseInt(info, 10) - if data.indexOf("Number of math displayed") > -1 - results['mathDisplay'] = parseInt(info, 10) - if data is "(errors" # errors reported as (errors:123) - results['errors'] = parseInt(info, 10) - if line.indexOf("!!! ") > -1 # errors logged as !!! message !!! - results['messages'] += line + "\n" - return results + return results; + }, + + _parseSynctexFromPdfOutput(output, base_dir) { + const results = []; + for (let line of Array.from(output.split("\n"))) { + let column, file_path, node; + [node, file_path, line, column] = Array.from(line.split("\t")); + if (node === "NODE") { + const file = file_path.slice(base_dir.length + 1); + results.push({ + file, + line: parseInt(line, 10), + column: parseInt(column, 10) + }); + } + } + return results; + }, + + + wordcount(project_id, user_id, file_name, image, callback) { + if (callback == null) { callback = function(error, pdfPositions) {}; } + logger.log({project_id, user_id, file_name, image}, "running wordcount"); + const file_path = `$COMPILE_DIR/${file_name}`; + const command = [ "texcount", '-nocol', '-inc', file_path, `-out=${file_path}.wc`]; + const compileDir = getCompileDir(project_id, user_id); + const timeout = 60 * 1000; + const compileName = getCompileName(project_id, user_id); + return fse.ensureDir(compileDir, function(error) { + if (error != null) { + logger.err({error, project_id, user_id, file_name}, "error ensuring dir for sync from code"); + return callback(error); + } + return CommandRunner.run(compileName, command, compileDir, image, timeout, {}, function(error) { + if (error != null) { return callback(error); } + return fs.readFile(compileDir + "/" + file_name + ".wc", "utf-8", function(err, stdout) { + if (err != null) { + //call it node_err so sentry doesn't use random path error as unique id so it can't be ignored + logger.err({node_err:err, command, compileDir, project_id, user_id}, "error reading word count output"); + return callback(err); + } + const results = CompileManager._parseWordcountFromOutput(stdout); + logger.log({project_id, user_id, wordcount: results}, "word count results"); + return callback(null, results); + }); + }); + }); + }, + + _parseWordcountFromOutput(output) { + const results = { + encode: "", + textWords: 0, + headWords: 0, + outside: 0, + headers: 0, + elements: 0, + mathInline: 0, + mathDisplay: 0, + errors: 0, + messages: "" + }; + for (let line of Array.from(output.split("\n"))) { + const [data, info] = Array.from(line.split(":")); + if (data.indexOf("Encoding") > -1) { + results['encode'] = info.trim(); + } + if (data.indexOf("in text") > -1) { + results['textWords'] = parseInt(info, 10); + } + if (data.indexOf("in head") > -1) { + results['headWords'] = parseInt(info, 10); + } + if (data.indexOf("outside") > -1) { + results['outside'] = parseInt(info, 10); + } + if (data.indexOf("of head") > -1) { + results['headers'] = parseInt(info, 10); + } + if (data.indexOf("Number of floats/tables/figures") > -1) { + results['elements'] = parseInt(info, 10); + } + if (data.indexOf("Number of math inlines") > -1) { + results['mathInline'] = parseInt(info, 10); + } + if (data.indexOf("Number of math displayed") > -1) { + results['mathDisplay'] = parseInt(info, 10); + } + if (data === "(errors") { // errors reported as (errors:123) + results['errors'] = parseInt(info, 10); + } + if (line.indexOf("!!! ") > -1) { // errors logged as !!! message !!! + results['messages'] += line + "\n"; + } + } + return results; + } +}); + +function __guard__(value, transform) { + return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; +} \ No newline at end of file diff --git a/services/clsi/app/coffee/ContentTypeMapper.js b/services/clsi/app/coffee/ContentTypeMapper.js index 68b2d14f38..c57057f9d8 100644 --- a/services/clsi/app/coffee/ContentTypeMapper.js +++ b/services/clsi/app/coffee/ContentTypeMapper.js @@ -1,24 +1,28 @@ -Path = require 'path' +let ContentTypeMapper; +const Path = require('path'); -# here we coerce html, css and js to text/plain, -# otherwise choose correct mime type based on file extension, -# falling back to octet-stream -module.exports = ContentTypeMapper = - map: (path) -> - switch Path.extname(path) - when '.txt', '.html', '.js', '.css', '.svg' - return 'text/plain' - when '.csv' - return 'text/csv' - when '.pdf' - return 'application/pdf' - when '.png' - return 'image/png' - when '.jpg', '.jpeg' - return 'image/jpeg' - when '.tiff' - return 'image/tiff' - when '.gif' - return 'image/gif' - else - return 'application/octet-stream' +// here we coerce html, css and js to text/plain, +// otherwise choose correct mime type based on file extension, +// falling back to octet-stream +module.exports = (ContentTypeMapper = { + map(path) { + switch (Path.extname(path)) { + case '.txt': case '.html': case '.js': case '.css': case '.svg': + return 'text/plain'; + case '.csv': + return 'text/csv'; + case '.pdf': + return 'application/pdf'; + case '.png': + return 'image/png'; + case '.jpg': case '.jpeg': + return 'image/jpeg'; + case '.tiff': + return 'image/tiff'; + case '.gif': + return 'image/gif'; + default: + return 'application/octet-stream'; + } + } +}); diff --git a/services/clsi/app/coffee/DbQueue.js b/services/clsi/app/coffee/DbQueue.js index a3593fddf4..0f1f8cf1aa 100644 --- a/services/clsi/app/coffee/DbQueue.js +++ b/services/clsi/app/coffee/DbQueue.js @@ -1,13 +1,16 @@ -async = require "async" -Settings = require "settings-sharelatex" -logger = require("logger-sharelatex") -queue = async.queue((task, cb)-> - task(cb) - , Settings.parallelSqlQueryLimit) +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const async = require("async"); +const Settings = require("settings-sharelatex"); +const logger = require("logger-sharelatex"); +const queue = async.queue((task, cb)=> task(cb) + , Settings.parallelSqlQueryLimit); -queue.drain = ()-> - logger.debug('all items have been processed') +queue.drain = ()=> logger.debug('all items have been processed'); module.exports = - queue: queue + {queue}; diff --git a/services/clsi/app/coffee/DockerLockManager.js b/services/clsi/app/coffee/DockerLockManager.js index bf90f02345..9c7deffe46 100644 --- a/services/clsi/app/coffee/DockerLockManager.js +++ b/services/clsi/app/coffee/DockerLockManager.js @@ -1,56 +1,84 @@ -logger = require "logger-sharelatex" +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let LockManager; +const logger = require("logger-sharelatex"); -LockState = {} # locks for docker container operations, by container name +const LockState = {}; // locks for docker container operations, by container name -module.exports = LockManager = +module.exports = (LockManager = { - MAX_LOCK_HOLD_TIME: 15000 # how long we can keep a lock - MAX_LOCK_WAIT_TIME: 10000 # how long we wait for a lock - LOCK_TEST_INTERVAL: 1000 # retry time + MAX_LOCK_HOLD_TIME: 15000, // how long we can keep a lock + MAX_LOCK_WAIT_TIME: 10000, // how long we wait for a lock + LOCK_TEST_INTERVAL: 1000, // retry time - tryLock: (key, callback = (err, gotLock) ->) -> - existingLock = LockState[key] - if existingLock? # the lock is already taken, check how old it is - lockAge = Date.now() - existingLock.created - if lockAge < LockManager.MAX_LOCK_HOLD_TIME - return callback(null, false) # we didn't get the lock, bail out - else - logger.error {key: key, lock: existingLock, age:lockAge}, "taking old lock by force" - # take the lock - LockState[key] = lockValue = {created: Date.now()} - callback(null, true, lockValue) + tryLock(key, callback) { + let lockValue; + if (callback == null) { callback = function(err, gotLock) {}; } + const existingLock = LockState[key]; + if (existingLock != null) { // the lock is already taken, check how old it is + const lockAge = Date.now() - existingLock.created; + if (lockAge < LockManager.MAX_LOCK_HOLD_TIME) { + return callback(null, false); // we didn't get the lock, bail out + } else { + logger.error({key, lock: existingLock, age:lockAge}, "taking old lock by force"); + } + } + // take the lock + LockState[key] = (lockValue = {created: Date.now()}); + return callback(null, true, lockValue); + }, - getLock: (key, callback = (error, lockValue) ->) -> - startTime = Date.now() - do attempt = () -> - LockManager.tryLock key, (error, gotLock, lockValue) -> - return callback(error) if error? - if gotLock - callback(null, lockValue) - else if Date.now() - startTime > LockManager.MAX_LOCK_WAIT_TIME - e = new Error("Lock timeout") - e.key = key - return callback(e) - else - setTimeout attempt, LockManager.LOCK_TEST_INTERVAL + getLock(key, callback) { + let attempt; + if (callback == null) { callback = function(error, lockValue) {}; } + const startTime = Date.now(); + return (attempt = () => + LockManager.tryLock(key, function(error, gotLock, lockValue) { + if (error != null) { return callback(error); } + if (gotLock) { + return callback(null, lockValue); + } else if ((Date.now() - startTime) > LockManager.MAX_LOCK_WAIT_TIME) { + const e = new Error("Lock timeout"); + e.key = key; + return callback(e); + } else { + return setTimeout(attempt, LockManager.LOCK_TEST_INTERVAL); + } + }) + )(); + }, - releaseLock: (key, lockValue, callback = (error) ->) -> - existingLock = LockState[key] - if existingLock is lockValue # lockValue is an object, so we can test by reference - delete LockState[key] # our lock, so we can free it - callback() - else if existingLock? # lock exists but doesn't match ours - logger.error {key:key, lock: existingLock}, "tried to release lock taken by force" - callback() - else - logger.error {key:key, lock: existingLock}, "tried to release lock that has gone" - callback() + releaseLock(key, lockValue, callback) { + if (callback == null) { callback = function(error) {}; } + const existingLock = LockState[key]; + if (existingLock === lockValue) { // lockValue is an object, so we can test by reference + delete LockState[key]; // our lock, so we can free it + return callback(); + } else if (existingLock != null) { // lock exists but doesn't match ours + logger.error({key, lock: existingLock}, "tried to release lock taken by force"); + return callback(); + } else { + logger.error({key, lock: existingLock}, "tried to release lock that has gone"); + return callback(); + } + }, - runWithLock: (key, runner, callback = ( (error) -> )) -> - LockManager.getLock key, (error, lockValue) -> - return callback(error) if error? - runner (error1, args...) -> - LockManager.releaseLock key, lockValue, (error2) -> - error = error1 or error2 - return callback(error) if error? - callback(null, args...) + runWithLock(key, runner, callback) { + if (callback == null) { callback = function(error) {}; } + return LockManager.getLock(key, function(error, lockValue) { + if (error != null) { return callback(error); } + return runner((error1, ...args) => + LockManager.releaseLock(key, lockValue, function(error2) { + error = error1 || error2; + if (error != null) { return callback(error); } + return callback(null, ...Array.from(args)); + }) + ); + }); + } +}); diff --git a/services/clsi/app/coffee/DockerRunner.js b/services/clsi/app/coffee/DockerRunner.js index 6ea929f249..ab78419ece 100644 --- a/services/clsi/app/coffee/DockerRunner.js +++ b/services/clsi/app/coffee/DockerRunner.js @@ -1,358 +1,475 @@ -Settings = require "settings-sharelatex" -logger = require "logger-sharelatex" -Docker = require("dockerode") -dockerode = new Docker() -crypto = require "crypto" -async = require "async" -LockManager = require "./DockerLockManager" -fs = require "fs" -Path = require 'path' -_ = require "underscore" +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__ + * DS205: Consider reworking code to avoid use of IIFEs + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let DockerRunner, oneHour; +const Settings = require("settings-sharelatex"); +const logger = require("logger-sharelatex"); +const Docker = require("dockerode"); +const dockerode = new Docker(); +const crypto = require("crypto"); +const async = require("async"); +const LockManager = require("./DockerLockManager"); +const fs = require("fs"); +const Path = require('path'); +const _ = require("underscore"); -logger.info "using docker runner" +logger.info("using docker runner"); -usingSiblingContainers = () -> - Settings?.path?.sandboxedCompilesHostDir? +const usingSiblingContainers = () => __guard__(Settings != null ? Settings.path : undefined, x => x.sandboxedCompilesHostDir) != null; -module.exports = DockerRunner = - ERR_NOT_DIRECTORY: new Error("not a directory") - ERR_TERMINATED: new Error("terminated") - ERR_EXITED: new Error("exited") - ERR_TIMED_OUT: new Error("container timed out") +module.exports = (DockerRunner = { + ERR_NOT_DIRECTORY: new Error("not a directory"), + ERR_TERMINATED: new Error("terminated"), + ERR_EXITED: new Error("exited"), + ERR_TIMED_OUT: new Error("container timed out"), - run: (project_id, command, directory, image, timeout, environment, callback = (error, output) ->) -> + run(project_id, command, directory, image, timeout, environment, callback) { - if usingSiblingContainers() - _newPath = Settings.path.sandboxedCompilesHostDir - logger.log {path: _newPath}, "altering bind path for sibling containers" - # Server Pro, example: - # '/var/lib/sharelatex/data/compiles/<project-id>' - # ... becomes ... - # '/opt/sharelatex_data/data/compiles/<project-id>' - directory = Path.join(Settings.path.sandboxedCompilesHostDir, Path.basename(directory)) + let name; + if (callback == null) { callback = function(error, output) {}; } + if (usingSiblingContainers()) { + const _newPath = Settings.path.sandboxedCompilesHostDir; + logger.log({path: _newPath}, "altering bind path for sibling containers"); + // Server Pro, example: + // '/var/lib/sharelatex/data/compiles/<project-id>' + // ... becomes ... + // '/opt/sharelatex_data/data/compiles/<project-id>' + directory = Path.join(Settings.path.sandboxedCompilesHostDir, Path.basename(directory)); + } - volumes = {} - volumes[directory] = "/compile" + const volumes = {}; + volumes[directory] = "/compile"; - command = (arg.toString().replace?('$COMPILE_DIR', "/compile") for arg in command) - if !image? - image = Settings.clsi.docker.image + command = (Array.from(command).map((arg) => __guardMethod__(arg.toString(), 'replace', o => o.replace('$COMPILE_DIR', "/compile")))); + if ((image == null)) { + ({ image } = Settings.clsi.docker); + } - if Settings.texliveImageNameOveride? - img = image.split("/") - image = "#{Settings.texliveImageNameOveride}/#{img[2]}" + if (Settings.texliveImageNameOveride != null) { + const img = image.split("/"); + image = `${Settings.texliveImageNameOveride}/${img[2]}`; + } - options = DockerRunner._getContainerOptions(command, image, volumes, timeout, environment) - fingerprint = DockerRunner._fingerprintContainer(options) - options.name = name = "project-#{project_id}-#{fingerprint}" + const options = DockerRunner._getContainerOptions(command, image, volumes, timeout, environment); + const fingerprint = DockerRunner._fingerprintContainer(options); + options.name = (name = `project-${project_id}-${fingerprint}`); - # logOptions = _.clone(options) - # logOptions?.HostConfig?.SecurityOpt = "secomp used, removed in logging" - logger.log project_id: project_id, "running docker container" - DockerRunner._runAndWaitForContainer options, volumes, timeout, (error, output) -> - if error?.message?.match("HTTP code is 500") - logger.log err: error, project_id: project_id, "error running container so destroying and retrying" - DockerRunner.destroyContainer name, null, true, (error) -> - return callback(error) if error? - DockerRunner._runAndWaitForContainer options, volumes, timeout, callback - else - callback(error, output) + // logOptions = _.clone(options) + // logOptions?.HostConfig?.SecurityOpt = "secomp used, removed in logging" + logger.log({project_id}, "running docker container"); + DockerRunner._runAndWaitForContainer(options, volumes, timeout, function(error, output) { + if (__guard__(error != null ? error.message : undefined, x => x.match("HTTP code is 500"))) { + logger.log({err: error, project_id}, "error running container so destroying and retrying"); + return DockerRunner.destroyContainer(name, null, true, function(error) { + if (error != null) { return callback(error); } + return DockerRunner._runAndWaitForContainer(options, volumes, timeout, callback); + }); + } else { + return callback(error, output); + } + }); - return name # pass back the container name to allow it to be killed + return name; + }, // pass back the container name to allow it to be killed - kill: (container_id, callback = (error) ->) -> - logger.log container_id: container_id, "sending kill signal to container" - container = dockerode.getContainer(container_id) - container.kill (error) -> - if error? and error?.message?.match?(/Cannot kill container .* is not running/) - logger.warn err: error, container_id: container_id, "container not running, continuing" - error = null - if error? - logger.error err: error, container_id: container_id, "error killing container" - return callback(error) - else - callback() + kill(container_id, callback) { + if (callback == null) { callback = function(error) {}; } + logger.log({container_id}, "sending kill signal to container"); + const container = dockerode.getContainer(container_id); + return container.kill(function(error) { + if ((error != null) && __guardMethod__(error != null ? error.message : undefined, 'match', o => o.match(/Cannot kill container .* is not running/))) { + logger.warn({err: error, container_id}, "container not running, continuing"); + error = null; + } + if (error != null) { + logger.error({err: error, container_id}, "error killing container"); + return callback(error); + } else { + return callback(); + } + }); + }, - _runAndWaitForContainer: (options, volumes, timeout, _callback = (error, output) ->) -> - callback = (args...) -> - _callback(args...) - # Only call the callback once - _callback = () -> + _runAndWaitForContainer(options, volumes, timeout, _callback) { + if (_callback == null) { _callback = function(error, output) {}; } + const callback = function(...args) { + _callback(...Array.from(args || [])); + // Only call the callback once + return _callback = function() {}; + }; - name = options.name + const { name } = options; - streamEnded = false - containerReturned = false - output = {} + let streamEnded = false; + let containerReturned = false; + let output = {}; - callbackIfFinished = () -> - if streamEnded and containerReturned - callback(null, output) + const callbackIfFinished = function() { + if (streamEnded && containerReturned) { + return callback(null, output); + } + }; - attachStreamHandler = (error, _output) -> - return callback(error) if error? - output = _output - streamEnded = true - callbackIfFinished() + const attachStreamHandler = function(error, _output) { + if (error != null) { return callback(error); } + output = _output; + streamEnded = true; + return callbackIfFinished(); + }; - DockerRunner.startContainer options, volumes, attachStreamHandler, (error, containerId) -> - return callback(error) if error? + return DockerRunner.startContainer(options, volumes, attachStreamHandler, function(error, containerId) { + if (error != null) { return callback(error); } - DockerRunner.waitForContainer name, timeout, (error, exitCode) -> - return callback(error) if error? - if exitCode is 137 # exit status from kill -9 - err = DockerRunner.ERR_TERMINATED - err.terminated = true - return callback(err) - if exitCode is 1 # exit status from chktex - err = DockerRunner.ERR_EXITED - err.code = exitCode - return callback(err) - containerReturned = true - options?.HostConfig?.SecurityOpt = null #small log line - logger.log err:err, exitCode:exitCode, options:options, "docker container has exited" - callbackIfFinished() - - _getContainerOptions: (command, image, volumes, timeout, environment) -> - timeoutInSeconds = timeout / 1000 - - dockerVolumes = {} - for hostVol, dockerVol of volumes - dockerVolumes[dockerVol] = {} - - if volumes[hostVol].slice(-3).indexOf(":r") == -1 - volumes[hostVol] = "#{dockerVol}:rw" - - # merge settings and environment parameter - env = {} - for src in [Settings.clsi.docker.env, environment or {}] - env[key] = value for key, value of src - # set the path based on the image year - if m = image.match /:([0-9]+)\.[0-9]+/ - year = m[1] - else - year = "2014" - env['PATH'] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/#{year}/bin/x86_64-linux/" - options = - "Cmd" : command, - "Image" : image - "Volumes" : dockerVolumes - "WorkingDir" : "/compile" - "NetworkDisabled" : true - "Memory" : 1024 * 1024 * 1024 * 1024 # 1 Gb - "User" : Settings.clsi.docker.user - "Env" : ("#{key}=#{value}" for key, value of env) # convert the environment hash to an array - "HostConfig" : - "Binds": ("#{hostVol}:#{dockerVol}" for hostVol, dockerVol of volumes) - "LogConfig": {"Type": "none", "Config": {}} - "Ulimits": [{'Name': 'cpu', 'Soft': timeoutInSeconds+5, 'Hard': timeoutInSeconds+10}] - "CapDrop": "ALL" - "SecurityOpt": ["no-new-privileges"] - - - if Settings.path?.synctexBinHostPath? - options["HostConfig"]["Binds"].push("#{Settings.path.synctexBinHostPath}:/opt/synctex:ro") - - if Settings.clsi.docker.seccomp_profile? - options.HostConfig.SecurityOpt.push "seccomp=#{Settings.clsi.docker.seccomp_profile}" - - return options - - _fingerprintContainer: (containerOptions) -> - # Yay, Hashing! - json = JSON.stringify(containerOptions) - return crypto.createHash("md5").update(json).digest("hex") - - startContainer: (options, volumes, attachStreamHandler, callback) -> - LockManager.runWithLock options.name, (releaseLock) -> - # Check that volumes exist before starting the container. - # When a container is started with volume pointing to a - # non-existent directory then docker creates the directory but - # with root ownership. - DockerRunner._checkVolumes options, volumes, (err) -> - return releaseLock(err) if err? - DockerRunner._startContainer options, volumes, attachStreamHandler, releaseLock - , callback - - # Check that volumes exist and are directories - _checkVolumes: (options, volumes, callback = (error, containerName) ->) -> - if usingSiblingContainers() - # Server Pro, with sibling-containers active, skip checks - return callback(null) - - checkVolume = (path, cb) -> - fs.stat path, (err, stats) -> - return cb(err) if err? - return cb(DockerRunner.ERR_NOT_DIRECTORY) if not stats?.isDirectory() - cb() - jobs = [] - for vol of volumes - do (vol) -> - jobs.push (cb) -> checkVolume(vol, cb) - async.series jobs, callback - - _startContainer: (options, volumes, attachStreamHandler, callback = ((error, output) ->)) -> - callback = _.once(callback) - name = options.name - - logger.log {container_name: name}, "starting container" - container = dockerode.getContainer(name) - - createAndStartContainer = -> - dockerode.createContainer options, (error, container) -> - return callback(error) if error? - startExistingContainer() - - startExistingContainer = -> - DockerRunner.attachToContainer options.name, attachStreamHandler, (error)-> - return callback(error) if error? - container.start (error) -> - if error? and error?.statusCode != 304 #already running - return callback(error) - else - callback() - - container.inspect (error, stats)-> - if error?.statusCode == 404 - createAndStartContainer() - else if error? - logger.err {container_name: name, error:error}, "unable to inspect container to start" - return callback(error) - else - startExistingContainer() - - - attachToContainer: (containerId, attachStreamHandler, attachStartCallback) -> - container = dockerode.getContainer(containerId) - container.attach {stdout: 1, stderr: 1, stream: 1}, (error, stream) -> - if error? - logger.error err: error, container_id: containerId, "error attaching to container" - return attachStartCallback(error) - else - attachStartCallback() - - - logger.log container_id: containerId, "attached to container" - - MAX_OUTPUT = 1024 * 1024 # limit output to 1MB - createStringOutputStream = (name) -> - return { - data: "" - overflowed: false - write: (data) -> - return if @overflowed - if @data.length < MAX_OUTPUT - @data += data - else - logger.error container_id: containerId, length: @data.length, maxLen: MAX_OUTPUT, "#{name} exceeds max size" - @data += "(...truncated at #{MAX_OUTPUT} chars...)" - @overflowed = true - # kill container if too much output - # docker.containers.kill(containerId, () ->) + return DockerRunner.waitForContainer(name, timeout, function(error, exitCode) { + let err; + if (error != null) { return callback(error); } + if (exitCode === 137) { // exit status from kill -9 + err = DockerRunner.ERR_TERMINATED; + err.terminated = true; + return callback(err); } + if (exitCode === 1) { // exit status from chktex + err = DockerRunner.ERR_EXITED; + err.code = exitCode; + return callback(err); + } + containerReturned = true; + __guard__(options != null ? options.HostConfig : undefined, x => x.SecurityOpt = null); //small log line + logger.log({err, exitCode, options}, "docker container has exited"); + return callbackIfFinished(); + }); + }); + }, - stdout = createStringOutputStream "stdout" - stderr = createStringOutputStream "stderr" + _getContainerOptions(command, image, volumes, timeout, environment) { + let m, year; + let key, value, hostVol, dockerVol; + const timeoutInSeconds = timeout / 1000; - container.modem.demuxStream(stream, stdout, stderr) + const dockerVolumes = {}; + for (hostVol in volumes) { + dockerVol = volumes[hostVol]; + dockerVolumes[dockerVol] = {}; - stream.on "error", (err) -> - logger.error err: err, container_id: containerId, "error reading from container stream" + if (volumes[hostVol].slice(-3).indexOf(":r") === -1) { + volumes[hostVol] = `${dockerVol}:rw`; + } + } - stream.on "end", () -> - attachStreamHandler null, {stdout: stdout.data, stderr: stderr.data} + // merge settings and environment parameter + const env = {}; + for (let src of [Settings.clsi.docker.env, environment || {}]) { + for (key in src) { value = src[key]; env[key] = value; } + } + // set the path based on the image year + if ((m = image.match(/:([0-9]+)\.[0-9]+/))) { + year = m[1]; + } else { + year = "2014"; + } + env['PATH'] = `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/${year}/bin/x86_64-linux/`; + const options = { + "Cmd" : command, + "Image" : image, + "Volumes" : dockerVolumes, + "WorkingDir" : "/compile", + "NetworkDisabled" : true, + "Memory" : 1024 * 1024 * 1024 * 1024, // 1 Gb + "User" : Settings.clsi.docker.user, + "Env" : (((() => { + const result = []; + for (key in env) { + value = env[key]; + result.push(`${key}=${value}`); + } + return result; + })())), // convert the environment hash to an array + "HostConfig" : { + "Binds": (((() => { + const result1 = []; + for (hostVol in volumes) { + dockerVol = volumes[hostVol]; + result1.push(`${hostVol}:${dockerVol}`); + } + return result1; + })())), + "LogConfig": {"Type": "none", "Config": {}}, + "Ulimits": [{'Name': 'cpu', 'Soft': timeoutInSeconds+5, 'Hard': timeoutInSeconds+10}], + "CapDrop": "ALL", + "SecurityOpt": ["no-new-privileges"] + } + }; - waitForContainer: (containerId, timeout, _callback = (error, exitCode) ->) -> - callback = (args...) -> - _callback(args...) - # Only call the callback once - _callback = () -> - container = dockerode.getContainer(containerId) + if ((Settings.path != null ? Settings.path.synctexBinHostPath : undefined) != null) { + options["HostConfig"]["Binds"].push(`${Settings.path.synctexBinHostPath}:/opt/synctex:ro`); + } - timedOut = false - timeoutId = setTimeout () -> - timedOut = true - logger.log container_id: containerId, "timeout reached, killing container" - container.kill(() ->) - , timeout + if (Settings.clsi.docker.seccomp_profile != null) { + options.HostConfig.SecurityOpt.push(`seccomp=${Settings.clsi.docker.seccomp_profile}`); + } - logger.log container_id: containerId, "waiting for docker container" - container.wait (error, res) -> - if error? - clearTimeout timeoutId - logger.error err: error, container_id: containerId, "error waiting for container" - return callback(error) - if timedOut - logger.log containerId: containerId, "docker container timed out" - error = DockerRunner.ERR_TIMED_OUT - error.timedout = true - callback error - else - clearTimeout timeoutId - logger.log container_id: containerId, exitCode: res.StatusCode, "docker container returned" - callback null, res.StatusCode + return options; + }, - destroyContainer: (containerName, containerId, shouldForce, callback = (error) ->) -> - # We want the containerName for the lock and, ideally, the - # containerId to delete. There is a bug in the docker.io module - # where if you delete by name and there is an error, it throws an - # async exception, but if you delete by id it just does a normal - # error callback. We fall back to deleting by name if no id is - # supplied. - LockManager.runWithLock containerName, (releaseLock) -> - DockerRunner._destroyContainer containerId or containerName, shouldForce, releaseLock - , callback + _fingerprintContainer(containerOptions) { + // Yay, Hashing! + const json = JSON.stringify(containerOptions); + return crypto.createHash("md5").update(json).digest("hex"); + }, - _destroyContainer: (containerId, shouldForce, callback = (error) ->) -> - logger.log container_id: containerId, "destroying docker container" - container = dockerode.getContainer(containerId) - container.remove {force: shouldForce == true}, (error) -> - if error? and error?.statusCode == 404 - logger.warn err: error, container_id: containerId, "container not found, continuing" - error = null - if error? - logger.error err: error, container_id: containerId, "error destroying container" - else - logger.log container_id: containerId, "destroyed container" - callback(error) + startContainer(options, volumes, attachStreamHandler, callback) { + return LockManager.runWithLock(options.name, releaseLock => + // Check that volumes exist before starting the container. + // When a container is started with volume pointing to a + // non-existent directory then docker creates the directory but + // with root ownership. + DockerRunner._checkVolumes(options, volumes, function(err) { + if (err != null) { return releaseLock(err); } + return DockerRunner._startContainer(options, volumes, attachStreamHandler, releaseLock); + }) + + , callback); + }, - # handle expiry of docker containers + // Check that volumes exist and are directories + _checkVolumes(options, volumes, callback) { + if (callback == null) { callback = function(error, containerName) {}; } + if (usingSiblingContainers()) { + // Server Pro, with sibling-containers active, skip checks + return callback(null); + } - MAX_CONTAINER_AGE: Settings.clsi.docker.maxContainerAge or oneHour = 60 * 60 * 1000 + const checkVolume = (path, cb) => + fs.stat(path, function(err, stats) { + if (err != null) { return cb(err); } + if (!(stats != null ? stats.isDirectory() : undefined)) { return cb(DockerRunner.ERR_NOT_DIRECTORY); } + return cb(); + }) + ; + const jobs = []; + for (let vol in volumes) { + (vol => jobs.push(cb => checkVolume(vol, cb)))(vol); + } + return async.series(jobs, callback); + }, - examineOldContainer: (container, callback = (error, name, id, ttl)->) -> - name = container.Name or container.Names?[0] - created = container.Created * 1000 # creation time is returned in seconds - now = Date.now() - age = now - created - maxAge = DockerRunner.MAX_CONTAINER_AGE - ttl = maxAge - age - logger.log {containerName: name, created: created, now: now, age: age, maxAge: maxAge, ttl: ttl}, "checking whether to destroy container" - callback(null, name, container.Id, ttl) + _startContainer(options, volumes, attachStreamHandler, callback) { + if (callback == null) { callback = function(error, output) {}; } + callback = _.once(callback); + const { name } = options; - destroyOldContainers: (callback = (error) ->) -> - dockerode.listContainers all: true, (error, containers) -> - return callback(error) if error? - jobs = [] - for container in containers or [] - do (container) -> - DockerRunner.examineOldContainer container, (err, name, id, ttl) -> - if name.slice(0, 9) == '/project-' && ttl <= 0 - jobs.push (cb) -> - DockerRunner.destroyContainer name, id, false, () -> cb() - # Ignore errors because some containers get stuck but - # will be destroyed next time - async.series jobs, callback + logger.log({container_name: name}, "starting container"); + const container = dockerode.getContainer(name); - startContainerMonitor: () -> - logger.log {maxAge: DockerRunner.MAX_CONTAINER_AGE}, "starting container expiry" - # randomise the start time - randomDelay = Math.floor(Math.random() * 5 * 60 * 1000) - setTimeout () -> - setInterval () -> - DockerRunner.destroyOldContainers() - , oneHour = 60 * 60 * 1000 - , randomDelay + const createAndStartContainer = () => + dockerode.createContainer(options, function(error, container) { + if (error != null) { return callback(error); } + return startExistingContainer(); + }) + ; -DockerRunner.startContainerMonitor() + var startExistingContainer = () => + DockerRunner.attachToContainer(options.name, attachStreamHandler, function(error){ + if (error != null) { return callback(error); } + return container.start(function(error) { + if ((error != null) && ((error != null ? error.statusCode : undefined) !== 304)) { //already running + return callback(error); + } else { + return callback(); + } + }); + }) + ; + + return container.inspect(function(error, stats){ + if ((error != null ? error.statusCode : undefined) === 404) { + return createAndStartContainer(); + } else if (error != null) { + logger.err({container_name: name, error}, "unable to inspect container to start"); + return callback(error); + } else { + return startExistingContainer(); + } + }); + }, + + + attachToContainer(containerId, attachStreamHandler, attachStartCallback) { + const container = dockerode.getContainer(containerId); + return container.attach({stdout: 1, stderr: 1, stream: 1}, function(error, stream) { + if (error != null) { + logger.error({err: error, container_id: containerId}, "error attaching to container"); + return attachStartCallback(error); + } else { + attachStartCallback(); + } + + + logger.log({container_id: containerId}, "attached to container"); + + const MAX_OUTPUT = 1024 * 1024; // limit output to 1MB + const createStringOutputStream = function(name) { + return { + data: "", + overflowed: false, + write(data) { + if (this.overflowed) { return; } + if (this.data.length < MAX_OUTPUT) { + return this.data += data; + } else { + logger.error({container_id: containerId, length: this.data.length, maxLen: MAX_OUTPUT}, `${name} exceeds max size`); + this.data += `(...truncated at ${MAX_OUTPUT} chars...)`; + return this.overflowed = true; + } + } + // kill container if too much output + // docker.containers.kill(containerId, () ->) + }; + }; + + const stdout = createStringOutputStream("stdout"); + const stderr = createStringOutputStream("stderr"); + + container.modem.demuxStream(stream, stdout, stderr); + + stream.on("error", err => logger.error({err, container_id: containerId}, "error reading from container stream")); + + return stream.on("end", () => attachStreamHandler(null, {stdout: stdout.data, stderr: stderr.data})); + }); + }, + + waitForContainer(containerId, timeout, _callback) { + if (_callback == null) { _callback = function(error, exitCode) {}; } + const callback = function(...args) { + _callback(...Array.from(args || [])); + // Only call the callback once + return _callback = function() {}; + }; + + const container = dockerode.getContainer(containerId); + + let timedOut = false; + const timeoutId = setTimeout(function() { + timedOut = true; + logger.log({container_id: containerId}, "timeout reached, killing container"); + return container.kill(function() {}); + } + , timeout); + + logger.log({container_id: containerId}, "waiting for docker container"); + return container.wait(function(error, res) { + if (error != null) { + clearTimeout(timeoutId); + logger.error({err: error, container_id: containerId}, "error waiting for container"); + return callback(error); + } + if (timedOut) { + logger.log({containerId}, "docker container timed out"); + error = DockerRunner.ERR_TIMED_OUT; + error.timedout = true; + return callback(error); + } else { + clearTimeout(timeoutId); + logger.log({container_id: containerId, exitCode: res.StatusCode}, "docker container returned"); + return callback(null, res.StatusCode); + } + }); + }, + + destroyContainer(containerName, containerId, shouldForce, callback) { + // We want the containerName for the lock and, ideally, the + // containerId to delete. There is a bug in the docker.io module + // where if you delete by name and there is an error, it throws an + // async exception, but if you delete by id it just does a normal + // error callback. We fall back to deleting by name if no id is + // supplied. + if (callback == null) { callback = function(error) {}; } + return LockManager.runWithLock(containerName, releaseLock => DockerRunner._destroyContainer(containerId || containerName, shouldForce, releaseLock) + , callback); + }, + + _destroyContainer(containerId, shouldForce, callback) { + if (callback == null) { callback = function(error) {}; } + logger.log({container_id: containerId}, "destroying docker container"); + const container = dockerode.getContainer(containerId); + return container.remove({force: shouldForce === true}, function(error) { + if ((error != null) && ((error != null ? error.statusCode : undefined) === 404)) { + logger.warn({err: error, container_id: containerId}, "container not found, continuing"); + error = null; + } + if (error != null) { + logger.error({err: error, container_id: containerId}, "error destroying container"); + } else { + logger.log({container_id: containerId}, "destroyed container"); + } + return callback(error); + }); + }, + + // handle expiry of docker containers + + MAX_CONTAINER_AGE: Settings.clsi.docker.maxContainerAge || (oneHour = 60 * 60 * 1000), + + examineOldContainer(container, callback) { + if (callback == null) { callback = function(error, name, id, ttl){}; } + const name = container.Name || (container.Names != null ? container.Names[0] : undefined); + const created = container.Created * 1000; // creation time is returned in seconds + const now = Date.now(); + const age = now - created; + const maxAge = DockerRunner.MAX_CONTAINER_AGE; + const ttl = maxAge - age; + logger.log({containerName: name, created, now, age, maxAge, ttl}, "checking whether to destroy container"); + return callback(null, name, container.Id, ttl); + }, + + destroyOldContainers(callback) { + if (callback == null) { callback = function(error) {}; } + return dockerode.listContainers({all: true}, function(error, containers) { + if (error != null) { return callback(error); } + const jobs = []; + for (let container of Array.from(containers || [])) { + (container => + DockerRunner.examineOldContainer(container, function(err, name, id, ttl) { + if ((name.slice(0, 9) === '/project-') && (ttl <= 0)) { + return jobs.push(cb => DockerRunner.destroyContainer(name, id, false, () => cb())); + } + }) + )(container); + } + // Ignore errors because some containers get stuck but + // will be destroyed next time + return async.series(jobs, callback); + }); + }, + + startContainerMonitor() { + logger.log({maxAge: DockerRunner.MAX_CONTAINER_AGE}, "starting container expiry"); + // randomise the start time + const randomDelay = Math.floor(Math.random() * 5 * 60 * 1000); + return setTimeout(() => + setInterval(() => DockerRunner.destroyOldContainers() + , (oneHour = 60 * 60 * 1000)) + + , randomDelay); + } +}); + +DockerRunner.startContainerMonitor(); + +function __guard__(value, transform) { + return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; +} +function __guardMethod__(obj, methodName, transform) { + if (typeof obj !== 'undefined' && obj !== null && typeof obj[methodName] === 'function') { + return transform(obj, methodName); + } else { + return undefined; + } +} \ No newline at end of file diff --git a/services/clsi/app/coffee/DraftModeManager.js b/services/clsi/app/coffee/DraftModeManager.js index 2f9e931c0c..8ddbbd02df 100644 --- a/services/clsi/app/coffee/DraftModeManager.js +++ b/services/clsi/app/coffee/DraftModeManager.js @@ -1,24 +1,37 @@ -fs = require "fs" -logger = require "logger-sharelatex" +/* + * 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 DraftModeManager; +const fs = require("fs"); +const logger = require("logger-sharelatex"); -module.exports = DraftModeManager = - injectDraftMode: (filename, callback = (error) ->) -> - fs.readFile filename, "utf8", (error, content) -> - return callback(error) if error? - # avoid adding draft mode more than once - if content?.indexOf("\\documentclass\[draft") >= 0 - return callback() - modified_content = DraftModeManager._injectDraftOption content - logger.log { - content: content.slice(0,1024), # \documentclass is normally v near the top +module.exports = (DraftModeManager = { + injectDraftMode(filename, callback) { + if (callback == null) { callback = function(error) {}; } + return fs.readFile(filename, "utf8", function(error, content) { + if (error != null) { return callback(error); } + // avoid adding draft mode more than once + if ((content != null ? content.indexOf("\\documentclass\[draft") : undefined) >= 0) { + return callback(); + } + const modified_content = DraftModeManager._injectDraftOption(content); + logger.log({ + content: content.slice(0,1024), // \documentclass is normally v near the top modified_content: modified_content.slice(0,1024), filename - }, "injected draft class" - fs.writeFile filename, modified_content, callback + }, "injected draft class"); + return fs.writeFile(filename, modified_content, callback); + }); + }, - _injectDraftOption: (content) -> - content - # With existing options (must be first, otherwise both are applied) + _injectDraftOption(content) { + return content + // With existing options (must be first, otherwise both are applied) .replace(/\\documentclass\[/g, "\\documentclass[draft,") - # Without existing options - .replace(/\\documentclass\{/g, "\\documentclass[draft]{") + // Without existing options + .replace(/\\documentclass\{/g, "\\documentclass[draft]{"); + } +}); diff --git a/services/clsi/app/coffee/Errors.js b/services/clsi/app/coffee/Errors.js index b375513e1c..3a9ef220c4 100644 --- a/services/clsi/app/coffee/Errors.js +++ b/services/clsi/app/coffee/Errors.js @@ -1,25 +1,30 @@ -NotFoundError = (message) -> - error = new Error(message) - error.name = "NotFoundError" - error.__proto__ = NotFoundError.prototype - return error -NotFoundError.prototype.__proto__ = Error.prototype +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; -FilesOutOfSyncError = (message) -> - error = new Error(message) - error.name = "FilesOutOfSyncError" - error.__proto__ = FilesOutOfSyncError.prototype - return error -FilesOutOfSyncError.prototype.__proto__ = Error.prototype +var FilesOutOfSyncError = function(message) { + const error = new Error(message); + error.name = "FilesOutOfSyncError"; + error.__proto__ = FilesOutOfSyncError.prototype; + return error; +}; +FilesOutOfSyncError.prototype.__proto__ = Error.prototype; -AlreadyCompilingError = (message) -> - error = new Error(message) - error.name = "AlreadyCompilingError" - error.__proto__ = AlreadyCompilingError.prototype - return error -AlreadyCompilingError.prototype.__proto__ = Error.prototype +var AlreadyCompilingError = function(message) { + const error = new Error(message); + error.name = "AlreadyCompilingError"; + error.__proto__ = AlreadyCompilingError.prototype; + return error; +}; +AlreadyCompilingError.prototype.__proto__ = Error.prototype; -module.exports = Errors = - NotFoundError: NotFoundError - FilesOutOfSyncError: FilesOutOfSyncError - AlreadyCompilingError: AlreadyCompilingError +module.exports = (Errors = { + NotFoundError, + FilesOutOfSyncError, + AlreadyCompilingError +}); diff --git a/services/clsi/app/coffee/LatexRunner.js b/services/clsi/app/coffee/LatexRunner.js index 29433f83e3..4c83e084fc 100644 --- a/services/clsi/app/coffee/LatexRunner.js +++ b/services/clsi/app/coffee/LatexRunner.js @@ -1,95 +1,123 @@ -Path = require "path" -Settings = require "settings-sharelatex" -logger = require "logger-sharelatex" -Metrics = require "./Metrics" -CommandRunner = require "./CommandRunner" +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__ + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let LatexRunner; +const Path = require("path"); +const Settings = require("settings-sharelatex"); +const logger = require("logger-sharelatex"); +const Metrics = require("./Metrics"); +const CommandRunner = require("./CommandRunner"); -ProcessTable = {} # table of currently running jobs (pids or docker container names) +const ProcessTable = {}; // table of currently running jobs (pids or docker container names) -module.exports = LatexRunner = - runLatex: (project_id, options, callback = (error) ->) -> - {directory, mainFile, compiler, timeout, image, environment, flags} = options - compiler ||= "pdflatex" - timeout ||= 60000 # milliseconds +module.exports = (LatexRunner = { + runLatex(project_id, options, callback) { + let command; + if (callback == null) { callback = function(error) {}; } + let {directory, mainFile, compiler, timeout, image, environment, flags} = options; + if (!compiler) { compiler = "pdflatex"; } + if (!timeout) { timeout = 60000; } // milliseconds - logger.log directory: directory, compiler: compiler, timeout: timeout, mainFile: mainFile, environment: environment, flags:flags, "starting compile" + logger.log({directory, compiler, timeout, mainFile, environment, flags}, "starting compile"); - # We want to run latexmk on the tex file which we will automatically - # generate from the Rtex/Rmd/md file. - mainFile = mainFile.replace(/\.(Rtex|md|Rmd)$/, ".tex") + // We want to run latexmk on the tex file which we will automatically + // generate from the Rtex/Rmd/md file. + mainFile = mainFile.replace(/\.(Rtex|md|Rmd)$/, ".tex"); - if compiler == "pdflatex" - command = LatexRunner._pdflatexCommand mainFile, flags - else if compiler == "latex" - command = LatexRunner._latexCommand mainFile, flags - else if compiler == "xelatex" - command = LatexRunner._xelatexCommand mainFile, flags - else if compiler == "lualatex" - command = LatexRunner._lualatexCommand mainFile, flags - else - return callback new Error("unknown compiler: #{compiler}") + if (compiler === "pdflatex") { + command = LatexRunner._pdflatexCommand(mainFile, flags); + } else if (compiler === "latex") { + command = LatexRunner._latexCommand(mainFile, flags); + } else if (compiler === "xelatex") { + command = LatexRunner._xelatexCommand(mainFile, flags); + } else if (compiler === "lualatex") { + command = LatexRunner._lualatexCommand(mainFile, flags); + } else { + return callback(new Error(`unknown compiler: ${compiler}`)); + } - if Settings.clsi?.strace - command = ["strace", "-o", "strace", "-ff"].concat(command) + if (Settings.clsi != null ? Settings.clsi.strace : undefined) { + command = ["strace", "-o", "strace", "-ff"].concat(command); + } - id = "#{project_id}" # record running project under this id + const id = `${project_id}`; // record running project under this id - ProcessTable[id] = CommandRunner.run project_id, command, directory, image, timeout, environment, (error, output) -> - delete ProcessTable[id] - return callback(error) if error? - runs = output?.stderr?.match(/^Run number \d+ of .*latex/mg)?.length or 0 - failed = if output?.stdout?.match(/^Latexmk: Errors/m)? then 1 else 0 - # counters from latexmk output - stats = {} - stats["latexmk-errors"] = failed - stats["latex-runs"] = runs - stats["latex-runs-with-errors"] = if failed then runs else 0 - stats["latex-runs-#{runs}"] = 1 - stats["latex-runs-with-errors-#{runs}"] = if failed then 1 else 0 - # timing information from /usr/bin/time - timings = {} - stderr = output?.stderr - timings["cpu-percent"] = stderr?.match(/Percent of CPU this job got: (\d+)/m)?[1] or 0 - timings["cpu-time"] = stderr?.match(/User time.*: (\d+.\d+)/m)?[1] or 0 - timings["sys-time"] = stderr?.match(/System time.*: (\d+.\d+)/m)?[1] or 0 - callback error, output, stats, timings + return ProcessTable[id] = CommandRunner.run(project_id, command, directory, image, timeout, environment, function(error, output) { + delete ProcessTable[id]; + if (error != null) { return callback(error); } + const runs = __guard__(__guard__(output != null ? output.stderr : undefined, x1 => x1.match(/^Run number \d+ of .*latex/mg)), x => x.length) || 0; + const failed = (__guard__(output != null ? output.stdout : undefined, x2 => x2.match(/^Latexmk: Errors/m)) != null) ? 1 : 0; + // counters from latexmk output + const stats = {}; + stats["latexmk-errors"] = failed; + stats["latex-runs"] = runs; + stats["latex-runs-with-errors"] = failed ? runs : 0; + stats[`latex-runs-${runs}`] = 1; + stats[`latex-runs-with-errors-${runs}`] = failed ? 1 : 0; + // timing information from /usr/bin/time + const timings = {}; + const stderr = output != null ? output.stderr : undefined; + timings["cpu-percent"] = __guard__(stderr != null ? stderr.match(/Percent of CPU this job got: (\d+)/m) : undefined, x3 => x3[1]) || 0; + timings["cpu-time"] = __guard__(stderr != null ? stderr.match(/User time.*: (\d+.\d+)/m) : undefined, x4 => x4[1]) || 0; + timings["sys-time"] = __guard__(stderr != null ? stderr.match(/System time.*: (\d+.\d+)/m) : undefined, x5 => x5[1]) || 0; + return callback(error, output, stats, timings); + }); + }, - killLatex: (project_id, callback = (error) ->) -> - id = "#{project_id}" - logger.log {id:id}, "killing running compile" - if not ProcessTable[id]? - logger.warn {id}, "no such project to kill" - return callback(null) - else - CommandRunner.kill ProcessTable[id], callback + killLatex(project_id, callback) { + if (callback == null) { callback = function(error) {}; } + const id = `${project_id}`; + logger.log({id}, "killing running compile"); + if ((ProcessTable[id] == null)) { + logger.warn({id}, "no such project to kill"); + return callback(null); + } else { + return CommandRunner.kill(ProcessTable[id], callback); + } + }, - _latexmkBaseCommand: (flags) -> - args = ["latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR", "-synctex=1","-interaction=batchmode"] - if flags - args = args.concat(flags) - (Settings?.clsi?.latexmkCommandPrefix || []).concat(args) + _latexmkBaseCommand(flags) { + let args = ["latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR", "-synctex=1","-interaction=batchmode"]; + if (flags) { + args = args.concat(flags); + } + return (__guard__(Settings != null ? Settings.clsi : undefined, x => x.latexmkCommandPrefix) || []).concat(args); + }, - _pdflatexCommand: (mainFile, flags) -> - LatexRunner._latexmkBaseCommand(flags).concat [ + _pdflatexCommand(mainFile, flags) { + return LatexRunner._latexmkBaseCommand(flags).concat([ "-pdf", Path.join("$COMPILE_DIR", mainFile) - ] + ]); + }, - _latexCommand: (mainFile, flags) -> - LatexRunner._latexmkBaseCommand(flags).concat [ + _latexCommand(mainFile, flags) { + return LatexRunner._latexmkBaseCommand(flags).concat([ "-pdfdvi", Path.join("$COMPILE_DIR", mainFile) - ] + ]); + }, - _xelatexCommand: (mainFile, flags) -> - LatexRunner._latexmkBaseCommand(flags).concat [ + _xelatexCommand(mainFile, flags) { + return LatexRunner._latexmkBaseCommand(flags).concat([ "-xelatex", Path.join("$COMPILE_DIR", mainFile) - ] + ]); + }, - _lualatexCommand: (mainFile, flags) -> - LatexRunner._latexmkBaseCommand(flags).concat [ + _lualatexCommand(mainFile, flags) { + return LatexRunner._latexmkBaseCommand(flags).concat([ "-lualatex", Path.join("$COMPILE_DIR", mainFile) - ] + ]); + } +}); + +function __guard__(value, transform) { + return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; +} \ No newline at end of file diff --git a/services/clsi/app/coffee/LocalCommandRunner.js b/services/clsi/app/coffee/LocalCommandRunner.js index c5ef3c692c..405c51bd74 100644 --- a/services/clsi/app/coffee/LocalCommandRunner.js +++ b/services/clsi/app/coffee/LocalCommandRunner.js @@ -1,48 +1,66 @@ -spawn = require("child_process").spawn -logger = require "logger-sharelatex" +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let CommandRunner; +const { spawn } = require("child_process"); +const logger = require("logger-sharelatex"); -logger.info "using standard command runner" +logger.info("using standard command runner"); -module.exports = CommandRunner = - run: (project_id, command, directory, image, timeout, environment, callback = (error) ->) -> - command = (arg.toString().replace('$COMPILE_DIR', directory) for arg in command) - logger.log project_id: project_id, command: command, directory: directory, "running command" - logger.warn "timeouts and sandboxing are not enabled with CommandRunner" +module.exports = (CommandRunner = { + run(project_id, command, directory, image, timeout, environment, callback) { + let key, value; + if (callback == null) { callback = function(error) {}; } + command = (Array.from(command).map((arg) => arg.toString().replace('$COMPILE_DIR', directory))); + logger.log({project_id, command, directory}, "running command"); + logger.warn("timeouts and sandboxing are not enabled with CommandRunner"); - # merge environment settings - env = {} - env[key] = value for key, value of process.env - env[key] = value for key, value of environment + // merge environment settings + const env = {}; + for (key in process.env) { value = process.env[key]; env[key] = value; } + for (key in environment) { value = environment[key]; env[key] = value; } - # run command as detached process so it has its own process group (which can be killed if needed) - proc = spawn command[0], command.slice(1), cwd: directory, env: env + // run command as detached process so it has its own process group (which can be killed if needed) + const proc = spawn(command[0], command.slice(1), {cwd: directory, env}); - stdout = "" - proc.stdout.on "data", (data)-> - stdout += data + let stdout = ""; + proc.stdout.on("data", data=> stdout += data); - proc.on "error", (err)-> - logger.err err:err, project_id:project_id, command: command, directory: directory, "error running command" - callback(err) + proc.on("error", function(err){ + logger.err({err, project_id, command, directory}, "error running command"); + return callback(err); + }); - proc.on "close", (code, signal) -> - logger.info code:code, signal:signal, project_id:project_id, "command exited" - if signal is 'SIGTERM' # signal from kill method below - err = new Error("terminated") - err.terminated = true - return callback(err) - else if code is 1 # exit status from chktex - err = new Error("exited") - err.code = code - return callback(err) - else - callback(null, {"stdout": stdout}) + proc.on("close", function(code, signal) { + let err; + logger.info({code, signal, project_id}, "command exited"); + if (signal === 'SIGTERM') { // signal from kill method below + err = new Error("terminated"); + err.terminated = true; + return callback(err); + } else if (code === 1) { // exit status from chktex + err = new Error("exited"); + err.code = code; + return callback(err); + } else { + return callback(null, {"stdout": stdout}); + } + }); - return proc.pid # return process id to allow job to be killed if necessary + return proc.pid; + }, // return process id to allow job to be killed if necessary - kill: (pid, callback = (error) ->) -> - try - process.kill -pid # kill all processes in group - catch err - return callback(err) - callback() + kill(pid, callback) { + if (callback == null) { callback = function(error) {}; } + try { + process.kill(-pid); // kill all processes in group + } catch (err) { + return callback(err); + } + return callback(); + } +}); diff --git a/services/clsi/app/coffee/LockManager.js b/services/clsi/app/coffee/LockManager.js index 5d9fe26a06..2405e8ac88 100644 --- a/services/clsi/app/coffee/LockManager.js +++ b/services/clsi/app/coffee/LockManager.js @@ -1,31 +1,50 @@ -Settings = require('settings-sharelatex') -logger = require "logger-sharelatex" -Lockfile = require('lockfile') # from https://github.com/npm/lockfile -Errors = require "./Errors" -fs = require("fs") -Path = require("path") -module.exports = LockManager = - LOCK_TEST_INTERVAL: 1000 # 50ms between each test of the lock - MAX_LOCK_WAIT_TIME: 15000 # 10s maximum time to spend trying to get the lock - LOCK_STALE: 5*60*1000 # 5 mins time until lock auto expires +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let LockManager; +const Settings = require('settings-sharelatex'); +const logger = require("logger-sharelatex"); +const Lockfile = require('lockfile'); // from https://github.com/npm/lockfile +const Errors = require("./Errors"); +const fs = require("fs"); +const Path = require("path"); +module.exports = (LockManager = { + LOCK_TEST_INTERVAL: 1000, // 50ms between each test of the lock + MAX_LOCK_WAIT_TIME: 15000, // 10s maximum time to spend trying to get the lock + LOCK_STALE: 5*60*1000, // 5 mins time until lock auto expires - runWithLock: (path, runner, callback = ((error) ->)) -> - lockOpts = - wait: @MAX_LOCK_WAIT_TIME - pollPeriod: @LOCK_TEST_INTERVAL - stale: @LOCK_STALE - Lockfile.lock path, lockOpts, (error) -> - if error?.code is 'EEXIST' - return callback new Errors.AlreadyCompilingError("compile in progress") - else if error? - fs.lstat path, (statLockErr, statLock)-> - fs.lstat Path.dirname(path), (statDirErr, statDir)-> - fs.readdir Path.dirname(path), (readdirErr, readdirDir)-> - logger.err error:error, path:path, statLock:statLock, statLockErr:statLockErr, statDir:statDir, statDirErr: statDirErr, readdirErr:readdirErr, readdirDir:readdirDir, "unable to get lock" - return callback(error) - else - runner (error1, args...) -> - Lockfile.unlock path, (error2) -> - error = error1 or error2 - return callback(error) if error? - callback(null, args...) + runWithLock(path, runner, callback) { + if (callback == null) { callback = function(error) {}; } + const lockOpts = { + wait: this.MAX_LOCK_WAIT_TIME, + pollPeriod: this.LOCK_TEST_INTERVAL, + stale: this.LOCK_STALE + }; + return Lockfile.lock(path, lockOpts, function(error) { + if ((error != null ? error.code : undefined) === 'EEXIST') { + return callback(new Errors.AlreadyCompilingError("compile in progress")); + } else if (error != null) { + return fs.lstat(path, (statLockErr, statLock)=> + fs.lstat(Path.dirname(path), (statDirErr, statDir)=> + fs.readdir(Path.dirname(path), function(readdirErr, readdirDir){ + logger.err({error, path, statLock, statLockErr, statDir, statDirErr, readdirErr, readdirDir}, "unable to get lock"); + return callback(error); + }) + ) + ); + } else { + return runner((error1, ...args) => + Lockfile.unlock(path, function(error2) { + error = error1 || error2; + if (error != null) { return callback(error); } + return callback(null, ...Array.from(args)); + }) + ); + } + }); + } +}); diff --git a/services/clsi/app/coffee/Metrics.js b/services/clsi/app/coffee/Metrics.js index 9965b252e4..8148d6641c 100644 --- a/services/clsi/app/coffee/Metrics.js +++ b/services/clsi/app/coffee/Metrics.js @@ -1,2 +1,2 @@ -module.exports = require "metrics-sharelatex" +module.exports = require("metrics-sharelatex"); diff --git a/services/clsi/app/coffee/OutputCacheManager.js b/services/clsi/app/coffee/OutputCacheManager.js index 5ef92ec602..6d03a10634 100644 --- a/services/clsi/app/coffee/OutputCacheManager.js +++ b/services/clsi/app/coffee/OutputCacheManager.js @@ -1,199 +1,270 @@ -async = require "async" -fs = require "fs" -fse = require "fs-extra" -Path = require "path" -logger = require "logger-sharelatex" -_ = require "underscore" -Settings = require "settings-sharelatex" -crypto = require "crypto" +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__ + * DS104: Avoid inline assignments + * DS204: Change includes calls to have a more natural evaluation order + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let OutputCacheManager; +const async = require("async"); +const fs = require("fs"); +const fse = require("fs-extra"); +const Path = require("path"); +const logger = require("logger-sharelatex"); +const _ = require("underscore"); +const Settings = require("settings-sharelatex"); +const crypto = require("crypto"); -OutputFileOptimiser = require "./OutputFileOptimiser" +const OutputFileOptimiser = require("./OutputFileOptimiser"); -module.exports = OutputCacheManager = - CACHE_SUBDIR: '.cache/clsi' - ARCHIVE_SUBDIR: '.archive/clsi' - # build id is HEXDATE-HEXRANDOM from Date.now()and RandomBytes - # for backwards compatibility, make the randombytes part optional - BUILD_REGEX: /^[0-9a-f]+(-[0-9a-f]+)?$/ - CACHE_LIMIT: 2 # maximum number of cache directories - CACHE_AGE: 60*60*1000 # up to one hour old +module.exports = (OutputCacheManager = { + CACHE_SUBDIR: '.cache/clsi', + ARCHIVE_SUBDIR: '.archive/clsi', + // build id is HEXDATE-HEXRANDOM from Date.now()and RandomBytes + // for backwards compatibility, make the randombytes part optional + BUILD_REGEX: /^[0-9a-f]+(-[0-9a-f]+)?$/, + CACHE_LIMIT: 2, // maximum number of cache directories + CACHE_AGE: 60*60*1000, // up to one hour old - path: (buildId, file) -> - # used by static server, given build id return '.cache/clsi/buildId' - if buildId.match OutputCacheManager.BUILD_REGEX - return Path.join(OutputCacheManager.CACHE_SUBDIR, buildId, file) - else - # for invalid build id, return top level - return file + path(buildId, file) { + // used by static server, given build id return '.cache/clsi/buildId' + if (buildId.match(OutputCacheManager.BUILD_REGEX)) { + return Path.join(OutputCacheManager.CACHE_SUBDIR, buildId, file); + } else { + // for invalid build id, return top level + return file; + } + }, - generateBuildId: (callback = (error, buildId) ->) -> - # generate a secure build id from Date.now() and 8 random bytes in hex - crypto.randomBytes 8, (err, buf) -> - return callback(err) if err? - random = buf.toString('hex') - date = Date.now().toString(16) - callback err, "#{date}-#{random}" + generateBuildId(callback) { + // generate a secure build id from Date.now() and 8 random bytes in hex + if (callback == null) { callback = function(error, buildId) {}; } + return crypto.randomBytes(8, function(err, buf) { + if (err != null) { return callback(err); } + const random = buf.toString('hex'); + const date = Date.now().toString(16); + return callback(err, `${date}-${random}`); + }); + }, - saveOutputFiles: (outputFiles, compileDir, callback = (error) ->) -> - OutputCacheManager.generateBuildId (err, buildId) -> - return callback(err) if err? - OutputCacheManager.saveOutputFilesInBuildDir outputFiles, compileDir, buildId, callback + saveOutputFiles(outputFiles, compileDir, callback) { + if (callback == null) { callback = function(error) {}; } + return OutputCacheManager.generateBuildId(function(err, buildId) { + if (err != null) { return callback(err); } + return OutputCacheManager.saveOutputFilesInBuildDir(outputFiles, compileDir, buildId, callback); + }); + }, - saveOutputFilesInBuildDir: (outputFiles, compileDir, buildId, callback = (error) ->) -> - # make a compileDir/CACHE_SUBDIR/build_id directory and - # copy all the output files into it - cacheRoot = Path.join(compileDir, OutputCacheManager.CACHE_SUBDIR) - # Put the files into a new cache subdirectory - cacheDir = Path.join(compileDir, OutputCacheManager.CACHE_SUBDIR, buildId) - # Is it a per-user compile? check if compile directory is PROJECTID-USERID - perUser = Path.basename(compileDir).match(/^[0-9a-f]{24}-[0-9a-f]{24}$/) + saveOutputFilesInBuildDir(outputFiles, compileDir, buildId, callback) { + // make a compileDir/CACHE_SUBDIR/build_id directory and + // copy all the output files into it + if (callback == null) { callback = function(error) {}; } + const cacheRoot = Path.join(compileDir, OutputCacheManager.CACHE_SUBDIR); + // Put the files into a new cache subdirectory + const cacheDir = Path.join(compileDir, OutputCacheManager.CACHE_SUBDIR, buildId); + // Is it a per-user compile? check if compile directory is PROJECTID-USERID + const perUser = Path.basename(compileDir).match(/^[0-9a-f]{24}-[0-9a-f]{24}$/); - # Archive logs in background - if Settings.clsi?.archive_logs or Settings.clsi?.strace - OutputCacheManager.archiveLogs outputFiles, compileDir, buildId, (err) -> - if err? - logger.warn err:err, "erroring archiving log files" + // Archive logs in background + if ((Settings.clsi != null ? Settings.clsi.archive_logs : undefined) || (Settings.clsi != null ? Settings.clsi.strace : undefined)) { + OutputCacheManager.archiveLogs(outputFiles, compileDir, buildId, function(err) { + if (err != null) { + return logger.warn({err}, "erroring archiving log files"); + } + }); + } - # make the new cache directory - fse.ensureDir cacheDir, (err) -> - if err? - logger.error err: err, directory: cacheDir, "error creating cache directory" - callback(err, outputFiles) - else - # copy all the output files into the new cache directory - results = [] - async.mapSeries outputFiles, (file, cb) -> - # don't send dot files as output, express doesn't serve them - if OutputCacheManager._fileIsHidden(file.path) - logger.debug compileDir: compileDir, path: file.path, "ignoring dotfile in output" - return cb() - # copy other files into cache directory if valid - newFile = _.clone(file) - [src, dst] = [Path.join(compileDir, file.path), Path.join(cacheDir, file.path)] - OutputCacheManager._checkFileIsSafe src, (err, isSafe) -> - return cb(err) if err? - if !isSafe - return cb() - OutputCacheManager._checkIfShouldCopy src, (err, shouldCopy) -> - return cb(err) if err? - if !shouldCopy - return cb() - OutputCacheManager._copyFile src, dst, (err) -> - return cb(err) if err? - newFile.build = buildId # attach a build id if we cached the file - results.push newFile - cb() - , (err) -> - if err? - # pass back the original files if we encountered *any* error - callback(err, outputFiles) - # clean up the directory we just created - fse.remove cacheDir, (err) -> - if err? - logger.error err: err, dir: cacheDir, "error removing cache dir after failure" - else - # pass back the list of new files in the cache - callback(err, results) - # let file expiry run in the background, expire all previous files if per-user - OutputCacheManager.expireOutputFiles cacheRoot, {keep: buildId, limit: if perUser then 1 else null} + // make the new cache directory + return fse.ensureDir(cacheDir, function(err) { + if (err != null) { + logger.error({err, directory: cacheDir}, "error creating cache directory"); + return callback(err, outputFiles); + } else { + // copy all the output files into the new cache directory + const results = []; + return async.mapSeries(outputFiles, function(file, cb) { + // don't send dot files as output, express doesn't serve them + if (OutputCacheManager._fileIsHidden(file.path)) { + logger.debug({compileDir, path: file.path}, "ignoring dotfile in output"); + return cb(); + } + // copy other files into cache directory if valid + const newFile = _.clone(file); + const [src, dst] = Array.from([Path.join(compileDir, file.path), Path.join(cacheDir, file.path)]); + return OutputCacheManager._checkFileIsSafe(src, function(err, isSafe) { + if (err != null) { return cb(err); } + if (!isSafe) { + return cb(); + } + return OutputCacheManager._checkIfShouldCopy(src, function(err, shouldCopy) { + if (err != null) { return cb(err); } + if (!shouldCopy) { + return cb(); + } + return OutputCacheManager._copyFile(src, dst, function(err) { + if (err != null) { return cb(err); } + newFile.build = buildId; // attach a build id if we cached the file + results.push(newFile); + return cb(); + }); + }); + }); + } + , function(err) { + if (err != null) { + // pass back the original files if we encountered *any* error + callback(err, outputFiles); + // clean up the directory we just created + return fse.remove(cacheDir, function(err) { + if (err != null) { + return logger.error({err, dir: cacheDir}, "error removing cache dir after failure"); + } + }); + } else { + // pass back the list of new files in the cache + callback(err, results); + // let file expiry run in the background, expire all previous files if per-user + return OutputCacheManager.expireOutputFiles(cacheRoot, {keep: buildId, limit: perUser ? 1 : null}); + } + }); + } + }); + }, - archiveLogs: (outputFiles, compileDir, buildId, callback = (error) ->) -> - archiveDir = Path.join(compileDir, OutputCacheManager.ARCHIVE_SUBDIR, buildId) - logger.log {dir: archiveDir}, "archiving log files for project" - fse.ensureDir archiveDir, (err) -> - return callback(err) if err? - async.mapSeries outputFiles, (file, cb) -> - [src, dst] = [Path.join(compileDir, file.path), Path.join(archiveDir, file.path)] - OutputCacheManager._checkFileIsSafe src, (err, isSafe) -> - return cb(err) if err? - return cb() if !isSafe - OutputCacheManager._checkIfShouldArchive src, (err, shouldArchive) -> - return cb(err) if err? - return cb() if !shouldArchive - OutputCacheManager._copyFile src, dst, cb - , callback + archiveLogs(outputFiles, compileDir, buildId, callback) { + if (callback == null) { callback = function(error) {}; } + const archiveDir = Path.join(compileDir, OutputCacheManager.ARCHIVE_SUBDIR, buildId); + logger.log({dir: archiveDir}, "archiving log files for project"); + return fse.ensureDir(archiveDir, function(err) { + if (err != null) { return callback(err); } + return async.mapSeries(outputFiles, function(file, cb) { + const [src, dst] = Array.from([Path.join(compileDir, file.path), Path.join(archiveDir, file.path)]); + return OutputCacheManager._checkFileIsSafe(src, function(err, isSafe) { + if (err != null) { return cb(err); } + if (!isSafe) { return cb(); } + return OutputCacheManager._checkIfShouldArchive(src, function(err, shouldArchive) { + if (err != null) { return cb(err); } + if (!shouldArchive) { return cb(); } + return OutputCacheManager._copyFile(src, dst, cb); + }); + }); + } + , callback); + }); + }, - expireOutputFiles: (cacheRoot, options, callback = (error) ->) -> - # look in compileDir for build dirs and delete if > N or age of mod time > T - fs.readdir cacheRoot, (err, results) -> - if err? - return callback(null) if err.code == 'ENOENT' # cache directory is empty - logger.error err: err, project_id: cacheRoot, "error clearing cache" - return callback(err) + expireOutputFiles(cacheRoot, options, callback) { + // look in compileDir for build dirs and delete if > N or age of mod time > T + if (callback == null) { callback = function(error) {}; } + return fs.readdir(cacheRoot, function(err, results) { + if (err != null) { + if (err.code === 'ENOENT') { return callback(null); } // cache directory is empty + logger.error({err, project_id: cacheRoot}, "error clearing cache"); + return callback(err); + } - dirs = results.sort().reverse() - currentTime = Date.now() + const dirs = results.sort().reverse(); + const currentTime = Date.now(); - isExpired = (dir, index) -> - return false if options?.keep == dir - # remove any directories over the requested (non-null) limit - return true if options?.limit? and index > options.limit - # remove any directories over the hard limit - return true if index > OutputCacheManager.CACHE_LIMIT - # we can get the build time from the first part of the directory name DDDD-RRRR - # DDDD is date and RRRR is random bytes - dirTime = parseInt(dir.split('-')?[0], 16) - age = currentTime - dirTime - return age > OutputCacheManager.CACHE_AGE + const isExpired = function(dir, index) { + if ((options != null ? options.keep : undefined) === dir) { return false; } + // remove any directories over the requested (non-null) limit + if (((options != null ? options.limit : undefined) != null) && (index > options.limit)) { return true; } + // remove any directories over the hard limit + if (index > OutputCacheManager.CACHE_LIMIT) { return true; } + // we can get the build time from the first part of the directory name DDDD-RRRR + // DDDD is date and RRRR is random bytes + const dirTime = parseInt(__guard__(dir.split('-'), x => x[0]), 16); + const age = currentTime - dirTime; + return age > OutputCacheManager.CACHE_AGE; + }; - toRemove = _.filter(dirs, isExpired) + const toRemove = _.filter(dirs, isExpired); - removeDir = (dir, cb) -> - fse.remove Path.join(cacheRoot, dir), (err, result) -> - logger.log cache: cacheRoot, dir: dir, "removed expired cache dir" - if err? - logger.error err: err, dir: dir, "cache remove error" - cb(err, result) + const removeDir = (dir, cb) => + fse.remove(Path.join(cacheRoot, dir), function(err, result) { + logger.log({cache: cacheRoot, dir}, "removed expired cache dir"); + if (err != null) { + logger.error({err, dir}, "cache remove error"); + } + return cb(err, result); + }) + ; - async.eachSeries toRemove, (dir, cb) -> - removeDir dir, cb - , callback + return async.eachSeries(toRemove, (dir, cb) => removeDir(dir, cb) + , callback); + }); + }, - _fileIsHidden: (path) -> - return path?.match(/^\.|\/\./)? + _fileIsHidden(path) { + return ((path != null ? path.match(/^\.|\/\./) : undefined) != null); + }, - _checkFileIsSafe: (src, callback = (error, isSafe) ->) -> - # check if we have a valid file to copy into the cache - fs.stat src, (err, stats) -> - if err?.code is 'ENOENT' - logger.warn err: err, file: src, "file has disappeared before copying to build cache" - callback(err, false) - else if err? - # some other problem reading the file - logger.error err: err, file: src, "stat error for file in cache" - callback(err, false) - else if not stats.isFile() - # other filetype - reject it - logger.warn src: src, stat: stats, "nonfile output - refusing to copy to cache" - callback(null, false) - else - # it's a plain file, ok to copy - callback(null, true) + _checkFileIsSafe(src, callback) { + // check if we have a valid file to copy into the cache + if (callback == null) { callback = function(error, isSafe) {}; } + return fs.stat(src, function(err, stats) { + if ((err != null ? err.code : undefined) === 'ENOENT') { + logger.warn({err, file: src}, "file has disappeared before copying to build cache"); + return callback(err, false); + } else if (err != null) { + // some other problem reading the file + logger.error({err, file: src}, "stat error for file in cache"); + return callback(err, false); + } else if (!stats.isFile()) { + // other filetype - reject it + logger.warn({src, stat: stats}, "nonfile output - refusing to copy to cache"); + return callback(null, false); + } else { + // it's a plain file, ok to copy + return callback(null, true); + } + }); + }, - _copyFile: (src, dst, callback) -> - # copy output file into the cache - fse.copy src, dst, (err) -> - if err?.code is 'ENOENT' - logger.warn err: err, file: src, "file has disappeared when copying to build cache" - callback(err, false) - else if err? - logger.error err: err, src: src, dst: dst, "copy error for file in cache" - callback(err) - else - if Settings.clsi?.optimiseInDocker - # don't run any optimisations on the pdf when they are done - # in the docker container - callback() - else - # call the optimiser for the file too - OutputFileOptimiser.optimiseFile src, dst, callback + _copyFile(src, dst, callback) { + // copy output file into the cache + return fse.copy(src, dst, function(err) { + if ((err != null ? err.code : undefined) === 'ENOENT') { + logger.warn({err, file: src}, "file has disappeared when copying to build cache"); + return callback(err, false); + } else if (err != null) { + logger.error({err, src, dst}, "copy error for file in cache"); + return callback(err); + } else { + if ((Settings.clsi != null ? Settings.clsi.optimiseInDocker : undefined)) { + // don't run any optimisations on the pdf when they are done + // in the docker container + return callback(); + } else { + // call the optimiser for the file too + return OutputFileOptimiser.optimiseFile(src, dst, callback); + } + } + }); + }, - _checkIfShouldCopy: (src, callback = (err, shouldCopy) ->) -> - return callback(null, !Path.basename(src).match(/^strace/)) + _checkIfShouldCopy(src, callback) { + if (callback == null) { callback = function(err, shouldCopy) {}; } + return callback(null, !Path.basename(src).match(/^strace/)); + }, - _checkIfShouldArchive: (src, callback = (err, shouldCopy) ->) -> - if Path.basename(src).match(/^strace/) - return callback(null, true) - if Settings.clsi?.archive_logs and Path.basename(src) in ["output.log", "output.blg"] - return callback(null, true) - return callback(null, false) + _checkIfShouldArchive(src, callback) { + let needle; + if (callback == null) { callback = function(err, shouldCopy) {}; } + if (Path.basename(src).match(/^strace/)) { + return callback(null, true); + } + if ((Settings.clsi != null ? Settings.clsi.archive_logs : undefined) && (needle = Path.basename(src), ["output.log", "output.blg"].includes(needle))) { + return callback(null, true); + } + return callback(null, false); + } +}); + +function __guard__(value, transform) { + return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; +} \ No newline at end of file diff --git a/services/clsi/app/coffee/OutputFileFinder.js b/services/clsi/app/coffee/OutputFileFinder.js index 662440b576..f0f837c00c 100644 --- a/services/clsi/app/coffee/OutputFileFinder.js +++ b/services/clsi/app/coffee/OutputFileFinder.js @@ -1,50 +1,78 @@ -async = require "async" -fs = require "fs" -Path = require "path" -spawn = require("child_process").spawn -logger = require "logger-sharelatex" +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__ + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let OutputFileFinder; +const async = require("async"); +const fs = require("fs"); +const Path = require("path"); +const { spawn } = require("child_process"); +const logger = require("logger-sharelatex"); -module.exports = OutputFileFinder = - findOutputFiles: (resources, directory, callback = (error, outputFiles, allFiles) ->) -> - incomingResources = {} - for resource in resources - incomingResources[resource.path] = true +module.exports = (OutputFileFinder = { + findOutputFiles(resources, directory, callback) { + if (callback == null) { callback = function(error, outputFiles, allFiles) {}; } + const incomingResources = {}; + for (let resource of Array.from(resources)) { + incomingResources[resource.path] = true; + } - OutputFileFinder._getAllFiles directory, (error, allFiles = []) -> - if error? - logger.err err:error, "error finding all output files" - return callback(error) - outputFiles = [] - for file in allFiles - if !incomingResources[file] - outputFiles.push { - path: file - type: file.match(/\.([^\.]+)$/)?[1] - } - callback null, outputFiles, allFiles + return OutputFileFinder._getAllFiles(directory, function(error, allFiles) { + if (allFiles == null) { allFiles = []; } + if (error != null) { + logger.err({err:error}, "error finding all output files"); + return callback(error); + } + const outputFiles = []; + for (let file of Array.from(allFiles)) { + if (!incomingResources[file]) { + outputFiles.push({ + path: file, + type: __guard__(file.match(/\.([^\.]+)$/), x => x[1]) + }); + } + } + return callback(null, outputFiles, allFiles); + }); + }, - _getAllFiles: (directory, _callback = (error, fileList) ->) -> - callback = (error, fileList) -> - _callback(error, fileList) - _callback = () -> + _getAllFiles(directory, _callback) { + if (_callback == null) { _callback = function(error, fileList) {}; } + const callback = function(error, fileList) { + _callback(error, fileList); + return _callback = function() {}; + }; - # don't include clsi-specific files/directories in the output list - EXCLUDE_DIRS = ["-name", ".cache", "-o", "-name", ".archive","-o", "-name", ".project-*"] - args = [directory, "(", EXCLUDE_DIRS..., ")", "-prune", "-o", "-type", "f", "-print"] - logger.log args: args, "running find command" + // don't include clsi-specific files/directories in the output list + const EXCLUDE_DIRS = ["-name", ".cache", "-o", "-name", ".archive","-o", "-name", ".project-*"]; + const args = [directory, "(", ...Array.from(EXCLUDE_DIRS), ")", "-prune", "-o", "-type", "f", "-print"]; + logger.log({args}, "running find command"); - proc = spawn("find", args) - stdout = "" - proc.stdout.on "data", (chunk) -> - stdout += chunk.toString() - proc.on "error", callback - proc.on "close", (code) -> - if code != 0 - logger.warn {directory, code}, "find returned error, directory likely doesn't exist" - return callback null, [] - fileList = stdout.trim().split("\n") - fileList = fileList.map (file) -> - # Strip leading directory - path = Path.relative(directory, file) - return callback null, fileList + const proc = spawn("find", args); + let stdout = ""; + proc.stdout.on("data", chunk => stdout += chunk.toString()); + proc.on("error", callback); + return proc.on("close", function(code) { + if (code !== 0) { + logger.warn({directory, code}, "find returned error, directory likely doesn't exist"); + return callback(null, []); + } + let fileList = stdout.trim().split("\n"); + fileList = fileList.map(function(file) { + // Strip leading directory + let path; + return path = Path.relative(directory, file); + }); + return callback(null, fileList); + }); + } +}); + +function __guard__(value, transform) { + return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; +} \ No newline at end of file diff --git a/services/clsi/app/coffee/OutputFileOptimiser.js b/services/clsi/app/coffee/OutputFileOptimiser.js index b702f36f8b..f8302aac5f 100644 --- a/services/clsi/app/coffee/OutputFileOptimiser.js +++ b/services/clsi/app/coffee/OutputFileOptimiser.js @@ -1,55 +1,77 @@ -fs = require "fs" -Path = require "path" -spawn = require("child_process").spawn -logger = require "logger-sharelatex" -Metrics = require "./Metrics" -_ = require "underscore" +/* + * 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 OutputFileOptimiser; +const fs = require("fs"); +const Path = require("path"); +const { spawn } = require("child_process"); +const logger = require("logger-sharelatex"); +const Metrics = require("./Metrics"); +const _ = require("underscore"); -module.exports = OutputFileOptimiser = +module.exports = (OutputFileOptimiser = { - optimiseFile: (src, dst, callback = (error) ->) -> - # check output file (src) and see if we can optimise it, storing - # the result in the build directory (dst) - if src.match(/\/output\.pdf$/) - OutputFileOptimiser.checkIfPDFIsOptimised src, (err, isOptimised) -> - return callback(null) if err? or isOptimised - OutputFileOptimiser.optimisePDF src, dst, callback - else - callback (null) + optimiseFile(src, dst, callback) { + // check output file (src) and see if we can optimise it, storing + // the result in the build directory (dst) + if (callback == null) { callback = function(error) {}; } + if (src.match(/\/output\.pdf$/)) { + return OutputFileOptimiser.checkIfPDFIsOptimised(src, function(err, isOptimised) { + if ((err != null) || isOptimised) { return callback(null); } + return OutputFileOptimiser.optimisePDF(src, dst, callback); + }); + } else { + return callback((null)); + } + }, - checkIfPDFIsOptimised: (file, callback) -> - SIZE = 16*1024 # check the header of the pdf - result = new Buffer(SIZE) - result.fill(0) # prevent leakage of uninitialised buffer - fs.open file, "r", (err, fd) -> - return callback(err) if err? - fs.read fd, result, 0, SIZE, 0, (errRead, bytesRead, buffer) -> - fs.close fd, (errClose) -> - return callback(errRead) if errRead? - return callback(errClose) if errReadClose? - isOptimised = buffer.toString('ascii').indexOf("/Linearized 1") >= 0 - callback(null, isOptimised) + checkIfPDFIsOptimised(file, callback) { + const SIZE = 16*1024; // check the header of the pdf + const result = new Buffer(SIZE); + result.fill(0); // prevent leakage of uninitialised buffer + return fs.open(file, "r", function(err, fd) { + if (err != null) { return callback(err); } + return fs.read(fd, result, 0, SIZE, 0, (errRead, bytesRead, buffer) => + fs.close(fd, function(errClose) { + if (errRead != null) { return callback(errRead); } + if (typeof errReadClose !== 'undefined' && errReadClose !== null) { return callback(errClose); } + const isOptimised = buffer.toString('ascii').indexOf("/Linearized 1") >= 0; + return callback(null, isOptimised); + }) + ); + }); + }, - optimisePDF: (src, dst, callback = (error) ->) -> - tmpOutput = dst + '.opt' - args = ["--linearize", src, tmpOutput] - logger.log args: args, "running qpdf command" + optimisePDF(src, dst, callback) { + if (callback == null) { callback = function(error) {}; } + const tmpOutput = dst + '.opt'; + const args = ["--linearize", src, tmpOutput]; + logger.log({args}, "running qpdf command"); - timer = new Metrics.Timer("qpdf") - proc = spawn("qpdf", args) - stdout = "" - proc.stdout.on "data", (chunk) -> - stdout += chunk.toString() - callback = _.once(callback) # avoid double call back for error and close event - proc.on "error", (err) -> - logger.warn {err, args}, "qpdf failed" - callback(null) # ignore the error - proc.on "close", (code) -> - timer.done() - if code != 0 - logger.warn {code, args}, "qpdf returned error" - return callback(null) # ignore the error - fs.rename tmpOutput, dst, (err) -> - if err? - logger.warn {tmpOutput, dst}, "failed to rename output of qpdf command" - callback(null) # ignore the error + const timer = new Metrics.Timer("qpdf"); + const proc = spawn("qpdf", args); + let stdout = ""; + proc.stdout.on("data", chunk => stdout += chunk.toString()); + callback = _.once(callback); // avoid double call back for error and close event + proc.on("error", function(err) { + logger.warn({err, args}, "qpdf failed"); + return callback(null); + }); // ignore the error + return proc.on("close", function(code) { + timer.done(); + if (code !== 0) { + logger.warn({code, args}, "qpdf returned error"); + return callback(null); // ignore the error + } + return fs.rename(tmpOutput, dst, function(err) { + if (err != null) { + logger.warn({tmpOutput, dst}, "failed to rename output of qpdf command"); + } + return callback(null); + }); + }); + } // ignore the error +}); diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.js b/services/clsi/app/coffee/ProjectPersistenceManager.js index 4ea02bf728..7b3d5ee242 100644 --- a/services/clsi/app/coffee/ProjectPersistenceManager.js +++ b/services/clsi/app/coffee/ProjectPersistenceManager.js @@ -1,84 +1,117 @@ -UrlCache = require "./UrlCache" -CompileManager = require "./CompileManager" -db = require "./db" -dbQueue = require "./DbQueue" -async = require "async" -logger = require "logger-sharelatex" -oneDay = 24 * 60 * 60 * 1000 -Settings = require "settings-sharelatex" +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let ProjectPersistenceManager; +const UrlCache = require("./UrlCache"); +const CompileManager = require("./CompileManager"); +const db = require("./db"); +const dbQueue = require("./DbQueue"); +const async = require("async"); +const logger = require("logger-sharelatex"); +const oneDay = 24 * 60 * 60 * 1000; +const Settings = require("settings-sharelatex"); -module.exports = ProjectPersistenceManager = +module.exports = (ProjectPersistenceManager = { - EXPIRY_TIMEOUT: Settings.project_cache_length_ms || oneDay * 2.5 + EXPIRY_TIMEOUT: Settings.project_cache_length_ms || (oneDay * 2.5), - markProjectAsJustAccessed: (project_id, callback = (error) ->) -> - job = (cb)-> - db.Project.findOrCreate(where: {project_id: project_id}) + markProjectAsJustAccessed(project_id, callback) { + if (callback == null) { callback = function(error) {}; } + const job = cb=> + db.Project.findOrCreate({where: {project_id}}) .spread( - (project, created) -> - project.updateAttributes(lastAccessed: new Date()) - .then(() -> cb()) - .error cb + (project, created) => + project.updateAttributes({lastAccessed: new Date()}) + .then(() => cb()) + .error(cb) ) - .error cb - dbQueue.queue.push(job, callback) + .error(cb) + ; + return dbQueue.queue.push(job, callback); + }, - clearExpiredProjects: (callback = (error) ->) -> - ProjectPersistenceManager._findExpiredProjectIds (error, project_ids) -> - return callback(error) if error? - logger.log project_ids: project_ids, "clearing expired projects" - jobs = for project_id in (project_ids or []) - do (project_id) -> - (callback) -> - ProjectPersistenceManager.clearProjectFromCache project_id, (err) -> - if err? - logger.error err: err, project_id: project_id, "error clearing project" - callback() - async.series jobs, (error) -> - return callback(error) if error? - CompileManager.clearExpiredProjects ProjectPersistenceManager.EXPIRY_TIMEOUT, (error) -> - callback() # ignore any errors from deleting directories + clearExpiredProjects(callback) { + if (callback == null) { callback = function(error) {}; } + return ProjectPersistenceManager._findExpiredProjectIds(function(error, project_ids) { + if (error != null) { return callback(error); } + logger.log({project_ids}, "clearing expired projects"); + const jobs = (Array.from(project_ids || [])).map((project_id) => + (project_id => + callback => + ProjectPersistenceManager.clearProjectFromCache(project_id, function(err) { + if (err != null) { + logger.error({err, project_id}, "error clearing project"); + } + return callback(); + }) + + )(project_id)); + return async.series(jobs, function(error) { + if (error != null) { return callback(error); } + return CompileManager.clearExpiredProjects(ProjectPersistenceManager.EXPIRY_TIMEOUT, error => callback()); + }); + }); + }, // ignore any errors from deleting directories - clearProject: (project_id, user_id, callback = (error) ->) -> - logger.log project_id: project_id, user_id:user_id, "clearing project for user" - CompileManager.clearProject project_id, user_id, (error) -> - return callback(error) if error? - ProjectPersistenceManager.clearProjectFromCache project_id, (error) -> - return callback(error) if error? - callback() + clearProject(project_id, user_id, callback) { + if (callback == null) { callback = function(error) {}; } + logger.log({project_id, user_id}, "clearing project for user"); + return CompileManager.clearProject(project_id, user_id, function(error) { + if (error != null) { return callback(error); } + return ProjectPersistenceManager.clearProjectFromCache(project_id, function(error) { + if (error != null) { return callback(error); } + return callback(); + }); + }); + }, - clearProjectFromCache: (project_id, callback = (error) ->) -> - logger.log project_id: project_id, "clearing project from cache" - UrlCache.clearProject project_id, (error) -> - if error? - logger.err error:error, project_id: project_id, "error clearing project from cache" - return callback(error) - ProjectPersistenceManager._clearProjectFromDatabase project_id, (error) -> - if error? - logger.err error:error, project_id:project_id, "error clearing project from database" - callback(error) + clearProjectFromCache(project_id, callback) { + if (callback == null) { callback = function(error) {}; } + logger.log({project_id}, "clearing project from cache"); + return UrlCache.clearProject(project_id, function(error) { + if (error != null) { + logger.err({error, project_id}, "error clearing project from cache"); + return callback(error); + } + return ProjectPersistenceManager._clearProjectFromDatabase(project_id, function(error) { + if (error != null) { + logger.err({error, project_id}, "error clearing project from database"); + } + return callback(error); + }); + }); + }, - _clearProjectFromDatabase: (project_id, callback = (error) ->) -> - logger.log project_id:project_id, "clearing project from database" - job = (cb)-> - db.Project.destroy(where: {project_id: project_id}) - .then(() -> cb()) - .error cb - dbQueue.queue.push(job, callback) + _clearProjectFromDatabase(project_id, callback) { + if (callback == null) { callback = function(error) {}; } + logger.log({project_id}, "clearing project from database"); + const job = cb=> + db.Project.destroy({where: {project_id}}) + .then(() => cb()) + .error(cb) + ; + return dbQueue.queue.push(job, callback); + }, - _findExpiredProjectIds: (callback = (error, project_ids) ->) -> - job = (cb)-> - keepProjectsFrom = new Date(Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT) - q = {} - q[db.op.lt] = keepProjectsFrom - db.Project.findAll(where:{lastAccessed:q}) - .then((projects) -> - cb null, projects.map((project) -> project.project_id) - ).error cb + _findExpiredProjectIds(callback) { + if (callback == null) { callback = function(error, project_ids) {}; } + const job = function(cb){ + const keepProjectsFrom = new Date(Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT); + const q = {}; + q[db.op.lt] = keepProjectsFrom; + return db.Project.findAll({where:{lastAccessed:q}}) + .then(projects => cb(null, projects.map(project => project.project_id))).error(cb); + }; - dbQueue.queue.push(job, callback) + return dbQueue.queue.push(job, callback); + } +}); -logger.log {EXPIRY_TIMEOUT: ProjectPersistenceManager.EXPIRY_TIMEOUT}, "project assets kept timeout" +logger.log({EXPIRY_TIMEOUT: ProjectPersistenceManager.EXPIRY_TIMEOUT}, "project assets kept timeout"); diff --git a/services/clsi/app/coffee/RequestParser.js b/services/clsi/app/coffee/RequestParser.js index 9b94712480..fdfb8bf46b 100644 --- a/services/clsi/app/coffee/RequestParser.js +++ b/services/clsi/app/coffee/RequestParser.js @@ -1,128 +1,182 @@ -settings = require("settings-sharelatex") +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS205: Consider reworking code to avoid use of IIFEs + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let RequestParser; +const settings = require("settings-sharelatex"); -module.exports = RequestParser = - VALID_COMPILERS: ["pdflatex", "latex", "xelatex", "lualatex"] - MAX_TIMEOUT: 600 +module.exports = (RequestParser = { + VALID_COMPILERS: ["pdflatex", "latex", "xelatex", "lualatex"], + MAX_TIMEOUT: 600, - parse: (body, callback = (error, data) ->) -> - response = {} + parse(body, callback) { + let resource; + if (callback == null) { callback = function(error, data) {}; } + const response = {}; - if !body.compile? - return callback "top level object should have a compile attribute" - - compile = body.compile - compile.options ||= {} - - try - response.compiler = @_parseAttribute "compiler", - compile.options.compiler, - validValues: @VALID_COMPILERS - default: "pdflatex" - type: "string" - response.timeout = @_parseAttribute "timeout", - compile.options.timeout - default: RequestParser.MAX_TIMEOUT - type: "number" - response.imageName = @_parseAttribute "imageName", - compile.options.imageName, - type: "string" - response.draft = @_parseAttribute "draft", - compile.options.draft, - default: false, - type: "boolean" - response.check = @_parseAttribute "check", - compile.options.check, - type: "string" - response.flags = @_parseAttribute "flags", - compile.options.flags, - default: [], - type: "object" - - # The syncType specifies whether the request contains all - # resources (full) or only those resources to be updated - # in-place (incremental). - response.syncType = @_parseAttribute "syncType", - compile.options.syncType, - validValues: ["full", "incremental"] - type: "string" - - # The syncState is an identifier passed in with the request - # which has the property that it changes when any resource is - # added, deleted, moved or renamed. - # - # on syncType full the syncState identifier is passed in and - # stored - # - # on syncType incremental the syncState identifier must match - # the stored value - response.syncState = @_parseAttribute "syncState", - compile.options.syncState, - type: "string" - - if response.timeout > RequestParser.MAX_TIMEOUT - response.timeout = RequestParser.MAX_TIMEOUT - response.timeout = response.timeout * 1000 # milliseconds - - response.resources = (@_parseResource(resource) for resource in (compile.resources or [])) - - rootResourcePath = @_parseAttribute "rootResourcePath", - compile.rootResourcePath - default: "main.tex" - type: "string" - originalRootResourcePath = rootResourcePath - sanitizedRootResourcePath = RequestParser._sanitizePath(rootResourcePath) - response.rootResourcePath = RequestParser._checkPath(sanitizedRootResourcePath) - - for resource in response.resources - if resource.path == originalRootResourcePath - resource.path = sanitizedRootResourcePath - catch error - return callback error - - callback null, response - - _parseResource: (resource) -> - if !resource.path? or typeof resource.path != "string" - throw "all resources should have a path attribute" - - if resource.modified? - modified = new Date(resource.modified) - if isNaN(modified.getTime()) - throw "resource modified date could not be understood: #{resource.modified}" - - if !resource.url? and !resource.content? - throw "all resources should have either a url or content attribute" - if resource.content? and typeof resource.content != "string" - throw "content attribute should be a string" - if resource.url? and typeof resource.url != "string" - throw "url attribute should be a string" - - return { - path: resource.path - modified: modified - url: resource.url - content: resource.content + if ((body.compile == null)) { + return callback("top level object should have a compile attribute"); } - _parseAttribute: (name, attribute, options) -> - if attribute? - if options.validValues? - if options.validValues.indexOf(attribute) == -1 - throw "#{name} attribute should be one of: #{options.validValues.join(", ")}" - if options.type? - if typeof attribute != options.type - throw "#{name} attribute should be a #{options.type}" - else - return options.default if options.default? - return attribute + const { compile } = body; + if (!compile.options) { compile.options = {}; } - _sanitizePath: (path) -> - # See http://php.net/manual/en/function.escapeshellcmd.php - path.replace(/[\#\&\;\`\|\*\?\~\<\>\^\(\)\[\]\{\}\$\\\x0A\xFF\x00]/g, "") + try { + response.compiler = this._parseAttribute("compiler", + compile.options.compiler, { + validValues: this.VALID_COMPILERS, + default: "pdflatex", + type: "string" + } + ); + response.timeout = this._parseAttribute("timeout", + compile.options.timeout, { + default: RequestParser.MAX_TIMEOUT, + type: "number" + } + ); + response.imageName = this._parseAttribute("imageName", + compile.options.imageName, + {type: "string"}); + response.draft = this._parseAttribute("draft", + compile.options.draft, { + default: false, + type: "boolean" + } + ); + response.check = this._parseAttribute("check", + compile.options.check, + {type: "string"}); + response.flags = this._parseAttribute("flags", + compile.options.flags, { + default: [], + type: "object" + } + ); - _checkPath: (path) -> - # check that the request does not use a relative path - for dir in path.split('/') - if dir == '..' - throw "relative path in root resource" - return path + // The syncType specifies whether the request contains all + // resources (full) or only those resources to be updated + // in-place (incremental). + response.syncType = this._parseAttribute("syncType", + compile.options.syncType, { + validValues: ["full", "incremental"], + type: "string" + } + ); + + // The syncState is an identifier passed in with the request + // which has the property that it changes when any resource is + // added, deleted, moved or renamed. + // + // on syncType full the syncState identifier is passed in and + // stored + // + // on syncType incremental the syncState identifier must match + // the stored value + response.syncState = this._parseAttribute("syncState", + compile.options.syncState, + {type: "string"}); + + if (response.timeout > RequestParser.MAX_TIMEOUT) { + response.timeout = RequestParser.MAX_TIMEOUT; + } + response.timeout = response.timeout * 1000; // milliseconds + + response.resources = ((() => { + const result = []; + for (resource of Array.from((compile.resources || []))) { result.push(this._parseResource(resource)); + } + return result; + })()); + + const rootResourcePath = this._parseAttribute("rootResourcePath", + compile.rootResourcePath, { + default: "main.tex", + type: "string" + } + ); + const originalRootResourcePath = rootResourcePath; + const sanitizedRootResourcePath = RequestParser._sanitizePath(rootResourcePath); + response.rootResourcePath = RequestParser._checkPath(sanitizedRootResourcePath); + + for (resource of Array.from(response.resources)) { + if (resource.path === originalRootResourcePath) { + resource.path = sanitizedRootResourcePath; + } + } + } catch (error1) { + const error = error1; + return callback(error); + } + + return callback(null, response); + }, + + _parseResource(resource) { + let modified; + if ((resource.path == null) || (typeof resource.path !== "string")) { + throw "all resources should have a path attribute"; + } + + if (resource.modified != null) { + modified = new Date(resource.modified); + if (isNaN(modified.getTime())) { + throw `resource modified date could not be understood: ${resource.modified}`; + } + } + + if ((resource.url == null) && (resource.content == null)) { + throw "all resources should have either a url or content attribute"; + } + if ((resource.content != null) && (typeof resource.content !== "string")) { + throw "content attribute should be a string"; + } + if ((resource.url != null) && (typeof resource.url !== "string")) { + throw "url attribute should be a string"; + } + + return { + path: resource.path, + modified, + url: resource.url, + content: resource.content + }; + }, + + _parseAttribute(name, attribute, options) { + if (attribute != null) { + if (options.validValues != null) { + if (options.validValues.indexOf(attribute) === -1) { + throw `${name} attribute should be one of: ${options.validValues.join(", ")}`; + } + } + if (options.type != null) { + if (typeof attribute !== options.type) { + throw `${name} attribute should be a ${options.type}`; + } + } + } else { + if (options.default != null) { return options.default; } + } + return attribute; + }, + + _sanitizePath(path) { + // See http://php.net/manual/en/function.escapeshellcmd.php + return path.replace(/[\#\&\;\`\|\*\?\~\<\>\^\(\)\[\]\{\}\$\\\x0A\xFF\x00]/g, ""); + }, + + _checkPath(path) { + // check that the request does not use a relative path + for (let dir of Array.from(path.split('/'))) { + if (dir === '..') { + throw "relative path in root resource"; + } + } + return path; + } +}); diff --git a/services/clsi/app/coffee/ResourceStateManager.js b/services/clsi/app/coffee/ResourceStateManager.js index 19fea47d72..f430c8fdfe 100644 --- a/services/clsi/app/coffee/ResourceStateManager.js +++ b/services/clsi/app/coffee/ResourceStateManager.js @@ -1,72 +1,108 @@ -Path = require "path" -fs = require "fs" -logger = require "logger-sharelatex" -settings = require("settings-sharelatex") -Errors = require "./Errors" -SafeReader = require "./SafeReader" +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__ + * DS201: Simplify complex destructure assignments + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let ResourceStateManager; +const Path = require("path"); +const fs = require("fs"); +const logger = require("logger-sharelatex"); +const settings = require("settings-sharelatex"); +const Errors = require("./Errors"); +const SafeReader = require("./SafeReader"); -module.exports = ResourceStateManager = +module.exports = (ResourceStateManager = { - # The sync state is an identifier which must match for an - # incremental update to be allowed. - # - # The initial value is passed in and stored on a full - # compile, along with the list of resources.. - # - # Subsequent incremental compiles must come with the same value - if - # not they will be rejected with a 409 Conflict response. The - # previous list of resources is returned. - # - # An incremental compile can only update existing files with new - # content. The sync state identifier must change if any docs or - # files are moved, added, deleted or renamed. + // The sync state is an identifier which must match for an + // incremental update to be allowed. + // + // The initial value is passed in and stored on a full + // compile, along with the list of resources.. + // + // Subsequent incremental compiles must come with the same value - if + // not they will be rejected with a 409 Conflict response. The + // previous list of resources is returned. + // + // An incremental compile can only update existing files with new + // content. The sync state identifier must change if any docs or + // files are moved, added, deleted or renamed. - SYNC_STATE_FILE: ".project-sync-state" - SYNC_STATE_MAX_SIZE: 128*1024 + SYNC_STATE_FILE: ".project-sync-state", + SYNC_STATE_MAX_SIZE: 128*1024, - saveProjectState: (state, resources, basePath, callback = (error) ->) -> - stateFile = Path.join(basePath, @SYNC_STATE_FILE) - if not state? # remove the file if no state passed in - logger.log state:state, basePath:basePath, "clearing sync state" - fs.unlink stateFile, (err) -> - if err? and err.code isnt 'ENOENT' - return callback(err) - else - return callback() - else - logger.log state:state, basePath:basePath, "writing sync state" - resourceList = (resource.path for resource in resources) - fs.writeFile stateFile, [resourceList..., "stateHash:#{state}"].join("\n"), callback + saveProjectState(state, resources, basePath, callback) { + if (callback == null) { callback = function(error) {}; } + const stateFile = Path.join(basePath, this.SYNC_STATE_FILE); + if ((state == null)) { // remove the file if no state passed in + logger.log({state, basePath}, "clearing sync state"); + return fs.unlink(stateFile, function(err) { + if ((err != null) && (err.code !== 'ENOENT')) { + return callback(err); + } else { + return callback(); + } + }); + } else { + logger.log({state, basePath}, "writing sync state"); + const resourceList = (Array.from(resources).map((resource) => resource.path)); + return fs.writeFile(stateFile, [...Array.from(resourceList), `stateHash:${state}`].join("\n"), callback); + } + }, - checkProjectStateMatches: (state, basePath, callback = (error, resources) ->) -> - stateFile = Path.join(basePath, @SYNC_STATE_FILE) - size = @SYNC_STATE_MAX_SIZE - SafeReader.readFile stateFile, size, 'utf8', (err, result, bytesRead) -> - return callback(err) if err? - if bytesRead is size - logger.error file:stateFile, size:size, bytesRead:bytesRead, "project state file truncated" - [resourceList..., oldState] = result?.toString()?.split("\n") or [] - newState = "stateHash:#{state}" - logger.log state:state, oldState: oldState, basePath:basePath, stateMatches: (newState is oldState), "checking sync state" - if newState isnt oldState - return callback new Errors.FilesOutOfSyncError("invalid state for incremental update") - else - resources = ({path: path} for path in resourceList) - callback(null, resources) + checkProjectStateMatches(state, basePath, callback) { + if (callback == null) { callback = function(error, resources) {}; } + const stateFile = Path.join(basePath, this.SYNC_STATE_FILE); + const size = this.SYNC_STATE_MAX_SIZE; + return SafeReader.readFile(stateFile, size, 'utf8', function(err, result, bytesRead) { + if (err != null) { return callback(err); } + if (bytesRead === size) { + logger.error({file:stateFile, size, bytesRead}, "project state file truncated"); + } + const array = __guard__(result != null ? result.toString() : undefined, x => x.split("\n")) || [], + adjustedLength = Math.max(array.length, 1), + resourceList = array.slice(0, adjustedLength - 1), + oldState = array[adjustedLength - 1]; + const newState = `stateHash:${state}`; + logger.log({state, oldState, basePath, stateMatches: (newState === oldState)}, "checking sync state"); + if (newState !== oldState) { + return callback(new Errors.FilesOutOfSyncError("invalid state for incremental update")); + } else { + const resources = (Array.from(resourceList).map((path) => ({path}))); + return callback(null, resources); + } + }); + }, - checkResourceFiles: (resources, allFiles, basePath, callback = (error) ->) -> - # check the paths are all relative to current directory - for file in resources or [] - for dir in file?.path?.split('/') - if dir == '..' - return callback new Error("relative path in resource file list") - # check if any of the input files are not present in list of files - seenFile = {} - for file in allFiles - seenFile[file] = true - missingFiles = (resource.path for resource in resources when not seenFile[resource.path]) - if missingFiles?.length > 0 - logger.err missingFiles:missingFiles, basePath:basePath, allFiles:allFiles, resources:resources, "missing input files for project" - return callback new Errors.FilesOutOfSyncError("resource files missing in incremental update") - else - callback() + checkResourceFiles(resources, allFiles, basePath, callback) { + // check the paths are all relative to current directory + let file; + if (callback == null) { callback = function(error) {}; } + for (file of Array.from(resources || [])) { + for (let dir of Array.from(__guard__(file != null ? file.path : undefined, x => x.split('/')))) { + if (dir === '..') { + return callback(new Error("relative path in resource file list")); + } + } + } + // check if any of the input files are not present in list of files + const seenFile = {}; + for (file of Array.from(allFiles)) { + seenFile[file] = true; + } + const missingFiles = (Array.from(resources).filter((resource) => !seenFile[resource.path]).map((resource) => resource.path)); + if ((missingFiles != null ? missingFiles.length : undefined) > 0) { + logger.err({missingFiles, basePath, allFiles, resources}, "missing input files for project"); + return callback(new Errors.FilesOutOfSyncError("resource files missing in incremental update")); + } else { + return callback(); + } + } +}); + +function __guard__(value, transform) { + return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; +} \ No newline at end of file diff --git a/services/clsi/app/coffee/ResourceWriter.js b/services/clsi/app/coffee/ResourceWriter.js index f3a4bd0735..0044ad935e 100644 --- a/services/clsi/app/coffee/ResourceWriter.js +++ b/services/clsi/app/coffee/ResourceWriter.js @@ -1,142 +1,206 @@ -UrlCache = require "./UrlCache" -Path = require "path" -fs = require "fs" -async = require "async" -mkdirp = require "mkdirp" -OutputFileFinder = require "./OutputFileFinder" -ResourceStateManager = require "./ResourceStateManager" -Metrics = require "./Metrics" -logger = require "logger-sharelatex" -settings = require("settings-sharelatex") +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let ResourceWriter; +const UrlCache = require("./UrlCache"); +const Path = require("path"); +const fs = require("fs"); +const async = require("async"); +const mkdirp = require("mkdirp"); +const OutputFileFinder = require("./OutputFileFinder"); +const ResourceStateManager = require("./ResourceStateManager"); +const Metrics = require("./Metrics"); +const logger = require("logger-sharelatex"); +const settings = require("settings-sharelatex"); -parallelFileDownloads = settings.parallelFileDownloads or 1 +const parallelFileDownloads = settings.parallelFileDownloads || 1; -module.exports = ResourceWriter = +module.exports = (ResourceWriter = { - syncResourcesToDisk: (request, basePath, callback = (error, resourceList) ->) -> - if request.syncType is "incremental" - logger.log project_id: request.project_id, user_id: request.user_id, "incremental sync" - ResourceStateManager.checkProjectStateMatches request.syncState, basePath, (error, resourceList) -> - return callback(error) if error? - ResourceWriter._removeExtraneousFiles resourceList, basePath, (error, outputFiles, allFiles) -> - return callback(error) if error? - ResourceStateManager.checkResourceFiles resourceList, allFiles, basePath, (error) -> - return callback(error) if error? - ResourceWriter.saveIncrementalResourcesToDisk request.project_id, request.resources, basePath, (error) -> - return callback(error) if error? - callback(null, resourceList) - else - logger.log project_id: request.project_id, user_id: request.user_id, "full sync" - @saveAllResourcesToDisk request.project_id, request.resources, basePath, (error) -> - return callback(error) if error? - ResourceStateManager.saveProjectState request.syncState, request.resources, basePath, (error) -> - return callback(error) if error? - callback(null, request.resources) + syncResourcesToDisk(request, basePath, callback) { + if (callback == null) { callback = function(error, resourceList) {}; } + if (request.syncType === "incremental") { + logger.log({project_id: request.project_id, user_id: request.user_id}, "incremental sync"); + return ResourceStateManager.checkProjectStateMatches(request.syncState, basePath, function(error, resourceList) { + if (error != null) { return callback(error); } + return ResourceWriter._removeExtraneousFiles(resourceList, basePath, function(error, outputFiles, allFiles) { + if (error != null) { return callback(error); } + return ResourceStateManager.checkResourceFiles(resourceList, allFiles, basePath, function(error) { + if (error != null) { return callback(error); } + return ResourceWriter.saveIncrementalResourcesToDisk(request.project_id, request.resources, basePath, function(error) { + if (error != null) { return callback(error); } + return callback(null, resourceList); + }); + }); + }); + }); + } else { + logger.log({project_id: request.project_id, user_id: request.user_id}, "full sync"); + return this.saveAllResourcesToDisk(request.project_id, request.resources, basePath, function(error) { + if (error != null) { return callback(error); } + return ResourceStateManager.saveProjectState(request.syncState, request.resources, basePath, function(error) { + if (error != null) { return callback(error); } + return callback(null, request.resources); + }); + }); + } + }, - saveIncrementalResourcesToDisk: (project_id, resources, basePath, callback = (error) ->) -> - @_createDirectory basePath, (error) => - return callback(error) if error? - jobs = for resource in resources - do (resource) => - (callback) => @_writeResourceToDisk(project_id, resource, basePath, callback) - async.parallelLimit jobs, parallelFileDownloads, callback + saveIncrementalResourcesToDisk(project_id, resources, basePath, callback) { + if (callback == null) { callback = function(error) {}; } + return this._createDirectory(basePath, error => { + if (error != null) { return callback(error); } + const jobs = Array.from(resources).map((resource) => + (resource => { + return callback => this._writeResourceToDisk(project_id, resource, basePath, callback); + })(resource)); + return async.parallelLimit(jobs, parallelFileDownloads, callback); + }); + }, - saveAllResourcesToDisk: (project_id, resources, basePath, callback = (error) ->) -> - @_createDirectory basePath, (error) => - return callback(error) if error? - @_removeExtraneousFiles resources, basePath, (error) => - return callback(error) if error? - jobs = for resource in resources - do (resource) => - (callback) => @_writeResourceToDisk(project_id, resource, basePath, callback) - async.parallelLimit jobs, parallelFileDownloads, callback + saveAllResourcesToDisk(project_id, resources, basePath, callback) { + if (callback == null) { callback = function(error) {}; } + return this._createDirectory(basePath, error => { + if (error != null) { return callback(error); } + return this._removeExtraneousFiles(resources, basePath, error => { + if (error != null) { return callback(error); } + const jobs = Array.from(resources).map((resource) => + (resource => { + return callback => this._writeResourceToDisk(project_id, resource, basePath, callback); + })(resource)); + return async.parallelLimit(jobs, parallelFileDownloads, callback); + }); + }); + }, - _createDirectory: (basePath, callback = (error) ->) -> - fs.mkdir basePath, (err) -> - if err? - if err.code is 'EEXIST' - return callback() - else - logger.log {err: err, dir:basePath}, "error creating directory" - return callback(err) - else - return callback() + _createDirectory(basePath, callback) { + if (callback == null) { callback = function(error) {}; } + return fs.mkdir(basePath, function(err) { + if (err != null) { + if (err.code === 'EEXIST') { + return callback(); + } else { + logger.log({err, dir:basePath}, "error creating directory"); + return callback(err); + } + } else { + return callback(); + } + }); + }, - _removeExtraneousFiles: (resources, basePath, _callback = (error, outputFiles, allFiles) ->) -> - timer = new Metrics.Timer("unlink-output-files") - callback = (error, result...) -> - timer.done() - _callback(error, result...) + _removeExtraneousFiles(resources, basePath, _callback) { + if (_callback == null) { _callback = function(error, outputFiles, allFiles) {}; } + const timer = new Metrics.Timer("unlink-output-files"); + const callback = function(error, ...result) { + timer.done(); + return _callback(error, ...Array.from(result)); + }; - OutputFileFinder.findOutputFiles resources, basePath, (error, outputFiles, allFiles) -> - return callback(error) if error? + return OutputFileFinder.findOutputFiles(resources, basePath, function(error, outputFiles, allFiles) { + if (error != null) { return callback(error); } - jobs = [] - for file in outputFiles or [] - do (file) -> - path = file.path - should_delete = true - if path.match(/^output\./) or path.match(/\.aux$/) or path.match(/^cache\//) # knitr cache - should_delete = false - if path.match(/^output-.*/) # Tikz cached figures (default case) - should_delete = false - if path.match(/\.(pdf|dpth|md5)$/) # Tikz cached figures (by extension) - should_delete = false - if path.match(/\.(pygtex|pygstyle)$/) or path.match(/(^|\/)_minted-[^\/]+\//) # minted files/directory - should_delete = false - if path.match(/\.md\.tex$/) or path.match(/(^|\/)_markdown_[^\/]+\//) # markdown files/directory - should_delete = false - if path.match(/-eps-converted-to\.pdf$/) # Epstopdf generated files - should_delete = false - if path == "output.pdf" or path == "output.dvi" or path == "output.log" or path == "output.xdv" - should_delete = true - if path == "output.tex" # created by TikzManager if present in output files - should_delete = true - if should_delete - jobs.push (callback) -> ResourceWriter._deleteFileIfNotDirectory Path.join(basePath, path), callback + const jobs = []; + for (let file of Array.from(outputFiles || [])) { + (function(file) { + const { path } = file; + let should_delete = true; + if (path.match(/^output\./) || path.match(/\.aux$/) || path.match(/^cache\//)) { // knitr cache + should_delete = false; + } + if (path.match(/^output-.*/)) { // Tikz cached figures (default case) + should_delete = false; + } + if (path.match(/\.(pdf|dpth|md5)$/)) { // Tikz cached figures (by extension) + should_delete = false; + } + if (path.match(/\.(pygtex|pygstyle)$/) || path.match(/(^|\/)_minted-[^\/]+\//)) { // minted files/directory + should_delete = false; + } + if (path.match(/\.md\.tex$/) || path.match(/(^|\/)_markdown_[^\/]+\//)) { // markdown files/directory + should_delete = false; + } + if (path.match(/-eps-converted-to\.pdf$/)) { // Epstopdf generated files + should_delete = false; + } + if ((path === "output.pdf") || (path === "output.dvi") || (path === "output.log") || (path === "output.xdv")) { + should_delete = true; + } + if (path === "output.tex") { // created by TikzManager if present in output files + should_delete = true; + } + if (should_delete) { + return jobs.push(callback => ResourceWriter._deleteFileIfNotDirectory(Path.join(basePath, path), callback)); + } + })(file); + } - async.series jobs, (error) -> - return callback(error) if error? - callback(null, outputFiles, allFiles) + return async.series(jobs, function(error) { + if (error != null) { return callback(error); } + return callback(null, outputFiles, allFiles); + }); + }); + }, - _deleteFileIfNotDirectory: (path, callback = (error) ->) -> - fs.stat path, (error, stat) -> - if error? and error.code is 'ENOENT' - return callback() - else if error? - logger.err {err: error, path: path}, "error stating file in deleteFileIfNotDirectory" - return callback(error) - else if stat.isFile() - fs.unlink path, (error) -> - if error? - logger.err {err: error, path: path}, "error removing file in deleteFileIfNotDirectory" - callback(error) - else - callback() - else - callback() + _deleteFileIfNotDirectory(path, callback) { + if (callback == null) { callback = function(error) {}; } + return fs.stat(path, function(error, stat) { + if ((error != null) && (error.code === 'ENOENT')) { + return callback(); + } else if (error != null) { + logger.err({err: error, path}, "error stating file in deleteFileIfNotDirectory"); + return callback(error); + } else if (stat.isFile()) { + return fs.unlink(path, function(error) { + if (error != null) { + logger.err({err: error, path}, "error removing file in deleteFileIfNotDirectory"); + return callback(error); + } else { + return callback(); + } + }); + } else { + return callback(); + } + }); + }, - _writeResourceToDisk: (project_id, resource, basePath, callback = (error) ->) -> - ResourceWriter.checkPath basePath, resource.path, (error, path) -> - return callback(error) if error? - mkdirp Path.dirname(path), (error) -> - return callback(error) if error? - # TODO: Don't overwrite file if it hasn't been modified - if resource.url? - UrlCache.downloadUrlToFile project_id, resource.url, path, resource.modified, (err)-> - if err? - logger.err err:err, project_id:project_id, path:path, resource_url:resource.url, modified:resource.modified, "error downloading file for resources" - callback() #try and continue compiling even if http resource can not be downloaded at this time - else - process = require("process") - fs.writeFile path, resource.content, callback - try - result = fs.lstatSync(path) - catch e + _writeResourceToDisk(project_id, resource, basePath, callback) { + if (callback == null) { callback = function(error) {}; } + return ResourceWriter.checkPath(basePath, resource.path, function(error, path) { + if (error != null) { return callback(error); } + return mkdirp(Path.dirname(path), function(error) { + if (error != null) { return callback(error); } + // TODO: Don't overwrite file if it hasn't been modified + if (resource.url != null) { + return UrlCache.downloadUrlToFile(project_id, resource.url, path, resource.modified, function(err){ + if (err != null) { + logger.err({err, project_id, path, resource_url:resource.url, modified:resource.modified}, "error downloading file for resources"); + } + return callback(); + }); //try and continue compiling even if http resource can not be downloaded at this time + } else { + const process = require("process"); + fs.writeFile(path, resource.content, callback); + try { + let result; + return result = fs.lstatSync(path); + } catch (e) {} + } + }); + }); + }, - checkPath: (basePath, resourcePath, callback) -> - path = Path.normalize(Path.join(basePath, resourcePath)) - if (path.slice(0, basePath.length + 1) != basePath + "/") - return callback new Error("resource path is outside root directory") - else - return callback(null, path) + checkPath(basePath, resourcePath, callback) { + const path = Path.normalize(Path.join(basePath, resourcePath)); + if (path.slice(0, basePath.length + 1) !== (basePath + "/")) { + return callback(new Error("resource path is outside root directory")); + } else { + return callback(null, path); + } + } +}); diff --git a/services/clsi/app/coffee/SafeReader.js b/services/clsi/app/coffee/SafeReader.js index adb96b1645..f1a6349465 100644 --- a/services/clsi/app/coffee/SafeReader.js +++ b/services/clsi/app/coffee/SafeReader.js @@ -1,25 +1,40 @@ -fs = require "fs" -logger = require "logger-sharelatex" +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let SafeReader; +const fs = require("fs"); +const logger = require("logger-sharelatex"); -module.exports = SafeReader = +module.exports = (SafeReader = { - # safely read up to size bytes from a file and return result as a - # string + // safely read up to size bytes from a file and return result as a + // string - readFile: (file, size, encoding, callback = (error, result) ->) -> - fs.open file, 'r', (err, fd) -> - return callback() if err? and err.code is 'ENOENT' - return callback(err) if err? + readFile(file, size, encoding, callback) { + if (callback == null) { callback = function(error, result) {}; } + return fs.open(file, 'r', function(err, fd) { + if ((err != null) && (err.code === 'ENOENT')) { return callback(); } + if (err != null) { return callback(err); } - # safely return always closing the file - callbackWithClose = (err, result...) -> - fs.close fd, (err1) -> - return callback(err) if err? - return callback(err1) if err1? - callback(null, result...) + // safely return always closing the file + const callbackWithClose = (err, ...result) => + fs.close(fd, function(err1) { + if (err != null) { return callback(err); } + if (err1 != null) { return callback(err1); } + return callback(null, ...Array.from(result)); + }) + ; - buff = new Buffer(size, 0) # fill with zeros - fs.read fd, buff, 0, buff.length, 0, (err, bytesRead, buffer) -> - return callbackWithClose(err) if err? - result = buffer.toString(encoding, 0, bytesRead) - callbackWithClose(null, result, bytesRead) + const buff = new Buffer(size, 0); // fill with zeros + return fs.read(fd, buff, 0, buff.length, 0, function(err, bytesRead, buffer) { + if (err != null) { return callbackWithClose(err); } + const result = buffer.toString(encoding, 0, bytesRead); + return callbackWithClose(null, result, bytesRead); + }); + }); + } +}); diff --git a/services/clsi/app/coffee/StaticServerForbidSymlinks.js b/services/clsi/app/coffee/StaticServerForbidSymlinks.js index 1b3cd45836..90a8879d1f 100644 --- a/services/clsi/app/coffee/StaticServerForbidSymlinks.js +++ b/services/clsi/app/coffee/StaticServerForbidSymlinks.js @@ -1,41 +1,64 @@ -Path = require("path") -fs = require("fs") -Settings = require("settings-sharelatex") -logger = require("logger-sharelatex") -url = require "url" +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__ + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let ForbidSymlinks; +const Path = require("path"); +const fs = require("fs"); +const Settings = require("settings-sharelatex"); +const logger = require("logger-sharelatex"); +const url = require("url"); -module.exports = ForbidSymlinks = (staticFn, root, options) -> - expressStatic = staticFn root, options - basePath = Path.resolve(root) - return (req, res, next) -> - path = url.parse(req.url)?.pathname - # check that the path is of the form /project_id_or_name/path/to/file.log - if result = path.match(/^\/?([a-zA-Z0-9_-]+)\/(.*)/) - project_id = result[1] - file = result[2] - else - logger.warn path: path, "unrecognized file request" - return res.sendStatus(404) - # check that the file does not use a relative path - for dir in file.split('/') - if dir == '..' - logger.warn path: path, "attempt to use a relative path" - return res.sendStatus(404) - # check that the requested path is normalized - requestedFsPath = "#{basePath}/#{project_id}/#{file}" - if requestedFsPath != Path.normalize(requestedFsPath) - logger.error path: requestedFsPath, "requestedFsPath is not normalized" - return res.sendStatus(404) - # check that the requested path is not a symlink - fs.realpath requestedFsPath, (err, realFsPath)-> - if err? - if err.code == 'ENOENT' - return res.sendStatus(404) - else - logger.error err:err, requestedFsPath:requestedFsPath, realFsPath:realFsPath, path: req.params[0], project_id: req.params.project_id, "error checking file access" - return res.sendStatus(500) - else if requestedFsPath != realFsPath - logger.warn requestedFsPath:requestedFsPath, realFsPath:realFsPath, path: req.params[0], project_id: req.params.project_id, "trying to access a different file (symlink), aborting" - return res.sendStatus(404) - else - expressStatic(req, res, next) +module.exports = (ForbidSymlinks = function(staticFn, root, options) { + const expressStatic = staticFn(root, options); + const basePath = Path.resolve(root); + return function(req, res, next) { + let file, project_id, result; + const path = __guard__(url.parse(req.url), x => x.pathname); + // check that the path is of the form /project_id_or_name/path/to/file.log + if (result = path.match(/^\/?([a-zA-Z0-9_-]+)\/(.*)/)) { + project_id = result[1]; + file = result[2]; + } else { + logger.warn({path}, "unrecognized file request"); + return res.sendStatus(404); + } + // check that the file does not use a relative path + for (let dir of Array.from(file.split('/'))) { + if (dir === '..') { + logger.warn({path}, "attempt to use a relative path"); + return res.sendStatus(404); + } + } + // check that the requested path is normalized + const requestedFsPath = `${basePath}/${project_id}/${file}`; + if (requestedFsPath !== Path.normalize(requestedFsPath)) { + logger.error({path: requestedFsPath}, "requestedFsPath is not normalized"); + return res.sendStatus(404); + } + // check that the requested path is not a symlink + return fs.realpath(requestedFsPath, function(err, realFsPath){ + if (err != null) { + if (err.code === 'ENOENT') { + return res.sendStatus(404); + } else { + logger.error({err, requestedFsPath, realFsPath, path: req.params[0], project_id: req.params.project_id}, "error checking file access"); + return res.sendStatus(500); + } + } else if (requestedFsPath !== realFsPath) { + logger.warn({requestedFsPath, realFsPath, path: req.params[0], project_id: req.params.project_id}, "trying to access a different file (symlink), aborting"); + return res.sendStatus(404); + } else { + return expressStatic(req, res, next); + } + }); + }; +}); + +function __guard__(value, transform) { + return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; +} \ No newline at end of file diff --git a/services/clsi/app/coffee/TikzManager.js b/services/clsi/app/coffee/TikzManager.js index 22def278b1..fb5264474a 100644 --- a/services/clsi/app/coffee/TikzManager.js +++ b/services/clsi/app/coffee/TikzManager.js @@ -1,37 +1,56 @@ -fs = require "fs" -Path = require "path" -ResourceWriter = require "./ResourceWriter" -SafeReader = require "./SafeReader" -logger = require "logger-sharelatex" +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let TikzManager; +const fs = require("fs"); +const Path = require("path"); +const ResourceWriter = require("./ResourceWriter"); +const SafeReader = require("./SafeReader"); +const logger = require("logger-sharelatex"); -# for \tikzexternalize or pstool to work the main file needs to match the -# jobname. Since we set the -jobname to output, we have to create a -# copy of the main file as 'output.tex'. +// for \tikzexternalize or pstool to work the main file needs to match the +// jobname. Since we set the -jobname to output, we have to create a +// copy of the main file as 'output.tex'. -module.exports = TikzManager = +module.exports = (TikzManager = { - checkMainFile: (compileDir, mainFile, resources, callback = (error, needsMainFile) ->) -> - # if there's already an output.tex file, we don't want to touch it - for resource in resources - if resource.path is "output.tex" - logger.log compileDir: compileDir, mainFile: mainFile, "output.tex already in resources" - return callback(null, false) - # if there's no output.tex, see if we are using tikz/pgf or pstool in the main file - ResourceWriter.checkPath compileDir, mainFile, (error, path) -> - return callback(error) if error? - SafeReader.readFile path, 65536, "utf8", (error, content) -> - return callback(error) if error? - usesTikzExternalize = content?.indexOf("\\tikzexternalize") >= 0 - usesPsTool = content?.indexOf("{pstool}") >= 0 - logger.log compileDir: compileDir, mainFile: mainFile, usesTikzExternalize:usesTikzExternalize, usesPsTool: usesPsTool, "checked for packages needing main file as output.tex" - needsMainFile = (usesTikzExternalize || usesPsTool) - callback null, needsMainFile + checkMainFile(compileDir, mainFile, resources, callback) { + // if there's already an output.tex file, we don't want to touch it + if (callback == null) { callback = function(error, needsMainFile) {}; } + for (let resource of Array.from(resources)) { + if (resource.path === "output.tex") { + logger.log({compileDir, mainFile}, "output.tex already in resources"); + return callback(null, false); + } + } + // if there's no output.tex, see if we are using tikz/pgf or pstool in the main file + return ResourceWriter.checkPath(compileDir, mainFile, function(error, path) { + if (error != null) { return callback(error); } + return SafeReader.readFile(path, 65536, "utf8", function(error, content) { + if (error != null) { return callback(error); } + const usesTikzExternalize = (content != null ? content.indexOf("\\tikzexternalize") : undefined) >= 0; + const usesPsTool = (content != null ? content.indexOf("{pstool}") : undefined) >= 0; + logger.log({compileDir, mainFile, usesTikzExternalize, usesPsTool}, "checked for packages needing main file as output.tex"); + const needsMainFile = (usesTikzExternalize || usesPsTool); + return callback(null, needsMainFile); + }); + }); + }, - injectOutputFile: (compileDir, mainFile, callback = (error) ->) -> - ResourceWriter.checkPath compileDir, mainFile, (error, path) -> - return callback(error) if error? - fs.readFile path, "utf8", (error, content) -> - return callback(error) if error? - logger.log compileDir: compileDir, mainFile: mainFile, "copied file to output.tex as project uses packages which require it" - # use wx flag to ensure that output file does not already exist - fs.writeFile Path.join(compileDir, "output.tex"), content, {flag:'wx'}, callback + injectOutputFile(compileDir, mainFile, callback) { + if (callback == null) { callback = function(error) {}; } + return ResourceWriter.checkPath(compileDir, mainFile, function(error, path) { + if (error != null) { return callback(error); } + return fs.readFile(path, "utf8", function(error, content) { + if (error != null) { return callback(error); } + logger.log({compileDir, mainFile}, "copied file to output.tex as project uses packages which require it"); + // use wx flag to ensure that output file does not already exist + return fs.writeFile(Path.join(compileDir, "output.tex"), content, {flag:'wx'}, callback); + }); + }); + } +}); diff --git a/services/clsi/app/coffee/UrlCache.js b/services/clsi/app/coffee/UrlCache.js index d44479a10a..9a199688dd 100644 --- a/services/clsi/app/coffee/UrlCache.js +++ b/services/clsi/app/coffee/UrlCache.js @@ -1,134 +1,189 @@ -db = require("./db") -dbQueue = require "./DbQueue" -UrlFetcher = require("./UrlFetcher") -Settings = require("settings-sharelatex") -crypto = require("crypto") -fs = require("fs") -logger = require "logger-sharelatex" -async = require "async" +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let UrlCache; +const db = require("./db"); +const dbQueue = require("./DbQueue"); +const UrlFetcher = require("./UrlFetcher"); +const Settings = require("settings-sharelatex"); +const crypto = require("crypto"); +const fs = require("fs"); +const logger = require("logger-sharelatex"); +const async = require("async"); -module.exports = UrlCache = - downloadUrlToFile: (project_id, url, destPath, lastModified, callback = (error) ->) -> - UrlCache._ensureUrlIsInCache project_id, url, lastModified, (error, pathToCachedUrl) => - return callback(error) if error? - UrlCache._copyFile pathToCachedUrl, destPath, (error) -> - if error? - UrlCache._clearUrlDetails project_id, url, () -> - callback(error) - else - callback(error) +module.exports = (UrlCache = { + downloadUrlToFile(project_id, url, destPath, lastModified, callback) { + if (callback == null) { callback = function(error) {}; } + return UrlCache._ensureUrlIsInCache(project_id, url, lastModified, (error, pathToCachedUrl) => { + if (error != null) { return callback(error); } + return UrlCache._copyFile(pathToCachedUrl, destPath, function(error) { + if (error != null) { + return UrlCache._clearUrlDetails(project_id, url, () => callback(error)); + } else { + return callback(error); + } + }); + }); + }, - clearProject: (project_id, callback = (error) ->) -> - UrlCache._findAllUrlsInProject project_id, (error, urls) -> - logger.log project_id: project_id, url_count: urls.length, "clearing project URLs" - return callback(error) if error? - jobs = for url in (urls or []) - do (url) -> - (callback) -> - UrlCache._clearUrlFromCache project_id, url, (error) -> - if error? - logger.error err: error, project_id: project_id, url: url, "error clearing project URL" - callback() - async.series jobs, callback + clearProject(project_id, callback) { + if (callback == null) { callback = function(error) {}; } + return UrlCache._findAllUrlsInProject(project_id, function(error, urls) { + logger.log({project_id, url_count: urls.length}, "clearing project URLs"); + if (error != null) { return callback(error); } + const jobs = (Array.from(urls || [])).map((url) => + (url => + callback => + UrlCache._clearUrlFromCache(project_id, url, function(error) { + if (error != null) { + logger.error({err: error, project_id, url}, "error clearing project URL"); + } + return callback(); + }) + + )(url)); + return async.series(jobs, callback); + }); + }, - _ensureUrlIsInCache: (project_id, url, lastModified, callback = (error, pathOnDisk) ->) -> - if lastModified? - # MYSQL only stores dates to an accuracy of a second but the incoming lastModified might have milliseconds. - # So round down to seconds - lastModified = new Date(Math.floor(lastModified.getTime() / 1000) * 1000) - UrlCache._doesUrlNeedDownloading project_id, url, lastModified, (error, needsDownloading) => - return callback(error) if error? - if needsDownloading - logger.log url: url, lastModified: lastModified, "downloading URL" - UrlFetcher.pipeUrlToFile url, UrlCache._cacheFilePathForUrl(project_id, url), (error) => - return callback(error) if error? - UrlCache._updateOrCreateUrlDetails project_id, url, lastModified, (error) => - return callback(error) if error? - callback null, UrlCache._cacheFilePathForUrl(project_id, url) - else - logger.log url: url, lastModified: lastModified, "URL is up to date in cache" - callback null, UrlCache._cacheFilePathForUrl(project_id, url) + _ensureUrlIsInCache(project_id, url, lastModified, callback) { + if (callback == null) { callback = function(error, pathOnDisk) {}; } + if (lastModified != null) { + // MYSQL only stores dates to an accuracy of a second but the incoming lastModified might have milliseconds. + // So round down to seconds + lastModified = new Date(Math.floor(lastModified.getTime() / 1000) * 1000); + } + return UrlCache._doesUrlNeedDownloading(project_id, url, lastModified, (error, needsDownloading) => { + if (error != null) { return callback(error); } + if (needsDownloading) { + logger.log({url, lastModified}, "downloading URL"); + return UrlFetcher.pipeUrlToFile(url, UrlCache._cacheFilePathForUrl(project_id, url), error => { + if (error != null) { return callback(error); } + return UrlCache._updateOrCreateUrlDetails(project_id, url, lastModified, error => { + if (error != null) { return callback(error); } + return callback(null, UrlCache._cacheFilePathForUrl(project_id, url)); + }); + }); + } else { + logger.log({url, lastModified}, "URL is up to date in cache"); + return callback(null, UrlCache._cacheFilePathForUrl(project_id, url)); + } + }); + }, - _doesUrlNeedDownloading: (project_id, url, lastModified, callback = (error, needsDownloading) ->) -> - if !lastModified? - return callback null, true - UrlCache._findUrlDetails project_id, url, (error, urlDetails) -> - return callback(error) if error? - if !urlDetails? or !urlDetails.lastModified? or urlDetails.lastModified.getTime() < lastModified.getTime() - return callback null, true - else - return callback null, false + _doesUrlNeedDownloading(project_id, url, lastModified, callback) { + if (callback == null) { callback = function(error, needsDownloading) {}; } + if ((lastModified == null)) { + return callback(null, true); + } + return UrlCache._findUrlDetails(project_id, url, function(error, urlDetails) { + if (error != null) { return callback(error); } + if ((urlDetails == null) || (urlDetails.lastModified == null) || (urlDetails.lastModified.getTime() < lastModified.getTime())) { + return callback(null, true); + } else { + return callback(null, false); + } + }); + }, - _cacheFileNameForUrl: (project_id, url) -> - project_id + ":" + crypto.createHash("md5").update(url).digest("hex") + _cacheFileNameForUrl(project_id, url) { + return project_id + ":" + crypto.createHash("md5").update(url).digest("hex"); + }, - _cacheFilePathForUrl: (project_id, url) -> - "#{Settings.path.clsiCacheDir}/#{UrlCache._cacheFileNameForUrl(project_id, url)}" + _cacheFilePathForUrl(project_id, url) { + return `${Settings.path.clsiCacheDir}/${UrlCache._cacheFileNameForUrl(project_id, url)}`; + }, - _copyFile: (from, to, _callback = (error) ->) -> - callbackOnce = (error) -> - if error? - logger.error err: error, from:from, to:to, "error copying file from cache" - _callback(error) - _callback = () -> - writeStream = fs.createWriteStream(to) - readStream = fs.createReadStream(from) - writeStream.on "error", callbackOnce - readStream.on "error", callbackOnce - writeStream.on "close", callbackOnce - writeStream.on "open", () -> - readStream.pipe(writeStream) + _copyFile(from, to, _callback) { + if (_callback == null) { _callback = function(error) {}; } + const callbackOnce = function(error) { + if (error != null) { + logger.error({err: error, from, to}, "error copying file from cache"); + } + _callback(error); + return _callback = function() {}; + }; + const writeStream = fs.createWriteStream(to); + const readStream = fs.createReadStream(from); + writeStream.on("error", callbackOnce); + readStream.on("error", callbackOnce); + writeStream.on("close", callbackOnce); + return writeStream.on("open", () => readStream.pipe(writeStream)); + }, - _clearUrlFromCache: (project_id, url, callback = (error) ->) -> - UrlCache._clearUrlDetails project_id, url, (error) -> - return callback(error) if error? - UrlCache._deleteUrlCacheFromDisk project_id, url, (error) -> - return callback(error) if error? - callback null + _clearUrlFromCache(project_id, url, callback) { + if (callback == null) { callback = function(error) {}; } + return UrlCache._clearUrlDetails(project_id, url, function(error) { + if (error != null) { return callback(error); } + return UrlCache._deleteUrlCacheFromDisk(project_id, url, function(error) { + if (error != null) { return callback(error); } + return callback(null); + }); + }); + }, - _deleteUrlCacheFromDisk: (project_id, url, callback = (error) ->) -> - fs.unlink UrlCache._cacheFilePathForUrl(project_id, url), (error) -> - if error? and error.code != 'ENOENT' # no error if the file isn't present - return callback(error) - else - return callback() + _deleteUrlCacheFromDisk(project_id, url, callback) { + if (callback == null) { callback = function(error) {}; } + return fs.unlink(UrlCache._cacheFilePathForUrl(project_id, url), function(error) { + if ((error != null) && (error.code !== 'ENOENT')) { // no error if the file isn't present + return callback(error); + } else { + return callback(); + } + }); + }, - _findUrlDetails: (project_id, url, callback = (error, urlDetails) ->) -> - job = (cb)-> - db.UrlCache.find(where: { url: url, project_id: project_id }) - .then((urlDetails) -> cb null, urlDetails) - .error cb - dbQueue.queue.push job, callback + _findUrlDetails(project_id, url, callback) { + if (callback == null) { callback = function(error, urlDetails) {}; } + const job = cb=> + db.UrlCache.find({where: { url, project_id }}) + .then(urlDetails => cb(null, urlDetails)) + .error(cb) + ; + return dbQueue.queue.push(job, callback); + }, - _updateOrCreateUrlDetails: (project_id, url, lastModified, callback = (error) ->) -> - job = (cb)-> - db.UrlCache.findOrCreate(where: {url: url, project_id: project_id}) + _updateOrCreateUrlDetails(project_id, url, lastModified, callback) { + if (callback == null) { callback = function(error) {}; } + const job = cb=> + db.UrlCache.findOrCreate({where: {url, project_id}}) .spread( - (urlDetails, created) -> - urlDetails.updateAttributes(lastModified: lastModified) - .then(() -> cb()) + (urlDetails, created) => + urlDetails.updateAttributes({lastModified}) + .then(() => cb()) .error(cb) ) - .error cb - dbQueue.queue.push(job, callback) + .error(cb) + ; + return dbQueue.queue.push(job, callback); + }, - _clearUrlDetails: (project_id, url, callback = (error) ->) -> - job = (cb)-> - db.UrlCache.destroy(where: {url: url, project_id: project_id}) - .then(() -> cb null) - .error cb - dbQueue.queue.push(job, callback) + _clearUrlDetails(project_id, url, callback) { + if (callback == null) { callback = function(error) {}; } + const job = cb=> + db.UrlCache.destroy({where: {url, project_id}}) + .then(() => cb(null)) + .error(cb) + ; + return dbQueue.queue.push(job, callback); + }, - _findAllUrlsInProject: (project_id, callback = (error, urls) ->) -> - job = (cb)-> - db.UrlCache.findAll(where: { project_id: project_id }) + _findAllUrlsInProject(project_id, callback) { + if (callback == null) { callback = function(error, urls) {}; } + const job = cb=> + db.UrlCache.findAll({where: { project_id }}) .then( - (urlEntries) -> - cb null, urlEntries.map((entry) -> entry.url) - ) - .error cb - dbQueue.queue.push(job, callback) + urlEntries => cb(null, urlEntries.map(entry => entry.url))) + .error(cb) + ; + return dbQueue.queue.push(job, callback); + } +}); diff --git a/services/clsi/app/coffee/UrlFetcher.js b/services/clsi/app/coffee/UrlFetcher.js index da10859f1e..ea20956000 100644 --- a/services/clsi/app/coffee/UrlFetcher.js +++ b/services/clsi/app/coffee/UrlFetcher.js @@ -1,70 +1,88 @@ -request = require("request").defaults(jar: false) -fs = require("fs") -logger = require "logger-sharelatex" -settings = require("settings-sharelatex") -URL = require('url'); +/* + * 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 UrlFetcher; +const request = require("request").defaults({jar: false}); +const fs = require("fs"); +const logger = require("logger-sharelatex"); +const settings = require("settings-sharelatex"); +const URL = require('url'); -oneMinute = 60 * 1000 +const oneMinute = 60 * 1000; -module.exports = UrlFetcher = - pipeUrlToFile: (url, filePath, _callback = (error) ->) -> - callbackOnce = (error) -> - clearTimeout timeoutHandler if timeoutHandler? - _callback(error) - _callback = () -> +module.exports = (UrlFetcher = { + pipeUrlToFile(url, filePath, _callback) { + if (_callback == null) { _callback = function(error) {}; } + const callbackOnce = function(error) { + if (timeoutHandler != null) { clearTimeout(timeoutHandler); } + _callback(error); + return _callback = function() {}; + }; - if settings.filestoreDomainOveride? - p = URL.parse(url).path - url = "#{settings.filestoreDomainOveride}#{p}" - timeoutHandler = setTimeout () -> - timeoutHandler = null - logger.error url:url, filePath: filePath, "Timed out downloading file to cache" - callbackOnce(new Error("Timed out downloading file to cache #{url}")) - # FIXME: maybe need to close fileStream here - , 3 * oneMinute + if (settings.filestoreDomainOveride != null) { + const p = URL.parse(url).path; + url = `${settings.filestoreDomainOveride}${p}`; + } + var timeoutHandler = setTimeout(function() { + timeoutHandler = null; + logger.error({url, filePath}, "Timed out downloading file to cache"); + return callbackOnce(new Error(`Timed out downloading file to cache ${url}`)); + } + // FIXME: maybe need to close fileStream here + , 3 * oneMinute); - logger.log url:url, filePath: filePath, "started downloading url to cache" - urlStream = request.get({url: url, timeout: oneMinute}) - urlStream.pause() # stop data flowing until we are ready + logger.log({url, filePath}, "started downloading url to cache"); + const urlStream = request.get({url, timeout: oneMinute}); + urlStream.pause(); // stop data flowing until we are ready - # attach handlers before setting up pipes - urlStream.on "error", (error) -> - logger.error err: error, url:url, filePath: filePath, "error downloading url" - callbackOnce(error or new Error("Something went wrong downloading the URL #{url}")) + // attach handlers before setting up pipes + urlStream.on("error", function(error) { + logger.error({err: error, url, filePath}, "error downloading url"); + return callbackOnce(error || new Error(`Something went wrong downloading the URL ${url}`)); + }); - urlStream.on "end", () -> - logger.log url:url, filePath: filePath, "finished downloading file into cache" + urlStream.on("end", () => logger.log({url, filePath}, "finished downloading file into cache")); - urlStream.on "response", (res) -> - if res.statusCode >= 200 and res.statusCode < 300 - fileStream = fs.createWriteStream(filePath) + return urlStream.on("response", function(res) { + if ((res.statusCode >= 200) && (res.statusCode < 300)) { + const fileStream = fs.createWriteStream(filePath); - # attach handlers before setting up pipes - fileStream.on 'error', (error) -> - logger.error err: error, url:url, filePath: filePath, "error writing file into cache" - fs.unlink filePath, (err) -> - if err? - logger.err err: err, filePath: filePath, "error deleting file from cache" - callbackOnce(error) + // attach handlers before setting up pipes + fileStream.on('error', function(error) { + logger.error({err: error, url, filePath}, "error writing file into cache"); + return fs.unlink(filePath, function(err) { + if (err != null) { + logger.err({err, filePath}, "error deleting file from cache"); + } + return callbackOnce(error); + }); + }); - fileStream.on 'finish', () -> - logger.log url:url, filePath: filePath, "finished writing file into cache" - callbackOnce() + fileStream.on('finish', function() { + logger.log({url, filePath}, "finished writing file into cache"); + return callbackOnce(); + }); - fileStream.on 'pipe', () -> - logger.log url:url, filePath: filePath, "piping into filestream" + fileStream.on('pipe', () => logger.log({url, filePath}, "piping into filestream")); - urlStream.pipe(fileStream) - urlStream.resume() # now we are ready to handle the data - else - logger.error statusCode: res.statusCode, url:url, filePath: filePath, "unexpected status code downloading url to cache" - # https://nodejs.org/api/http.html#http_class_http_clientrequest - # If you add a 'response' event handler, then you must consume - # the data from the response object, either by calling - # response.read() whenever there is a 'readable' event, or by - # adding a 'data' handler, or by calling the .resume() - # method. Until the data is consumed, the 'end' event will not - # fire. Also, until the data is read it will consume memory - # that can eventually lead to a 'process out of memory' error. - urlStream.resume() # discard the data - callbackOnce(new Error("URL returned non-success status code: #{res.statusCode} #{url}")) + urlStream.pipe(fileStream); + return urlStream.resume(); // now we are ready to handle the data + } else { + logger.error({statusCode: res.statusCode, url, filePath}, "unexpected status code downloading url to cache"); + // https://nodejs.org/api/http.html#http_class_http_clientrequest + // If you add a 'response' event handler, then you must consume + // the data from the response object, either by calling + // response.read() whenever there is a 'readable' event, or by + // adding a 'data' handler, or by calling the .resume() + // method. Until the data is consumed, the 'end' event will not + // fire. Also, until the data is read it will consume memory + // that can eventually lead to a 'process out of memory' error. + urlStream.resume(); // discard the data + return callbackOnce(new Error(`URL returned non-success status code: ${res.statusCode} ${url}`)); + } + }); + } +}); diff --git a/services/clsi/app/coffee/db.js b/services/clsi/app/coffee/db.js index de48dfdf55..385ad8d3de 100644 --- a/services/clsi/app/coffee/db.js +++ b/services/clsi/app/coffee/db.js @@ -1,55 +1,59 @@ -Sequelize = require("sequelize") -Settings = require("settings-sharelatex") -_ = require("underscore") -logger = require "logger-sharelatex" +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const Sequelize = require("sequelize"); +const Settings = require("settings-sharelatex"); +const _ = require("underscore"); +const logger = require("logger-sharelatex"); -options = _.extend {logging:false}, Settings.mysql.clsi +const options = _.extend({logging:false}, Settings.mysql.clsi); -logger.log dbPath:Settings.mysql.clsi.storage, "connecting to db" +logger.log({dbPath:Settings.mysql.clsi.storage}, "connecting to db"); -sequelize = new Sequelize( +const sequelize = new Sequelize( Settings.mysql.clsi.database, Settings.mysql.clsi.username, Settings.mysql.clsi.password, options -) +); -if Settings.mysql.clsi.dialect == "sqlite" - logger.log "running PRAGMA journal_mode=WAL;" - sequelize.query("PRAGMA journal_mode=WAL;") - sequelize.query("PRAGMA synchronous=OFF;") - sequelize.query("PRAGMA read_uncommitted = true;") +if (Settings.mysql.clsi.dialect === "sqlite") { + logger.log("running PRAGMA journal_mode=WAL;"); + sequelize.query("PRAGMA journal_mode=WAL;"); + sequelize.query("PRAGMA synchronous=OFF;"); + sequelize.query("PRAGMA read_uncommitted = true;"); +} -module.exports = +module.exports = { UrlCache: sequelize.define("UrlCache", { - url: Sequelize.STRING - project_id: Sequelize.STRING + url: Sequelize.STRING, + project_id: Sequelize.STRING, lastModified: Sequelize.DATE }, { indexes: [ {fields: ['url', 'project_id']}, {fields: ['project_id']} ] - }) + }), Project: sequelize.define("Project", { - project_id: {type: Sequelize.STRING, primaryKey: true} + project_id: {type: Sequelize.STRING, primaryKey: true}, lastAccessed: Sequelize.DATE }, { indexes: [ {fields: ['lastAccessed']} ] - }) + }), - op: Sequelize.Op + op: Sequelize.Op, - sync: () -> - logger.log dbPath:Settings.mysql.clsi.storage, "syncing db schema" - sequelize.sync() - .then(-> - logger.log "db sync complete" - ).catch((err)-> - console.log err, "error syncing" - ) + sync() { + logger.log({dbPath:Settings.mysql.clsi.storage}, "syncing db schema"); + return sequelize.sync() + .then(() => logger.log("db sync complete")).catch(err=> console.log(err, "error syncing")); + } +}; From beb6100e25b3b24e4250b96e7acdc65a463cc759 Mon Sep 17 00:00:00 2001 From: decaffeinate <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:14:28 +0100 Subject: [PATCH 518/754] decaffeinate: Run post-processing cleanups on CommandRunner.coffee and 25 other files --- services/clsi/app/coffee/CommandRunner.js | 2 + services/clsi/app/coffee/CompileController.js | 7 +++ services/clsi/app/coffee/CompileManager.js | 43 +++++++++++-------- services/clsi/app/coffee/ContentTypeMapper.js | 5 +++ services/clsi/app/coffee/DbQueue.js | 2 + services/clsi/app/coffee/DockerLockManager.js | 5 +++ services/clsi/app/coffee/DockerRunner.js | 22 +++++++--- services/clsi/app/coffee/DraftModeManager.js | 7 +++ services/clsi/app/coffee/Errors.js | 6 +++ services/clsi/app/coffee/LatexRunner.js | 8 ++++ .../clsi/app/coffee/LocalCommandRunner.js | 8 ++++ services/clsi/app/coffee/LockManager.js | 6 +++ services/clsi/app/coffee/Metrics.js | 2 + .../clsi/app/coffee/OutputCacheManager.js | 5 +++ services/clsi/app/coffee/OutputFileFinder.js | 12 +++++- .../clsi/app/coffee/OutputFileOptimiser.js | 9 ++++ .../app/coffee/ProjectPersistenceManager.js | 6 +++ services/clsi/app/coffee/RequestParser.js | 13 +++++- .../clsi/app/coffee/ResourceStateManager.js | 16 ++++--- services/clsi/app/coffee/ResourceWriter.js | 13 +++++- services/clsi/app/coffee/SafeReader.js | 7 +++ .../app/coffee/StaticServerForbidSymlinks.js | 10 ++++- services/clsi/app/coffee/TikzManager.js | 8 +++- services/clsi/app/coffee/UrlCache.js | 7 +++ services/clsi/app/coffee/UrlFetcher.js | 8 ++++ services/clsi/app/coffee/db.js | 5 +++ 26 files changed, 206 insertions(+), 36 deletions(-) diff --git a/services/clsi/app/coffee/CommandRunner.js b/services/clsi/app/coffee/CommandRunner.js index dd7210a300..1c46ee9564 100644 --- a/services/clsi/app/coffee/CommandRunner.js +++ b/services/clsi/app/coffee/CommandRunner.js @@ -1,3 +1,5 @@ +// TODO: This file was created by bulk-decaffeinate. +// Sanity-check the conversion and remove this comment. /* * decaffeinate suggestions: * DS207: Consider shorter variations of null checks diff --git a/services/clsi/app/coffee/CompileController.js b/services/clsi/app/coffee/CompileController.js index cfdbcfe8d6..60925fc101 100644 --- a/services/clsi/app/coffee/CompileController.js +++ b/services/clsi/app/coffee/CompileController.js @@ -1,3 +1,10 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from diff --git a/services/clsi/app/coffee/CompileManager.js b/services/clsi/app/coffee/CompileManager.js index 82dafd12a7..76fb8b0477 100644 --- a/services/clsi/app/coffee/CompileManager.js +++ b/services/clsi/app/coffee/CompileManager.js @@ -1,3 +1,12 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-return-assign, + no-undef, + 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 @@ -88,13 +97,13 @@ module.exports = (CompileManager = { // only run chktex on LaTeX files (not knitr .Rtex files or any others) const isLaTeXFile = request.rootResourcePath != null ? request.rootResourcePath.match(/\.tex$/i) : undefined; if ((request.check != null) && isLaTeXFile) { - env['CHKTEX_OPTIONS'] = '-nall -e9 -e10 -w15 -w16'; - env['CHKTEX_ULIMIT_OPTIONS'] = '-t 5 -v 64000'; + env.CHKTEX_OPTIONS = '-nall -e9 -e10 -w15 -w16'; + env.CHKTEX_ULIMIT_OPTIONS = '-t 5 -v 64000'; if (request.check === 'error') { - env['CHKTEX_EXIT_ON_ERROR'] = 1; + env.CHKTEX_EXIT_ON_ERROR = 1; } if (request.check === 'validate') { - env['CHKTEX_VALIDATE'] = 1; + env.CHKTEX_VALIDATE = 1; } } @@ -337,7 +346,7 @@ module.exports = (CompileManager = { _parseSynctexFromCodeOutput(output) { const results = []; - for (let line of Array.from(output.split("\n"))) { + for (const line of Array.from(output.split("\n"))) { const [node, page, h, v, width, height] = Array.from(line.split("\t")); if (node === "NODE") { results.push({ @@ -387,7 +396,7 @@ module.exports = (CompileManager = { if (error != null) { return callback(error); } return fs.readFile(compileDir + "/" + file_name + ".wc", "utf-8", function(err, stdout) { if (err != null) { - //call it node_err so sentry doesn't use random path error as unique id so it can't be ignored + // call it node_err so sentry doesn't use random path error as unique id so it can't be ignored logger.err({node_err:err, command, compileDir, project_id, user_id}, "error reading word count output"); return callback(err); } @@ -412,37 +421,37 @@ module.exports = (CompileManager = { errors: 0, messages: "" }; - for (let line of Array.from(output.split("\n"))) { + for (const line of Array.from(output.split("\n"))) { const [data, info] = Array.from(line.split(":")); if (data.indexOf("Encoding") > -1) { - results['encode'] = info.trim(); + results.encode = info.trim(); } if (data.indexOf("in text") > -1) { - results['textWords'] = parseInt(info, 10); + results.textWords = parseInt(info, 10); } if (data.indexOf("in head") > -1) { - results['headWords'] = parseInt(info, 10); + results.headWords = parseInt(info, 10); } if (data.indexOf("outside") > -1) { - results['outside'] = parseInt(info, 10); + results.outside = parseInt(info, 10); } if (data.indexOf("of head") > -1) { - results['headers'] = parseInt(info, 10); + results.headers = parseInt(info, 10); } if (data.indexOf("Number of floats/tables/figures") > -1) { - results['elements'] = parseInt(info, 10); + results.elements = parseInt(info, 10); } if (data.indexOf("Number of math inlines") > -1) { - results['mathInline'] = parseInt(info, 10); + results.mathInline = parseInt(info, 10); } if (data.indexOf("Number of math displayed") > -1) { - results['mathDisplay'] = parseInt(info, 10); + results.mathDisplay = parseInt(info, 10); } if (data === "(errors") { // errors reported as (errors:123) - results['errors'] = parseInt(info, 10); + results.errors = parseInt(info, 10); } if (line.indexOf("!!! ") > -1) { // errors logged as !!! message !!! - results['messages'] += line + "\n"; + results.messages += line + "\n"; } } return results; diff --git a/services/clsi/app/coffee/ContentTypeMapper.js b/services/clsi/app/coffee/ContentTypeMapper.js index c57057f9d8..fdd66d3827 100644 --- a/services/clsi/app/coffee/ContentTypeMapper.js +++ b/services/clsi/app/coffee/ContentTypeMapper.js @@ -1,3 +1,8 @@ +/* eslint-disable + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. let ContentTypeMapper; const Path = require('path'); diff --git a/services/clsi/app/coffee/DbQueue.js b/services/clsi/app/coffee/DbQueue.js index 0f1f8cf1aa..89ff323434 100644 --- a/services/clsi/app/coffee/DbQueue.js +++ b/services/clsi/app/coffee/DbQueue.js @@ -1,3 +1,5 @@ +// TODO: This file was created by bulk-decaffeinate. +// Sanity-check the conversion and remove this comment. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns diff --git a/services/clsi/app/coffee/DockerLockManager.js b/services/clsi/app/coffee/DockerLockManager.js index 9c7deffe46..274ff66c6f 100644 --- a/services/clsi/app/coffee/DockerLockManager.js +++ b/services/clsi/app/coffee/DockerLockManager.js @@ -1,3 +1,8 @@ +/* eslint-disable + handle-callback-err, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from diff --git a/services/clsi/app/coffee/DockerRunner.js b/services/clsi/app/coffee/DockerRunner.js index ab78419ece..dc04b5dfdf 100644 --- a/services/clsi/app/coffee/DockerRunner.js +++ b/services/clsi/app/coffee/DockerRunner.js @@ -1,3 +1,11 @@ +/* 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 @@ -140,7 +148,7 @@ module.exports = (DockerRunner = { return callback(err); } containerReturned = true; - __guard__(options != null ? options.HostConfig : undefined, x => x.SecurityOpt = null); //small log line + __guard__(options != null ? options.HostConfig : undefined, x => x.SecurityOpt = null); // small log line logger.log({err, exitCode, options}, "docker container has exited"); return callbackIfFinished(); }); @@ -164,7 +172,7 @@ module.exports = (DockerRunner = { // merge settings and environment parameter const env = {}; - for (let src of [Settings.clsi.docker.env, environment || {}]) { + for (const src of [Settings.clsi.docker.env, environment || {}]) { for (key in src) { value = src[key]; env[key] = value; } } // set the path based on the image year @@ -173,7 +181,7 @@ module.exports = (DockerRunner = { } else { year = "2014"; } - env['PATH'] = `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/${year}/bin/x86_64-linux/`; + env.PATH = `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/${year}/bin/x86_64-linux/`; const options = { "Cmd" : command, "Image" : image, @@ -208,7 +216,7 @@ module.exports = (DockerRunner = { if ((Settings.path != null ? Settings.path.synctexBinHostPath : undefined) != null) { - options["HostConfig"]["Binds"].push(`${Settings.path.synctexBinHostPath}:/opt/synctex:ro`); + options.HostConfig.Binds.push(`${Settings.path.synctexBinHostPath}:/opt/synctex:ro`); } if (Settings.clsi.docker.seccomp_profile != null) { @@ -254,7 +262,7 @@ module.exports = (DockerRunner = { }) ; const jobs = []; - for (let vol in volumes) { + for (const vol in volumes) { (vol => jobs.push(cb => checkVolume(vol, cb)))(vol); } return async.series(jobs, callback); @@ -279,7 +287,7 @@ module.exports = (DockerRunner = { DockerRunner.attachToContainer(options.name, attachStreamHandler, function(error){ if (error != null) { return callback(error); } return container.start(function(error) { - if ((error != null) && ((error != null ? error.statusCode : undefined) !== 304)) { //already running + if ((error != null) && ((error != null ? error.statusCode : undefined) !== 304)) { // already running return callback(error); } else { return callback(); @@ -434,7 +442,7 @@ module.exports = (DockerRunner = { return dockerode.listContainers({all: true}, function(error, containers) { if (error != null) { return callback(error); } const jobs = []; - for (let container of Array.from(containers || [])) { + for (const container of Array.from(containers || [])) { (container => DockerRunner.examineOldContainer(container, function(err, name, id, ttl) { if ((name.slice(0, 9) === '/project-') && (ttl <= 0)) { diff --git a/services/clsi/app/coffee/DraftModeManager.js b/services/clsi/app/coffee/DraftModeManager.js index 8ddbbd02df..79f39ab24d 100644 --- a/services/clsi/app/coffee/DraftModeManager.js +++ b/services/clsi/app/coffee/DraftModeManager.js @@ -1,3 +1,10 @@ +/* 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 diff --git a/services/clsi/app/coffee/Errors.js b/services/clsi/app/coffee/Errors.js index 3a9ef220c4..e7ace2c2ce 100644 --- a/services/clsi/app/coffee/Errors.js +++ b/services/clsi/app/coffee/Errors.js @@ -1,3 +1,9 @@ +/* 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); diff --git a/services/clsi/app/coffee/LatexRunner.js b/services/clsi/app/coffee/LatexRunner.js index 4c83e084fc..e569df8d16 100644 --- a/services/clsi/app/coffee/LatexRunner.js +++ b/services/clsi/app/coffee/LatexRunner.js @@ -1,3 +1,11 @@ +/* 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: * DS102: Remove unnecessary code created because of implicit returns diff --git a/services/clsi/app/coffee/LocalCommandRunner.js b/services/clsi/app/coffee/LocalCommandRunner.js index 405c51bd74..24c0d8eafb 100644 --- a/services/clsi/app/coffee/LocalCommandRunner.js +++ b/services/clsi/app/coffee/LocalCommandRunner.js @@ -1,3 +1,11 @@ +/* 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 diff --git a/services/clsi/app/coffee/LockManager.js b/services/clsi/app/coffee/LockManager.js index 2405e8ac88..8930fab6ed 100644 --- a/services/clsi/app/coffee/LockManager.js +++ b/services/clsi/app/coffee/LockManager.js @@ -1,3 +1,9 @@ +/* eslint-disable + handle-callback-err, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from diff --git a/services/clsi/app/coffee/Metrics.js b/services/clsi/app/coffee/Metrics.js index 8148d6641c..94623da3fa 100644 --- a/services/clsi/app/coffee/Metrics.js +++ b/services/clsi/app/coffee/Metrics.js @@ -1,2 +1,4 @@ +// TODO: This file was created by bulk-decaffeinate. +// Sanity-check the conversion and remove this comment. module.exports = require("metrics-sharelatex"); diff --git a/services/clsi/app/coffee/OutputCacheManager.js b/services/clsi/app/coffee/OutputCacheManager.js index 6d03a10634..b1bda0e8c5 100644 --- a/services/clsi/app/coffee/OutputCacheManager.js +++ b/services/clsi/app/coffee/OutputCacheManager.js @@ -1,3 +1,8 @@ +/* eslint-disable + handle-callback-err, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from diff --git a/services/clsi/app/coffee/OutputFileFinder.js b/services/clsi/app/coffee/OutputFileFinder.js index f0f837c00c..21a758747d 100644 --- a/services/clsi/app/coffee/OutputFileFinder.js +++ b/services/clsi/app/coffee/OutputFileFinder.js @@ -1,3 +1,11 @@ +/* eslint-disable + handle-callback-err, + no-return-assign, + no-unused-vars, + no-useless-escape, +*/ +// 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 @@ -17,7 +25,7 @@ module.exports = (OutputFileFinder = { findOutputFiles(resources, directory, callback) { if (callback == null) { callback = function(error, outputFiles, allFiles) {}; } const incomingResources = {}; - for (let resource of Array.from(resources)) { + for (const resource of Array.from(resources)) { incomingResources[resource.path] = true; } @@ -28,7 +36,7 @@ module.exports = (OutputFileFinder = { return callback(error); } const outputFiles = []; - for (let file of Array.from(allFiles)) { + for (const file of Array.from(allFiles)) { if (!incomingResources[file]) { outputFiles.push({ path: file, diff --git a/services/clsi/app/coffee/OutputFileOptimiser.js b/services/clsi/app/coffee/OutputFileOptimiser.js index f8302aac5f..149d38462e 100644 --- a/services/clsi/app/coffee/OutputFileOptimiser.js +++ b/services/clsi/app/coffee/OutputFileOptimiser.js @@ -1,3 +1,12 @@ +/* eslint-disable + handle-callback-err, + no-return-assign, + no-undef, + no-unused-vars, + node/no-deprecated-api, +*/ +// 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 diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.js b/services/clsi/app/coffee/ProjectPersistenceManager.js index 7b3d5ee242..856c15667b 100644 --- a/services/clsi/app/coffee/ProjectPersistenceManager.js +++ b/services/clsi/app/coffee/ProjectPersistenceManager.js @@ -1,3 +1,9 @@ +/* eslint-disable + camelcase, + handle-callback-err, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from diff --git a/services/clsi/app/coffee/RequestParser.js b/services/clsi/app/coffee/RequestParser.js index fdfb8bf46b..6641086a54 100644 --- a/services/clsi/app/coffee/RequestParser.js +++ b/services/clsi/app/coffee/RequestParser.js @@ -1,3 +1,14 @@ +/* eslint-disable + handle-callback-err, + no-control-regex, + no-throw-literal, + no-unused-vars, + no-useless-escape, + standard/no-callback-literal, + valid-typeof, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from @@ -172,7 +183,7 @@ module.exports = (RequestParser = { _checkPath(path) { // check that the request does not use a relative path - for (let dir of Array.from(path.split('/'))) { + for (const dir of Array.from(path.split('/'))) { if (dir === '..') { throw "relative path in root resource"; } diff --git a/services/clsi/app/coffee/ResourceStateManager.js b/services/clsi/app/coffee/ResourceStateManager.js index f430c8fdfe..45cfdc6118 100644 --- a/services/clsi/app/coffee/ResourceStateManager.js +++ b/services/clsi/app/coffee/ResourceStateManager.js @@ -1,3 +1,9 @@ +/* eslint-disable + handle-callback-err, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from @@ -62,10 +68,10 @@ module.exports = (ResourceStateManager = { if (bytesRead === size) { logger.error({file:stateFile, size, bytesRead}, "project state file truncated"); } - const array = __guard__(result != null ? result.toString() : undefined, x => x.split("\n")) || [], - adjustedLength = Math.max(array.length, 1), - resourceList = array.slice(0, adjustedLength - 1), - oldState = array[adjustedLength - 1]; + const array = __guard__(result != null ? result.toString() : undefined, x => x.split("\n")) || []; + const adjustedLength = Math.max(array.length, 1); + const resourceList = array.slice(0, adjustedLength - 1); + const oldState = array[adjustedLength - 1]; const newState = `stateHash:${state}`; logger.log({state, oldState, basePath, stateMatches: (newState === oldState)}, "checking sync state"); if (newState !== oldState) { @@ -82,7 +88,7 @@ module.exports = (ResourceStateManager = { let file; if (callback == null) { callback = function(error) {}; } for (file of Array.from(resources || [])) { - for (let dir of Array.from(__guard__(file != null ? file.path : undefined, x => x.split('/')))) { + for (const dir of Array.from(__guard__(file != null ? file.path : undefined, x => x.split('/')))) { if (dir === '..') { return callback(new Error("relative path in resource file list")); } diff --git a/services/clsi/app/coffee/ResourceWriter.js b/services/clsi/app/coffee/ResourceWriter.js index 0044ad935e..028fc533d5 100644 --- a/services/clsi/app/coffee/ResourceWriter.js +++ b/services/clsi/app/coffee/ResourceWriter.js @@ -1,3 +1,12 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-return-assign, + no-unused-vars, + no-useless-escape, +*/ +// 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 @@ -105,7 +114,7 @@ module.exports = (ResourceWriter = { if (error != null) { return callback(error); } const jobs = []; - for (let file of Array.from(outputFiles || [])) { + for (const file of Array.from(outputFiles || [])) { (function(file) { const { path } = file; let should_delete = true; @@ -182,7 +191,7 @@ module.exports = (ResourceWriter = { logger.err({err, project_id, path, resource_url:resource.url, modified:resource.modified}, "error downloading file for resources"); } return callback(); - }); //try and continue compiling even if http resource can not be downloaded at this time + }); // try and continue compiling even if http resource can not be downloaded at this time } else { const process = require("process"); fs.writeFile(path, resource.content, callback); diff --git a/services/clsi/app/coffee/SafeReader.js b/services/clsi/app/coffee/SafeReader.js index f1a6349465..2fd599b301 100644 --- a/services/clsi/app/coffee/SafeReader.js +++ b/services/clsi/app/coffee/SafeReader.js @@ -1,3 +1,10 @@ +/* eslint-disable + handle-callback-err, + no-unused-vars, + node/no-deprecated-api, +*/ +// 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 diff --git a/services/clsi/app/coffee/StaticServerForbidSymlinks.js b/services/clsi/app/coffee/StaticServerForbidSymlinks.js index 90a8879d1f..8ac3e489be 100644 --- a/services/clsi/app/coffee/StaticServerForbidSymlinks.js +++ b/services/clsi/app/coffee/StaticServerForbidSymlinks.js @@ -1,3 +1,11 @@ +/* eslint-disable + camelcase, + no-cond-assign, + no-unused-vars, + node/no-deprecated-api, +*/ +// 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 @@ -28,7 +36,7 @@ module.exports = (ForbidSymlinks = function(staticFn, root, options) { return res.sendStatus(404); } // check that the file does not use a relative path - for (let dir of Array.from(file.split('/'))) { + for (const dir of Array.from(file.split('/'))) { if (dir === '..') { logger.warn({path}, "attempt to use a relative path"); return res.sendStatus(404); diff --git a/services/clsi/app/coffee/TikzManager.js b/services/clsi/app/coffee/TikzManager.js index fb5264474a..9fa4a93604 100644 --- a/services/clsi/app/coffee/TikzManager.js +++ b/services/clsi/app/coffee/TikzManager.js @@ -1,3 +1,9 @@ +/* eslint-disable + handle-callback-err, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from @@ -21,7 +27,7 @@ module.exports = (TikzManager = { checkMainFile(compileDir, mainFile, resources, callback) { // if there's already an output.tex file, we don't want to touch it if (callback == null) { callback = function(error, needsMainFile) {}; } - for (let resource of Array.from(resources)) { + for (const resource of Array.from(resources)) { if (resource.path === "output.tex") { logger.log({compileDir, mainFile}, "output.tex already in resources"); return callback(null, false); diff --git a/services/clsi/app/coffee/UrlCache.js b/services/clsi/app/coffee/UrlCache.js index 9a199688dd..ade815b2cf 100644 --- a/services/clsi/app/coffee/UrlCache.js +++ b/services/clsi/app/coffee/UrlCache.js @@ -1,3 +1,10 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-return-assign, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from diff --git a/services/clsi/app/coffee/UrlFetcher.js b/services/clsi/app/coffee/UrlFetcher.js index ea20956000..fec397c1de 100644 --- a/services/clsi/app/coffee/UrlFetcher.js +++ b/services/clsi/app/coffee/UrlFetcher.js @@ -1,3 +1,11 @@ +/* eslint-disable + handle-callback-err, + no-return-assign, + no-unused-vars, + node/no-deprecated-api, +*/ +// 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 diff --git a/services/clsi/app/coffee/db.js b/services/clsi/app/coffee/db.js index 385ad8d3de..c5dd980040 100644 --- a/services/clsi/app/coffee/db.js +++ b/services/clsi/app/coffee/db.js @@ -1,3 +1,8 @@ +/* eslint-disable + no-console, +*/ +// 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 From 3af6bdd58806e03726ed43711c8e1c9665fe9c51 Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:14:34 +0100 Subject: [PATCH 519/754] decaffeinate: rename app/coffee dir to app/js --- services/clsi/app/{coffee => js}/CommandRunner.js | 0 services/clsi/app/{coffee => js}/CompileController.js | 0 services/clsi/app/{coffee => js}/CompileManager.js | 0 services/clsi/app/{coffee => js}/ContentTypeMapper.js | 0 services/clsi/app/{coffee => js}/DbQueue.js | 0 services/clsi/app/{coffee => js}/DockerLockManager.js | 0 services/clsi/app/{coffee => js}/DockerRunner.js | 0 services/clsi/app/{coffee => js}/DraftModeManager.js | 0 services/clsi/app/{coffee => js}/Errors.js | 0 services/clsi/app/{coffee => js}/LatexRunner.js | 0 services/clsi/app/{coffee => js}/LocalCommandRunner.js | 0 services/clsi/app/{coffee => js}/LockManager.js | 0 services/clsi/app/{coffee => js}/Metrics.js | 0 services/clsi/app/{coffee => js}/OutputCacheManager.js | 0 services/clsi/app/{coffee => js}/OutputFileFinder.js | 0 services/clsi/app/{coffee => js}/OutputFileOptimiser.js | 0 services/clsi/app/{coffee => js}/ProjectPersistenceManager.js | 0 services/clsi/app/{coffee => js}/RequestParser.js | 0 services/clsi/app/{coffee => js}/ResourceStateManager.js | 0 services/clsi/app/{coffee => js}/ResourceWriter.js | 0 services/clsi/app/{coffee => js}/SafeReader.js | 0 services/clsi/app/{coffee => js}/StaticServerForbidSymlinks.js | 0 services/clsi/app/{coffee => js}/TikzManager.js | 0 services/clsi/app/{coffee => js}/UrlCache.js | 0 services/clsi/app/{coffee => js}/UrlFetcher.js | 0 services/clsi/app/{coffee => js}/db.js | 0 26 files changed, 0 insertions(+), 0 deletions(-) rename services/clsi/app/{coffee => js}/CommandRunner.js (100%) rename services/clsi/app/{coffee => js}/CompileController.js (100%) rename services/clsi/app/{coffee => js}/CompileManager.js (100%) rename services/clsi/app/{coffee => js}/ContentTypeMapper.js (100%) rename services/clsi/app/{coffee => js}/DbQueue.js (100%) rename services/clsi/app/{coffee => js}/DockerLockManager.js (100%) rename services/clsi/app/{coffee => js}/DockerRunner.js (100%) rename services/clsi/app/{coffee => js}/DraftModeManager.js (100%) rename services/clsi/app/{coffee => js}/Errors.js (100%) rename services/clsi/app/{coffee => js}/LatexRunner.js (100%) rename services/clsi/app/{coffee => js}/LocalCommandRunner.js (100%) rename services/clsi/app/{coffee => js}/LockManager.js (100%) rename services/clsi/app/{coffee => js}/Metrics.js (100%) rename services/clsi/app/{coffee => js}/OutputCacheManager.js (100%) rename services/clsi/app/{coffee => js}/OutputFileFinder.js (100%) rename services/clsi/app/{coffee => js}/OutputFileOptimiser.js (100%) rename services/clsi/app/{coffee => js}/ProjectPersistenceManager.js (100%) rename services/clsi/app/{coffee => js}/RequestParser.js (100%) rename services/clsi/app/{coffee => js}/ResourceStateManager.js (100%) rename services/clsi/app/{coffee => js}/ResourceWriter.js (100%) rename services/clsi/app/{coffee => js}/SafeReader.js (100%) rename services/clsi/app/{coffee => js}/StaticServerForbidSymlinks.js (100%) rename services/clsi/app/{coffee => js}/TikzManager.js (100%) rename services/clsi/app/{coffee => js}/UrlCache.js (100%) rename services/clsi/app/{coffee => js}/UrlFetcher.js (100%) rename services/clsi/app/{coffee => js}/db.js (100%) diff --git a/services/clsi/app/coffee/CommandRunner.js b/services/clsi/app/js/CommandRunner.js similarity index 100% rename from services/clsi/app/coffee/CommandRunner.js rename to services/clsi/app/js/CommandRunner.js diff --git a/services/clsi/app/coffee/CompileController.js b/services/clsi/app/js/CompileController.js similarity index 100% rename from services/clsi/app/coffee/CompileController.js rename to services/clsi/app/js/CompileController.js diff --git a/services/clsi/app/coffee/CompileManager.js b/services/clsi/app/js/CompileManager.js similarity index 100% rename from services/clsi/app/coffee/CompileManager.js rename to services/clsi/app/js/CompileManager.js diff --git a/services/clsi/app/coffee/ContentTypeMapper.js b/services/clsi/app/js/ContentTypeMapper.js similarity index 100% rename from services/clsi/app/coffee/ContentTypeMapper.js rename to services/clsi/app/js/ContentTypeMapper.js diff --git a/services/clsi/app/coffee/DbQueue.js b/services/clsi/app/js/DbQueue.js similarity index 100% rename from services/clsi/app/coffee/DbQueue.js rename to services/clsi/app/js/DbQueue.js diff --git a/services/clsi/app/coffee/DockerLockManager.js b/services/clsi/app/js/DockerLockManager.js similarity index 100% rename from services/clsi/app/coffee/DockerLockManager.js rename to services/clsi/app/js/DockerLockManager.js diff --git a/services/clsi/app/coffee/DockerRunner.js b/services/clsi/app/js/DockerRunner.js similarity index 100% rename from services/clsi/app/coffee/DockerRunner.js rename to services/clsi/app/js/DockerRunner.js diff --git a/services/clsi/app/coffee/DraftModeManager.js b/services/clsi/app/js/DraftModeManager.js similarity index 100% rename from services/clsi/app/coffee/DraftModeManager.js rename to services/clsi/app/js/DraftModeManager.js diff --git a/services/clsi/app/coffee/Errors.js b/services/clsi/app/js/Errors.js similarity index 100% rename from services/clsi/app/coffee/Errors.js rename to services/clsi/app/js/Errors.js diff --git a/services/clsi/app/coffee/LatexRunner.js b/services/clsi/app/js/LatexRunner.js similarity index 100% rename from services/clsi/app/coffee/LatexRunner.js rename to services/clsi/app/js/LatexRunner.js diff --git a/services/clsi/app/coffee/LocalCommandRunner.js b/services/clsi/app/js/LocalCommandRunner.js similarity index 100% rename from services/clsi/app/coffee/LocalCommandRunner.js rename to services/clsi/app/js/LocalCommandRunner.js diff --git a/services/clsi/app/coffee/LockManager.js b/services/clsi/app/js/LockManager.js similarity index 100% rename from services/clsi/app/coffee/LockManager.js rename to services/clsi/app/js/LockManager.js diff --git a/services/clsi/app/coffee/Metrics.js b/services/clsi/app/js/Metrics.js similarity index 100% rename from services/clsi/app/coffee/Metrics.js rename to services/clsi/app/js/Metrics.js diff --git a/services/clsi/app/coffee/OutputCacheManager.js b/services/clsi/app/js/OutputCacheManager.js similarity index 100% rename from services/clsi/app/coffee/OutputCacheManager.js rename to services/clsi/app/js/OutputCacheManager.js diff --git a/services/clsi/app/coffee/OutputFileFinder.js b/services/clsi/app/js/OutputFileFinder.js similarity index 100% rename from services/clsi/app/coffee/OutputFileFinder.js rename to services/clsi/app/js/OutputFileFinder.js diff --git a/services/clsi/app/coffee/OutputFileOptimiser.js b/services/clsi/app/js/OutputFileOptimiser.js similarity index 100% rename from services/clsi/app/coffee/OutputFileOptimiser.js rename to services/clsi/app/js/OutputFileOptimiser.js diff --git a/services/clsi/app/coffee/ProjectPersistenceManager.js b/services/clsi/app/js/ProjectPersistenceManager.js similarity index 100% rename from services/clsi/app/coffee/ProjectPersistenceManager.js rename to services/clsi/app/js/ProjectPersistenceManager.js diff --git a/services/clsi/app/coffee/RequestParser.js b/services/clsi/app/js/RequestParser.js similarity index 100% rename from services/clsi/app/coffee/RequestParser.js rename to services/clsi/app/js/RequestParser.js diff --git a/services/clsi/app/coffee/ResourceStateManager.js b/services/clsi/app/js/ResourceStateManager.js similarity index 100% rename from services/clsi/app/coffee/ResourceStateManager.js rename to services/clsi/app/js/ResourceStateManager.js diff --git a/services/clsi/app/coffee/ResourceWriter.js b/services/clsi/app/js/ResourceWriter.js similarity index 100% rename from services/clsi/app/coffee/ResourceWriter.js rename to services/clsi/app/js/ResourceWriter.js diff --git a/services/clsi/app/coffee/SafeReader.js b/services/clsi/app/js/SafeReader.js similarity index 100% rename from services/clsi/app/coffee/SafeReader.js rename to services/clsi/app/js/SafeReader.js diff --git a/services/clsi/app/coffee/StaticServerForbidSymlinks.js b/services/clsi/app/js/StaticServerForbidSymlinks.js similarity index 100% rename from services/clsi/app/coffee/StaticServerForbidSymlinks.js rename to services/clsi/app/js/StaticServerForbidSymlinks.js diff --git a/services/clsi/app/coffee/TikzManager.js b/services/clsi/app/js/TikzManager.js similarity index 100% rename from services/clsi/app/coffee/TikzManager.js rename to services/clsi/app/js/TikzManager.js diff --git a/services/clsi/app/coffee/UrlCache.js b/services/clsi/app/js/UrlCache.js similarity index 100% rename from services/clsi/app/coffee/UrlCache.js rename to services/clsi/app/js/UrlCache.js diff --git a/services/clsi/app/coffee/UrlFetcher.js b/services/clsi/app/js/UrlFetcher.js similarity index 100% rename from services/clsi/app/coffee/UrlFetcher.js rename to services/clsi/app/js/UrlFetcher.js diff --git a/services/clsi/app/coffee/db.js b/services/clsi/app/js/db.js similarity index 100% rename from services/clsi/app/coffee/db.js rename to services/clsi/app/js/db.js From 8729acd48cb54a550b06e5c790caa25703e9f5b2 Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:14:37 +0100 Subject: [PATCH 520/754] prettier: convert app/js decaffeinated files to Prettier format --- services/clsi/app/js/CommandRunner.js | 18 +- services/clsi/app/js/CompileController.js | 356 +++--- services/clsi/app/js/CompileManager.js | 1070 ++++++++++------- services/clsi/app/js/ContentTypeMapper.js | 53 +- services/clsi/app/js/DbQueue.js | 18 +- services/clsi/app/js/DockerLockManager.js | 166 +-- services/clsi/app/js/DockerRunner.js | 1011 +++++++++------- services/clsi/app/js/DraftModeManager.js | 73 +- services/clsi/app/js/Errors.js | 48 +- services/clsi/app/js/LatexRunner.js | 273 +++-- services/clsi/app/js/LocalCommandRunner.js | 117 +- services/clsi/app/js/LockManager.js | 100 +- services/clsi/app/js/Metrics.js | 3 +- services/clsi/app/js/OutputCacheManager.js | 602 ++++++---- services/clsi/app/js/OutputFileFinder.js | 155 ++- services/clsi/app/js/OutputFileOptimiser.js | 152 +-- .../clsi/app/js/ProjectPersistenceManager.js | 242 ++-- services/clsi/app/js/RequestParser.js | 334 ++--- services/clsi/app/js/ResourceStateManager.js | 224 ++-- services/clsi/app/js/ResourceWriter.js | 511 +++++--- services/clsi/app/js/SafeReader.js | 73 +- .../clsi/app/js/StaticServerForbidSymlinks.js | 128 +- services/clsi/app/js/TikzManager.js | 118 +- services/clsi/app/js/UrlCache.js | 420 ++++--- services/clsi/app/js/UrlFetcher.js | 166 +-- services/clsi/app/js/db.js | 89 +- 26 files changed, 3881 insertions(+), 2639 deletions(-) diff --git a/services/clsi/app/js/CommandRunner.js b/services/clsi/app/js/CommandRunner.js index 1c46ee9564..8e07dacf6d 100644 --- a/services/clsi/app/js/CommandRunner.js +++ b/services/clsi/app/js/CommandRunner.js @@ -5,16 +5,16 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let commandRunnerPath; -const Settings = require("settings-sharelatex"); -const logger = require("logger-sharelatex"); +let commandRunnerPath +const Settings = require('settings-sharelatex') +const logger = require('logger-sharelatex') if ((Settings.clsi != null ? Settings.clsi.dockerRunner : undefined) === true) { - commandRunnerPath = "./DockerRunner"; -} else { - commandRunnerPath = "./LocalCommandRunner"; + commandRunnerPath = './DockerRunner' +} else { + commandRunnerPath = './LocalCommandRunner' } -logger.info({commandRunnerPath}, "selecting command runner for clsi"); -const CommandRunner = require(commandRunnerPath); +logger.info({ commandRunnerPath }, 'selecting command runner for clsi') +const CommandRunner = require(commandRunnerPath) -module.exports = CommandRunner; +module.exports = CommandRunner diff --git a/services/clsi/app/js/CompileController.js b/services/clsi/app/js/CompileController.js index 60925fc101..e146b62c0a 100644 --- a/services/clsi/app/js/CompileController.js +++ b/services/clsi/app/js/CompileController.js @@ -12,159 +12,227 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let CompileController; -const RequestParser = require("./RequestParser"); -const CompileManager = require("./CompileManager"); -const Settings = require("settings-sharelatex"); -const Metrics = require("./Metrics"); -const ProjectPersistenceManager = require("./ProjectPersistenceManager"); -const logger = require("logger-sharelatex"); -const Errors = require("./Errors"); +let CompileController +const RequestParser = require('./RequestParser') +const CompileManager = require('./CompileManager') +const Settings = require('settings-sharelatex') +const Metrics = require('./Metrics') +const ProjectPersistenceManager = require('./ProjectPersistenceManager') +const logger = require('logger-sharelatex') +const Errors = require('./Errors') -module.exports = (CompileController = { - compile(req, res, next) { - if (next == null) { next = function(error) {}; } - const timer = new Metrics.Timer("compile-request"); - return RequestParser.parse(req.body, function(error, request) { - if (error != null) { return next(error); } - request.project_id = req.params.project_id; - if (req.params.user_id != null) { request.user_id = req.params.user_id; } - return ProjectPersistenceManager.markProjectAsJustAccessed(request.project_id, function(error) { - if (error != null) { return next(error); } - return CompileManager.doCompileWithLock(request, function(error, outputFiles) { - let code, status; - if (outputFiles == null) { outputFiles = []; } - if (error instanceof Errors.AlreadyCompilingError) { - code = 423; // Http 423 Locked - status = "compile-in-progress"; - } else if (error instanceof Errors.FilesOutOfSyncError) { - code = 409; // Http 409 Conflict - status = "retry"; - } else if (error != null ? error.terminated : undefined) { - status = "terminated"; - } else if (error != null ? error.validate : undefined) { - status = `validation-${error.validate}`; - } else if (error != null ? error.timedout : undefined) { - status = "timedout"; - logger.log({err: error, project_id: request.project_id}, "timeout running compile"); - } else if (error != null) { - status = "error"; - code = 500; - logger.warn({err: error, project_id: request.project_id}, "error running compile"); - } else { - let file; - status = "failure"; - for (file of Array.from(outputFiles)) { - if (file.path != null ? file.path.match(/output\.pdf$/) : undefined) { - status = "success"; - } - } +module.exports = CompileController = { + compile(req, res, next) { + if (next == null) { + next = function(error) {} + } + const timer = new Metrics.Timer('compile-request') + return RequestParser.parse(req.body, function(error, request) { + if (error != null) { + return next(error) + } + request.project_id = req.params.project_id + if (req.params.user_id != null) { + request.user_id = req.params.user_id + } + return ProjectPersistenceManager.markProjectAsJustAccessed( + request.project_id, + function(error) { + if (error != null) { + return next(error) + } + return CompileManager.doCompileWithLock(request, function( + error, + outputFiles + ) { + let code, status + if (outputFiles == null) { + outputFiles = [] + } + if (error instanceof Errors.AlreadyCompilingError) { + code = 423 // Http 423 Locked + status = 'compile-in-progress' + } else if (error instanceof Errors.FilesOutOfSyncError) { + code = 409 // Http 409 Conflict + status = 'retry' + } else if (error != null ? error.terminated : undefined) { + status = 'terminated' + } else if (error != null ? error.validate : undefined) { + status = `validation-${error.validate}` + } else if (error != null ? error.timedout : undefined) { + status = 'timedout' + logger.log( + { err: error, project_id: request.project_id }, + 'timeout running compile' + ) + } else if (error != null) { + status = 'error' + code = 500 + logger.warn( + { err: error, project_id: request.project_id }, + 'error running compile' + ) + } else { + let file + status = 'failure' + for (file of Array.from(outputFiles)) { + if ( + file.path != null + ? file.path.match(/output\.pdf$/) + : undefined + ) { + status = 'success' + } + } - if (status === "failure") { - logger.warn({project_id: request.project_id, outputFiles}, "project failed to compile successfully, no output.pdf generated"); - } + if (status === 'failure') { + logger.warn( + { project_id: request.project_id, outputFiles }, + 'project failed to compile successfully, no output.pdf generated' + ) + } - // log an error if any core files are found - for (file of Array.from(outputFiles)) { - if (file.path === "core") { - logger.error({project_id:request.project_id, req, outputFiles}, "core file found in output"); - } - } - } + // log an error if any core files are found + for (file of Array.from(outputFiles)) { + if (file.path === 'core') { + logger.error( + { project_id: request.project_id, req, outputFiles }, + 'core file found in output' + ) + } + } + } - if (error != null) { - outputFiles = error.outputFiles || []; - } + if (error != null) { + outputFiles = error.outputFiles || [] + } - timer.done(); - return res.status(code || 200).send({ - compile: { - status, - error: (error != null ? error.message : undefined) || error, - outputFiles: outputFiles.map(file => - ({ - url: - `${Settings.apis.clsi.url}/project/${request.project_id}` + - ((request.user_id != null) ? `/user/${request.user_id}` : "") + - ((file.build != null) ? `/build/${file.build}` : "") + - `/output/${file.path}`, - path: file.path, - type: file.type, - build: file.build - }) - ) - } - }); - }); - }); - }); - }, + timer.done() + return res.status(code || 200).send({ + compile: { + status, + error: (error != null ? error.message : undefined) || error, + outputFiles: outputFiles.map(file => ({ + url: + `${Settings.apis.clsi.url}/project/${request.project_id}` + + (request.user_id != null + ? `/user/${request.user_id}` + : '') + + (file.build != null ? `/build/${file.build}` : '') + + `/output/${file.path}`, + path: file.path, + type: file.type, + build: file.build + })) + } + }) + }) + } + ) + }) + }, - stopCompile(req, res, next) { - const {project_id, user_id} = req.params; - return CompileManager.stopCompile(project_id, user_id, function(error) { - if (error != null) { return next(error); } - return res.sendStatus(204); - }); - }, + stopCompile(req, res, next) { + const { project_id, user_id } = req.params + return CompileManager.stopCompile(project_id, user_id, function(error) { + if (error != null) { + return next(error) + } + return res.sendStatus(204) + }) + }, - clearCache(req, res, next) { - if (next == null) { next = function(error) {}; } - return ProjectPersistenceManager.clearProject(req.params.project_id, req.params.user_id, function(error) { - if (error != null) { return next(error); } - return res.sendStatus(204); - }); - }, // No content + clearCache(req, res, next) { + if (next == null) { + next = function(error) {} + } + return ProjectPersistenceManager.clearProject( + req.params.project_id, + req.params.user_id, + function(error) { + if (error != null) { + return next(error) + } + return res.sendStatus(204) + } + ) + }, // No content - syncFromCode(req, res, next) { - if (next == null) { next = function(error) {}; } - const { file } = req.query; - const line = parseInt(req.query.line, 10); - const column = parseInt(req.query.column, 10); - const { project_id } = req.params; - const { user_id } = req.params; - return CompileManager.syncFromCode(project_id, user_id, file, line, column, function(error, pdfPositions) { - if (error != null) { return next(error); } - return res.json({ - pdf: pdfPositions - }); - }); - }, + syncFromCode(req, res, next) { + if (next == null) { + next = function(error) {} + } + const { file } = req.query + const line = parseInt(req.query.line, 10) + const column = parseInt(req.query.column, 10) + const { project_id } = req.params + const { user_id } = req.params + return CompileManager.syncFromCode( + project_id, + user_id, + file, + line, + column, + function(error, pdfPositions) { + if (error != null) { + return next(error) + } + return res.json({ + pdf: pdfPositions + }) + } + ) + }, - syncFromPdf(req, res, next) { - if (next == null) { next = function(error) {}; } - const page = parseInt(req.query.page, 10); - const h = parseFloat(req.query.h); - const v = parseFloat(req.query.v); - const { project_id } = req.params; - const { user_id } = req.params; - return CompileManager.syncFromPdf(project_id, user_id, page, h, v, function(error, codePositions) { - if (error != null) { return next(error); } - return res.json({ - code: codePositions - }); - }); - }, + syncFromPdf(req, res, next) { + if (next == null) { + next = function(error) {} + } + const page = parseInt(req.query.page, 10) + const h = parseFloat(req.query.h) + const v = parseFloat(req.query.v) + const { project_id } = req.params + const { user_id } = req.params + return CompileManager.syncFromPdf(project_id, user_id, page, h, v, function( + error, + codePositions + ) { + if (error != null) { + return next(error) + } + return res.json({ + code: codePositions + }) + }) + }, - wordcount(req, res, next) { - if (next == null) { next = function(error) {}; } - const file = req.query.file || "main.tex"; - const { project_id } = req.params; - const { user_id } = req.params; - const { image } = req.query; - logger.log({image, file, project_id}, "word count request"); + wordcount(req, res, next) { + if (next == null) { + next = function(error) {} + } + const file = req.query.file || 'main.tex' + const { project_id } = req.params + const { user_id } = req.params + const { image } = req.query + logger.log({ image, file, project_id }, 'word count request') - return CompileManager.wordcount(project_id, user_id, file, image, function(error, result) { - if (error != null) { return next(error); } - return res.json({ - texcount: result - }); - }); - }, - - status(req, res, next ){ - if (next == null) { next = function(error){}; } - return res.send("OK"); - } -}); + return CompileManager.wordcount(project_id, user_id, file, image, function( + error, + result + ) { + if (error != null) { + return next(error) + } + return res.json({ + texcount: result + }) + }) + }, + status(req, res, next) { + if (next == null) { + next = function(error) {} + } + return res.send('OK') + } +} diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js index 76fb8b0477..3bf54bc75b 100644 --- a/services/clsi/app/js/CompileManager.js +++ b/services/clsi/app/js/CompileManager.js @@ -15,449 +15,691 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let CompileManager; -const ResourceWriter = require("./ResourceWriter"); -const LatexRunner = require("./LatexRunner"); -const OutputFileFinder = require("./OutputFileFinder"); -const OutputCacheManager = require("./OutputCacheManager"); -const Settings = require("settings-sharelatex"); -const Path = require("path"); -const logger = require("logger-sharelatex"); -const Metrics = require("./Metrics"); -const child_process = require("child_process"); -const DraftModeManager = require("./DraftModeManager"); -const TikzManager = require("./TikzManager"); -const LockManager = require("./LockManager"); -const fs = require("fs"); -const fse = require("fs-extra"); -const os = require("os"); -const async = require("async"); -const Errors = require('./Errors'); -const CommandRunner = require("./CommandRunner"); +let CompileManager +const ResourceWriter = require('./ResourceWriter') +const LatexRunner = require('./LatexRunner') +const OutputFileFinder = require('./OutputFileFinder') +const OutputCacheManager = require('./OutputCacheManager') +const Settings = require('settings-sharelatex') +const Path = require('path') +const logger = require('logger-sharelatex') +const Metrics = require('./Metrics') +const child_process = require('child_process') +const DraftModeManager = require('./DraftModeManager') +const TikzManager = require('./TikzManager') +const LockManager = require('./LockManager') +const fs = require('fs') +const fse = require('fs-extra') +const os = require('os') +const async = require('async') +const Errors = require('./Errors') +const CommandRunner = require('./CommandRunner') const getCompileName = function(project_id, user_id) { - if (user_id != null) { return `${project_id}-${user_id}`; } else { return project_id; } -}; + if (user_id != null) { + return `${project_id}-${user_id}` + } else { + return project_id + } +} -const getCompileDir = (project_id, user_id) => Path.join(Settings.path.compilesDir, getCompileName(project_id, user_id)); +const getCompileDir = (project_id, user_id) => + Path.join(Settings.path.compilesDir, getCompileName(project_id, user_id)) -module.exports = (CompileManager = { +module.exports = CompileManager = { + doCompileWithLock(request, callback) { + if (callback == null) { + callback = function(error, outputFiles) {} + } + const compileDir = getCompileDir(request.project_id, request.user_id) + const lockFile = Path.join(compileDir, '.project-lock') + // use a .project-lock file in the compile directory to prevent + // simultaneous compiles + return fse.ensureDir(compileDir, function(error) { + if (error != null) { + return callback(error) + } + return LockManager.runWithLock( + lockFile, + releaseLock => CompileManager.doCompile(request, releaseLock), + callback + ) + }) + }, - doCompileWithLock(request, callback) { - if (callback == null) { callback = function(error, outputFiles) {}; } - const compileDir = getCompileDir(request.project_id, request.user_id); - const lockFile = Path.join(compileDir, ".project-lock"); - // use a .project-lock file in the compile directory to prevent - // simultaneous compiles - return fse.ensureDir(compileDir, function(error) { - if (error != null) { return callback(error); } - return LockManager.runWithLock(lockFile, releaseLock => CompileManager.doCompile(request, releaseLock) - , callback); - }); - }, + doCompile(request, callback) { + if (callback == null) { + callback = function(error, outputFiles) {} + } + const compileDir = getCompileDir(request.project_id, request.user_id) + let timer = new Metrics.Timer('write-to-disk') + logger.log( + { project_id: request.project_id, user_id: request.user_id }, + 'syncing resources to disk' + ) + return ResourceWriter.syncResourcesToDisk(request, compileDir, function( + error, + resourceList + ) { + // NOTE: resourceList is insecure, it should only be used to exclude files from the output list + if (error != null && error instanceof Errors.FilesOutOfSyncError) { + logger.warn( + { project_id: request.project_id, user_id: request.user_id }, + 'files out of sync, please retry' + ) + return callback(error) + } else if (error != null) { + logger.err( + { + err: error, + project_id: request.project_id, + user_id: request.user_id + }, + 'error writing resources to disk' + ) + return callback(error) + } + logger.log( + { + project_id: request.project_id, + user_id: request.user_id, + time_taken: Date.now() - timer.start + }, + 'written files to disk' + ) + timer.done() - doCompile(request, callback) { - if (callback == null) { callback = function(error, outputFiles) {}; } - const compileDir = getCompileDir(request.project_id, request.user_id); - let timer = new Metrics.Timer("write-to-disk"); - logger.log({project_id: request.project_id, user_id: request.user_id}, "syncing resources to disk"); - return ResourceWriter.syncResourcesToDisk(request, compileDir, function(error, resourceList) { - // NOTE: resourceList is insecure, it should only be used to exclude files from the output list - if ((error != null) && error instanceof Errors.FilesOutOfSyncError) { - logger.warn({project_id: request.project_id, user_id: request.user_id}, "files out of sync, please retry"); - return callback(error); - } else if (error != null) { - logger.err({err:error, project_id: request.project_id, user_id: request.user_id}, "error writing resources to disk"); - return callback(error); - } - logger.log({project_id: request.project_id, user_id: request.user_id, time_taken: Date.now() - timer.start}, "written files to disk"); - timer.done(); + const injectDraftModeIfRequired = function(callback) { + if (request.draft) { + return DraftModeManager.injectDraftMode( + Path.join(compileDir, request.rootResourcePath), + callback + ) + } else { + return callback() + } + } - const injectDraftModeIfRequired = function(callback) { - if (request.draft) { - return DraftModeManager.injectDraftMode(Path.join(compileDir, request.rootResourcePath), callback); - } else { - return callback(); - } - }; + const createTikzFileIfRequired = callback => + TikzManager.checkMainFile( + compileDir, + request.rootResourcePath, + resourceList, + function(error, needsMainFile) { + if (error != null) { + return callback(error) + } + if (needsMainFile) { + return TikzManager.injectOutputFile( + compileDir, + request.rootResourcePath, + callback + ) + } else { + return callback() + } + } + ) + // set up environment variables for chktex + const env = {} + // only run chktex on LaTeX files (not knitr .Rtex files or any others) + const isLaTeXFile = + request.rootResourcePath != null + ? request.rootResourcePath.match(/\.tex$/i) + : undefined + if (request.check != null && isLaTeXFile) { + env.CHKTEX_OPTIONS = '-nall -e9 -e10 -w15 -w16' + env.CHKTEX_ULIMIT_OPTIONS = '-t 5 -v 64000' + if (request.check === 'error') { + env.CHKTEX_EXIT_ON_ERROR = 1 + } + if (request.check === 'validate') { + env.CHKTEX_VALIDATE = 1 + } + } - const createTikzFileIfRequired = callback => - TikzManager.checkMainFile(compileDir, request.rootResourcePath, resourceList, function(error, needsMainFile) { - if (error != null) { return callback(error); } - if (needsMainFile) { - return TikzManager.injectOutputFile(compileDir, request.rootResourcePath, callback); - } else { - return callback(); - } - }) - ; + // apply a series of file modifications/creations for draft mode and tikz + return async.series( + [injectDraftModeIfRequired, createTikzFileIfRequired], + function(error) { + if (error != null) { + return callback(error) + } + timer = new Metrics.Timer('run-compile') + // find the image tag to log it as a metric, e.g. 2015.1 (convert . to - for graphite) + let tag = + __guard__( + __guard__( + request.imageName != null + ? request.imageName.match(/:(.*)/) + : undefined, + x1 => x1[1] + ), + x => x.replace(/\./g, '-') + ) || 'default' + if (!request.project_id.match(/^[0-9a-f]{24}$/)) { + tag = 'other' + } // exclude smoke test + Metrics.inc('compiles') + Metrics.inc(`compiles-with-image.${tag}`) + const compileName = getCompileName( + request.project_id, + request.user_id + ) + return LatexRunner.runLatex( + compileName, + { + directory: compileDir, + mainFile: request.rootResourcePath, + compiler: request.compiler, + timeout: request.timeout, + image: request.imageName, + flags: request.flags, + environment: env + }, + function(error, output, stats, timings) { + // request was for validation only + let metric_key, metric_value + if (request.check === 'validate') { + const result = (error != null + ? error.code + : undefined) + ? 'fail' + : 'pass' + error = new Error('validation') + error.validate = result + } + // request was for compile, and failed on validation + if ( + request.check === 'error' && + (error != null ? error.message : undefined) === 'exited' + ) { + error = new Error('compilation') + error.validate = 'fail' + } + // compile was killed by user, was a validation, or a compile which failed validation + if ( + (error != null ? error.terminated : undefined) || + (error != null ? error.validate : undefined) || + (error != null ? error.timedout : undefined) + ) { + OutputFileFinder.findOutputFiles( + resourceList, + compileDir, + function(err, outputFiles) { + if (err != null) { + return callback(err) + } + error.outputFiles = outputFiles // return output files so user can check logs + return callback(error) + } + ) + return + } + // compile completed normally + if (error != null) { + return callback(error) + } + Metrics.inc('compiles-succeeded') + const object = stats || {} + for (metric_key in object) { + metric_value = object[metric_key] + Metrics.count(metric_key, metric_value) + } + const object1 = timings || {} + for (metric_key in object1) { + metric_value = object1[metric_key] + Metrics.timing(metric_key, metric_value) + } + const loadavg = + typeof os.loadavg === 'function' ? os.loadavg() : undefined + if (loadavg != null) { + Metrics.gauge('load-avg', loadavg[0]) + } + const ts = timer.done() + logger.log( + { + project_id: request.project_id, + user_id: request.user_id, + time_taken: ts, + stats, + timings, + loadavg + }, + 'done compile' + ) + if ((stats != null ? stats['latex-runs'] : undefined) > 0) { + Metrics.timing('run-compile-per-pass', ts / stats['latex-runs']) + } + if ( + (stats != null ? stats['latex-runs'] : undefined) > 0 && + (timings != null ? timings['cpu-time'] : undefined) > 0 + ) { + Metrics.timing( + 'run-compile-cpu-time-per-pass', + timings['cpu-time'] / stats['latex-runs'] + ) + } - // set up environment variables for chktex - const env = {}; - // only run chktex on LaTeX files (not knitr .Rtex files or any others) - const isLaTeXFile = request.rootResourcePath != null ? request.rootResourcePath.match(/\.tex$/i) : undefined; - if ((request.check != null) && isLaTeXFile) { - env.CHKTEX_OPTIONS = '-nall -e9 -e10 -w15 -w16'; - env.CHKTEX_ULIMIT_OPTIONS = '-t 5 -v 64000'; - if (request.check === 'error') { - env.CHKTEX_EXIT_ON_ERROR = 1; - } - if (request.check === 'validate') { - env.CHKTEX_VALIDATE = 1; - } - } + return OutputFileFinder.findOutputFiles( + resourceList, + compileDir, + function(error, outputFiles) { + if (error != null) { + return callback(error) + } + return OutputCacheManager.saveOutputFiles( + outputFiles, + compileDir, + (error, newOutputFiles) => callback(null, newOutputFiles) + ) + } + ) + } + ) + } + ) + }) + }, - // apply a series of file modifications/creations for draft mode and tikz - return async.series([injectDraftModeIfRequired, createTikzFileIfRequired], function(error) { - if (error != null) { return callback(error); } - timer = new Metrics.Timer("run-compile"); - // find the image tag to log it as a metric, e.g. 2015.1 (convert . to - for graphite) - let tag = __guard__(__guard__(request.imageName != null ? request.imageName.match(/:(.*)/) : undefined, x1 => x1[1]), x => x.replace(/\./g,'-')) || "default"; - if (!request.project_id.match(/^[0-9a-f]{24}$/)) { tag = "other"; } // exclude smoke test - Metrics.inc("compiles"); - Metrics.inc(`compiles-with-image.${tag}`); - const compileName = getCompileName(request.project_id, request.user_id); - return LatexRunner.runLatex(compileName, { - directory: compileDir, - mainFile: request.rootResourcePath, - compiler: request.compiler, - timeout: request.timeout, - image: request.imageName, - flags: request.flags, - environment: env - }, function(error, output, stats, timings) { - // request was for validation only - let metric_key, metric_value; - if (request.check === "validate") { - const result = (error != null ? error.code : undefined) ? "fail" : "pass"; - error = new Error("validation"); - error.validate = result; - } - // request was for compile, and failed on validation - if ((request.check === "error") && ((error != null ? error.message : undefined) === 'exited')) { - error = new Error("compilation"); - error.validate = "fail"; - } - // compile was killed by user, was a validation, or a compile which failed validation - if ((error != null ? error.terminated : undefined) || (error != null ? error.validate : undefined) || (error != null ? error.timedout : undefined)) { - OutputFileFinder.findOutputFiles(resourceList, compileDir, function(err, outputFiles) { - if (err != null) { return callback(err); } - error.outputFiles = outputFiles; // return output files so user can check logs - return callback(error); - }); - return; - } - // compile completed normally - if (error != null) { return callback(error); } - Metrics.inc("compiles-succeeded"); - const object = stats || {}; - for (metric_key in object) { - metric_value = object[metric_key]; - Metrics.count(metric_key, metric_value); - } - const object1 = timings || {}; - for (metric_key in object1) { - metric_value = object1[metric_key]; - Metrics.timing(metric_key, metric_value); - } - const loadavg = typeof os.loadavg === 'function' ? os.loadavg() : undefined; - if (loadavg != null) { Metrics.gauge("load-avg", loadavg[0]); } - const ts = timer.done(); - logger.log({project_id: request.project_id, user_id: request.user_id, time_taken: ts, stats, timings, loadavg}, "done compile"); - if ((stats != null ? stats["latex-runs"] : undefined) > 0) { - Metrics.timing("run-compile-per-pass", ts / stats["latex-runs"]); - } - if (((stats != null ? stats["latex-runs"] : undefined) > 0) && ((timings != null ? timings["cpu-time"] : undefined) > 0)) { - Metrics.timing("run-compile-cpu-time-per-pass", timings["cpu-time"] / stats["latex-runs"]); - } + stopCompile(project_id, user_id, callback) { + if (callback == null) { + callback = function(error) {} + } + const compileName = getCompileName(project_id, user_id) + return LatexRunner.killLatex(compileName, callback) + }, - return OutputFileFinder.findOutputFiles(resourceList, compileDir, function(error, outputFiles) { - if (error != null) { return callback(error); } - return OutputCacheManager.saveOutputFiles(outputFiles, compileDir, (error, newOutputFiles) => callback(null, newOutputFiles)); - }); - }); - }); - }); - }, + clearProject(project_id, user_id, _callback) { + if (_callback == null) { + _callback = function(error) {} + } + const callback = function(error) { + _callback(error) + return (_callback = function() {}) + } - stopCompile(project_id, user_id, callback) { - if (callback == null) { callback = function(error) {}; } - const compileName = getCompileName(project_id, user_id); - return LatexRunner.killLatex(compileName, callback); - }, + const compileDir = getCompileDir(project_id, user_id) - clearProject(project_id, user_id, _callback) { - if (_callback == null) { _callback = function(error) {}; } - const callback = function(error) { - _callback(error); - return _callback = function() {}; - }; + return CompileManager._checkDirectory(compileDir, function(err, exists) { + if (err != null) { + return callback(err) + } + if (!exists) { + return callback() + } // skip removal if no directory present - const compileDir = getCompileDir(project_id, user_id); + const proc = child_process.spawn('rm', ['-r', compileDir]) - return CompileManager._checkDirectory(compileDir, function(err, exists) { - if (err != null) { return callback(err); } - if (!exists) { return callback(); } // skip removal if no directory present + proc.on('error', callback) - const proc = child_process.spawn("rm", ["-r", compileDir]); + let stderr = '' + proc.stderr.on('data', chunk => (stderr += chunk.toString())) - proc.on("error", callback); + return proc.on('close', function(code) { + if (code === 0) { + return callback(null) + } else { + return callback(new Error(`rm -r ${compileDir} failed: ${stderr}`)) + } + }) + }) + }, - let stderr = ""; - proc.stderr.on("data", chunk => stderr += chunk.toString()); + _findAllDirs(callback) { + if (callback == null) { + callback = function(error, allDirs) {} + } + const root = Settings.path.compilesDir + return fs.readdir(root, function(err, files) { + if (err != null) { + return callback(err) + } + const allDirs = Array.from(files).map(file => Path.join(root, file)) + return callback(null, allDirs) + }) + }, - return proc.on("close", function(code) { - if (code === 0) { - return callback(null); - } else { - return callback(new Error(`rm -r ${compileDir} failed: ${stderr}`)); - } - }); - }); - }, + clearExpiredProjects(max_cache_age_ms, callback) { + if (callback == null) { + callback = function(error) {} + } + const now = Date.now() + // action for each directory + const expireIfNeeded = (checkDir, cb) => + fs.stat(checkDir, function(err, stats) { + if (err != null) { + return cb() + } // ignore errors checking directory + const age = now - stats.mtime + const hasExpired = age > max_cache_age_ms + if (hasExpired) { + return fse.remove(checkDir, cb) + } else { + return cb() + } + }) + // iterate over all project directories + return CompileManager._findAllDirs(function(error, allDirs) { + if (error != null) { + return callback() + } + return async.eachSeries(allDirs, expireIfNeeded, callback) + }) + }, - _findAllDirs(callback) { - if (callback == null) { callback = function(error, allDirs) {}; } - const root = Settings.path.compilesDir; - return fs.readdir(root, function(err, files) { - if (err != null) { return callback(err); } - const allDirs = (Array.from(files).map((file) => Path.join(root, file))); - return callback(null, allDirs); - }); - }, + _checkDirectory(compileDir, callback) { + if (callback == null) { + callback = function(error, exists) {} + } + return fs.lstat(compileDir, function(err, stats) { + if ((err != null ? err.code : undefined) === 'ENOENT') { + return callback(null, false) // directory does not exist + } else if (err != null) { + logger.err( + { dir: compileDir, err }, + 'error on stat of project directory for removal' + ) + return callback(err) + } else if (!(stats != null ? stats.isDirectory() : undefined)) { + logger.err( + { dir: compileDir, stats }, + 'bad project directory for removal' + ) + return callback(new Error('project directory is not directory')) + } else { + return callback(null, true) + } + }) + }, // directory exists - clearExpiredProjects(max_cache_age_ms, callback) { - if (callback == null) { callback = function(error) {}; } - const now = Date.now(); - // action for each directory - const expireIfNeeded = (checkDir, cb) => - fs.stat(checkDir, function(err, stats) { - if (err != null) { return cb(); } // ignore errors checking directory - const age = now - stats.mtime; - const hasExpired = (age > max_cache_age_ms); - if (hasExpired) { return fse.remove(checkDir, cb); } else { return cb(); } - }) - ; - // iterate over all project directories - return CompileManager._findAllDirs(function(error, allDirs) { - if (error != null) { return callback(); } - return async.eachSeries(allDirs, expireIfNeeded, callback); - }); - }, + syncFromCode(project_id, user_id, file_name, line, column, callback) { + // If LaTeX was run in a virtual environment, the file path that synctex expects + // might not match the file path on the host. The .synctex.gz file however, will be accessed + // wherever it is on the host. + if (callback == null) { + callback = function(error, pdfPositions) {} + } + const compileName = getCompileName(project_id, user_id) + const base_dir = Settings.path.synctexBaseDir(compileName) + const file_path = base_dir + '/' + file_name + const compileDir = getCompileDir(project_id, user_id) + const synctex_path = `${base_dir}/output.pdf` + const command = ['code', synctex_path, file_path, line, column] + return fse.ensureDir(compileDir, function(error) { + if (error != null) { + logger.err( + { error, project_id, user_id, file_name }, + 'error ensuring dir for sync from code' + ) + return callback(error) + } + return CompileManager._runSynctex(project_id, user_id, command, function( + error, + stdout + ) { + if (error != null) { + return callback(error) + } + logger.log( + { project_id, user_id, file_name, line, column, command, stdout }, + 'synctex code output' + ) + return callback( + null, + CompileManager._parseSynctexFromCodeOutput(stdout) + ) + }) + }) + }, - _checkDirectory(compileDir, callback) { - if (callback == null) { callback = function(error, exists) {}; } - return fs.lstat(compileDir, function(err, stats) { - if ((err != null ? err.code : undefined) === 'ENOENT') { - return callback(null, false); // directory does not exist - } else if (err != null) { - logger.err({dir: compileDir, err}, "error on stat of project directory for removal"); - return callback(err); - } else if (!(stats != null ? stats.isDirectory() : undefined)) { - logger.err({dir: compileDir, stats}, "bad project directory for removal"); - return callback(new Error("project directory is not directory")); - } else { - return callback(null, true); - } - }); - }, // directory exists + syncFromPdf(project_id, user_id, page, h, v, callback) { + if (callback == null) { + callback = function(error, filePositions) {} + } + const compileName = getCompileName(project_id, user_id) + const compileDir = getCompileDir(project_id, user_id) + const base_dir = Settings.path.synctexBaseDir(compileName) + const synctex_path = `${base_dir}/output.pdf` + const command = ['pdf', synctex_path, page, h, v] + return fse.ensureDir(compileDir, function(error) { + if (error != null) { + logger.err( + { error, project_id, user_id, file_name }, + 'error ensuring dir for sync to code' + ) + return callback(error) + } + return CompileManager._runSynctex(project_id, user_id, command, function( + error, + stdout + ) { + if (error != null) { + return callback(error) + } + logger.log( + { project_id, user_id, page, h, v, stdout }, + 'synctex pdf output' + ) + return callback( + null, + CompileManager._parseSynctexFromPdfOutput(stdout, base_dir) + ) + }) + }) + }, - syncFromCode(project_id, user_id, file_name, line, column, callback) { - // If LaTeX was run in a virtual environment, the file path that synctex expects - // might not match the file path on the host. The .synctex.gz file however, will be accessed - // wherever it is on the host. - if (callback == null) { callback = function(error, pdfPositions) {}; } - const compileName = getCompileName(project_id, user_id); - const base_dir = Settings.path.synctexBaseDir(compileName); - const file_path = base_dir + "/" + file_name; - const compileDir = getCompileDir(project_id, user_id); - const synctex_path = `${base_dir}/output.pdf`; - const command = ["code", synctex_path, file_path, line, column]; - return fse.ensureDir(compileDir, function(error) { - if (error != null) { - logger.err({error, project_id, user_id, file_name}, "error ensuring dir for sync from code"); - return callback(error); - } - return CompileManager._runSynctex(project_id, user_id, command, function(error, stdout) { - if (error != null) { return callback(error); } - logger.log({project_id, user_id, file_name, line, column, command, stdout}, "synctex code output"); - return callback(null, CompileManager._parseSynctexFromCodeOutput(stdout)); - }); - }); - }, + _checkFileExists(path, callback) { + if (callback == null) { + callback = function(error) {} + } + const synctexDir = Path.dirname(path) + const synctexFile = Path.join(synctexDir, 'output.synctex.gz') + return fs.stat(synctexDir, function(error, stats) { + if ((error != null ? error.code : undefined) === 'ENOENT') { + return callback( + new Errors.NotFoundError('called synctex with no output directory') + ) + } + if (error != null) { + return callback(error) + } + return fs.stat(synctexFile, function(error, stats) { + if ((error != null ? error.code : undefined) === 'ENOENT') { + return callback( + new Errors.NotFoundError('called synctex with no output file') + ) + } + if (error != null) { + return callback(error) + } + if (!(stats != null ? stats.isFile() : undefined)) { + return callback(new Error('not a file')) + } + return callback() + }) + }) + }, - syncFromPdf(project_id, user_id, page, h, v, callback) { - if (callback == null) { callback = function(error, filePositions) {}; } - const compileName = getCompileName(project_id, user_id); - const compileDir = getCompileDir(project_id, user_id); - const base_dir = Settings.path.synctexBaseDir(compileName); - const synctex_path = `${base_dir}/output.pdf`; - const command = ["pdf", synctex_path, page, h, v]; - return fse.ensureDir(compileDir, function(error) { - if (error != null) { - logger.err({error, project_id, user_id, file_name}, "error ensuring dir for sync to code"); - return callback(error); - } - return CompileManager._runSynctex(project_id, user_id, command, function(error, stdout) { - if (error != null) { return callback(error); } - logger.log({project_id, user_id, page, h, v, stdout}, "synctex pdf output"); - return callback(null, CompileManager._parseSynctexFromPdfOutput(stdout, base_dir)); - }); - }); - }, + _runSynctex(project_id, user_id, command, callback) { + if (callback == null) { + callback = function(error, stdout) {} + } + const seconds = 1000 - _checkFileExists(path, callback) { - if (callback == null) { callback = function(error) {}; } - const synctexDir = Path.dirname(path); - const synctexFile = Path.join(synctexDir, "output.synctex.gz"); - return fs.stat(synctexDir, function(error, stats) { - if ((error != null ? error.code : undefined) === 'ENOENT') { - return callback(new Errors.NotFoundError("called synctex with no output directory")); - } - if (error != null) { return callback(error); } - return fs.stat(synctexFile, function(error, stats) { - if ((error != null ? error.code : undefined) === 'ENOENT') { - return callback(new Errors.NotFoundError("called synctex with no output file")); - } - if (error != null) { return callback(error); } - if (!(stats != null ? stats.isFile() : undefined)) { return callback(new Error("not a file")); } - return callback(); - }); - }); - }, + command.unshift('/opt/synctex') - _runSynctex(project_id, user_id, command, callback) { - if (callback == null) { callback = function(error, stdout) {}; } - const seconds = 1000; + const directory = getCompileDir(project_id, user_id) + const timeout = 60 * 1000 // increased to allow for large projects + const compileName = getCompileName(project_id, user_id) + return CommandRunner.run( + compileName, + command, + directory, + Settings.clsi != null ? Settings.clsi.docker.image : undefined, + timeout, + {}, + function(error, output) { + if (error != null) { + logger.err( + { err: error, command, project_id, user_id }, + 'error running synctex' + ) + return callback(error) + } + return callback(null, output.stdout) + } + ) + }, - command.unshift("/opt/synctex"); + _parseSynctexFromCodeOutput(output) { + const results = [] + for (const line of Array.from(output.split('\n'))) { + const [node, page, h, v, width, height] = Array.from(line.split('\t')) + if (node === 'NODE') { + results.push({ + page: parseInt(page, 10), + h: parseFloat(h), + v: parseFloat(v), + height: parseFloat(height), + width: parseFloat(width) + }) + } + } + return results + }, - const directory = getCompileDir(project_id, user_id); - const timeout = 60 * 1000; // increased to allow for large projects - const compileName = getCompileName(project_id, user_id); - return CommandRunner.run(compileName, command, directory, Settings.clsi != null ? Settings.clsi.docker.image : undefined, timeout, {}, function(error, output) { - if (error != null) { - logger.err({err:error, command, project_id, user_id}, "error running synctex"); - return callback(error); - } - return callback(null, output.stdout); - }); - }, + _parseSynctexFromPdfOutput(output, base_dir) { + const results = [] + for (let line of Array.from(output.split('\n'))) { + let column, file_path, node + ;[node, file_path, line, column] = Array.from(line.split('\t')) + if (node === 'NODE') { + const file = file_path.slice(base_dir.length + 1) + results.push({ + file, + line: parseInt(line, 10), + column: parseInt(column, 10) + }) + } + } + return results + }, - _parseSynctexFromCodeOutput(output) { - const results = []; - for (const line of Array.from(output.split("\n"))) { - const [node, page, h, v, width, height] = Array.from(line.split("\t")); - if (node === "NODE") { - results.push({ - page: parseInt(page, 10), - h: parseFloat(h), - v: parseFloat(v), - height: parseFloat(height), - width: parseFloat(width) - }); - } - } - return results; - }, + wordcount(project_id, user_id, file_name, image, callback) { + if (callback == null) { + callback = function(error, pdfPositions) {} + } + logger.log({ project_id, user_id, file_name, image }, 'running wordcount') + const file_path = `$COMPILE_DIR/${file_name}` + const command = [ + 'texcount', + '-nocol', + '-inc', + file_path, + `-out=${file_path}.wc` + ] + const compileDir = getCompileDir(project_id, user_id) + const timeout = 60 * 1000 + const compileName = getCompileName(project_id, user_id) + return fse.ensureDir(compileDir, function(error) { + if (error != null) { + logger.err( + { error, project_id, user_id, file_name }, + 'error ensuring dir for sync from code' + ) + return callback(error) + } + return CommandRunner.run( + compileName, + command, + compileDir, + image, + timeout, + {}, + function(error) { + if (error != null) { + return callback(error) + } + return fs.readFile( + compileDir + '/' + file_name + '.wc', + 'utf-8', + function(err, stdout) { + if (err != null) { + // call it node_err so sentry doesn't use random path error as unique id so it can't be ignored + logger.err( + { node_err: err, command, compileDir, project_id, user_id }, + 'error reading word count output' + ) + return callback(err) + } + const results = CompileManager._parseWordcountFromOutput(stdout) + logger.log( + { project_id, user_id, wordcount: results }, + 'word count results' + ) + return callback(null, results) + } + ) + } + ) + }) + }, - _parseSynctexFromPdfOutput(output, base_dir) { - const results = []; - for (let line of Array.from(output.split("\n"))) { - let column, file_path, node; - [node, file_path, line, column] = Array.from(line.split("\t")); - if (node === "NODE") { - const file = file_path.slice(base_dir.length + 1); - results.push({ - file, - line: parseInt(line, 10), - column: parseInt(column, 10) - }); - } - } - return results; - }, - - - wordcount(project_id, user_id, file_name, image, callback) { - if (callback == null) { callback = function(error, pdfPositions) {}; } - logger.log({project_id, user_id, file_name, image}, "running wordcount"); - const file_path = `$COMPILE_DIR/${file_name}`; - const command = [ "texcount", '-nocol', '-inc', file_path, `-out=${file_path}.wc`]; - const compileDir = getCompileDir(project_id, user_id); - const timeout = 60 * 1000; - const compileName = getCompileName(project_id, user_id); - return fse.ensureDir(compileDir, function(error) { - if (error != null) { - logger.err({error, project_id, user_id, file_name}, "error ensuring dir for sync from code"); - return callback(error); - } - return CommandRunner.run(compileName, command, compileDir, image, timeout, {}, function(error) { - if (error != null) { return callback(error); } - return fs.readFile(compileDir + "/" + file_name + ".wc", "utf-8", function(err, stdout) { - if (err != null) { - // call it node_err so sentry doesn't use random path error as unique id so it can't be ignored - logger.err({node_err:err, command, compileDir, project_id, user_id}, "error reading word count output"); - return callback(err); - } - const results = CompileManager._parseWordcountFromOutput(stdout); - logger.log({project_id, user_id, wordcount: results}, "word count results"); - return callback(null, results); - }); - }); - }); - }, - - _parseWordcountFromOutput(output) { - const results = { - encode: "", - textWords: 0, - headWords: 0, - outside: 0, - headers: 0, - elements: 0, - mathInline: 0, - mathDisplay: 0, - errors: 0, - messages: "" - }; - for (const line of Array.from(output.split("\n"))) { - const [data, info] = Array.from(line.split(":")); - if (data.indexOf("Encoding") > -1) { - results.encode = info.trim(); - } - if (data.indexOf("in text") > -1) { - results.textWords = parseInt(info, 10); - } - if (data.indexOf("in head") > -1) { - results.headWords = parseInt(info, 10); - } - if (data.indexOf("outside") > -1) { - results.outside = parseInt(info, 10); - } - if (data.indexOf("of head") > -1) { - results.headers = parseInt(info, 10); - } - if (data.indexOf("Number of floats/tables/figures") > -1) { - results.elements = parseInt(info, 10); - } - if (data.indexOf("Number of math inlines") > -1) { - results.mathInline = parseInt(info, 10); - } - if (data.indexOf("Number of math displayed") > -1) { - results.mathDisplay = parseInt(info, 10); - } - if (data === "(errors") { // errors reported as (errors:123) - results.errors = parseInt(info, 10); - } - if (line.indexOf("!!! ") > -1) { // errors logged as !!! message !!! - results.messages += line + "\n"; - } - } - return results; - } -}); + _parseWordcountFromOutput(output) { + const results = { + encode: '', + textWords: 0, + headWords: 0, + outside: 0, + headers: 0, + elements: 0, + mathInline: 0, + mathDisplay: 0, + errors: 0, + messages: '' + } + for (const line of Array.from(output.split('\n'))) { + const [data, info] = Array.from(line.split(':')) + if (data.indexOf('Encoding') > -1) { + results.encode = info.trim() + } + if (data.indexOf('in text') > -1) { + results.textWords = parseInt(info, 10) + } + if (data.indexOf('in head') > -1) { + results.headWords = parseInt(info, 10) + } + if (data.indexOf('outside') > -1) { + results.outside = parseInt(info, 10) + } + if (data.indexOf('of head') > -1) { + results.headers = parseInt(info, 10) + } + if (data.indexOf('Number of floats/tables/figures') > -1) { + results.elements = parseInt(info, 10) + } + if (data.indexOf('Number of math inlines') > -1) { + results.mathInline = parseInt(info, 10) + } + if (data.indexOf('Number of math displayed') > -1) { + results.mathDisplay = parseInt(info, 10) + } + if (data === '(errors') { + // errors reported as (errors:123) + results.errors = parseInt(info, 10) + } + if (line.indexOf('!!! ') > -1) { + // errors logged as !!! message !!! + results.messages += line + '\n' + } + } + return results + } +} function __guard__(value, transform) { - return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; -} \ No newline at end of file + return typeof value !== 'undefined' && value !== null + ? transform(value) + : undefined +} diff --git a/services/clsi/app/js/ContentTypeMapper.js b/services/clsi/app/js/ContentTypeMapper.js index fdd66d3827..f690bf9df7 100644 --- a/services/clsi/app/js/ContentTypeMapper.js +++ b/services/clsi/app/js/ContentTypeMapper.js @@ -3,31 +3,36 @@ */ // TODO: This file was created by bulk-decaffeinate. // Fix any style issues and re-enable lint. -let ContentTypeMapper; -const Path = require('path'); +let ContentTypeMapper +const Path = require('path') // here we coerce html, css and js to text/plain, // otherwise choose correct mime type based on file extension, // falling back to octet-stream -module.exports = (ContentTypeMapper = { - map(path) { - switch (Path.extname(path)) { - case '.txt': case '.html': case '.js': case '.css': case '.svg': - return 'text/plain'; - case '.csv': - return 'text/csv'; - case '.pdf': - return 'application/pdf'; - case '.png': - return 'image/png'; - case '.jpg': case '.jpeg': - return 'image/jpeg'; - case '.tiff': - return 'image/tiff'; - case '.gif': - return 'image/gif'; - default: - return 'application/octet-stream'; - } - } -}); +module.exports = ContentTypeMapper = { + map(path) { + switch (Path.extname(path)) { + case '.txt': + case '.html': + case '.js': + case '.css': + case '.svg': + return 'text/plain' + case '.csv': + return 'text/csv' + case '.pdf': + return 'application/pdf' + case '.png': + return 'image/png' + case '.jpg': + case '.jpeg': + return 'image/jpeg' + case '.tiff': + return 'image/tiff' + case '.gif': + return 'image/gif' + default: + return 'application/octet-stream' + } + } +} diff --git a/services/clsi/app/js/DbQueue.js b/services/clsi/app/js/DbQueue.js index 89ff323434..7589370c98 100644 --- a/services/clsi/app/js/DbQueue.js +++ b/services/clsi/app/js/DbQueue.js @@ -5,14 +5,14 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const async = require("async"); -const Settings = require("settings-sharelatex"); -const logger = require("logger-sharelatex"); -const queue = async.queue((task, cb)=> task(cb) - , Settings.parallelSqlQueryLimit); +const async = require('async') +const Settings = require('settings-sharelatex') +const logger = require('logger-sharelatex') +const queue = async.queue( + (task, cb) => task(cb), + Settings.parallelSqlQueryLimit +) -queue.drain = ()=> logger.debug('all items have been processed'); - -module.exports = - {queue}; +queue.drain = () => logger.debug('all items have been processed') +module.exports = { queue } diff --git a/services/clsi/app/js/DockerLockManager.js b/services/clsi/app/js/DockerLockManager.js index 274ff66c6f..2685b42dc8 100644 --- a/services/clsi/app/js/DockerLockManager.js +++ b/services/clsi/app/js/DockerLockManager.js @@ -10,80 +10,104 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let LockManager; -const logger = require("logger-sharelatex"); +let LockManager +const logger = require('logger-sharelatex') -const LockState = {}; // locks for docker container operations, by container name +const LockState = {} // locks for docker container operations, by container name -module.exports = (LockManager = { +module.exports = LockManager = { + MAX_LOCK_HOLD_TIME: 15000, // how long we can keep a lock + MAX_LOCK_WAIT_TIME: 10000, // how long we wait for a lock + LOCK_TEST_INTERVAL: 1000, // retry time - MAX_LOCK_HOLD_TIME: 15000, // how long we can keep a lock - MAX_LOCK_WAIT_TIME: 10000, // how long we wait for a lock - LOCK_TEST_INTERVAL: 1000, // retry time + tryLock(key, callback) { + let lockValue + if (callback == null) { + callback = function(err, gotLock) {} + } + const existingLock = LockState[key] + if (existingLock != null) { + // the lock is already taken, check how old it is + const lockAge = Date.now() - existingLock.created + if (lockAge < LockManager.MAX_LOCK_HOLD_TIME) { + return callback(null, false) // we didn't get the lock, bail out + } else { + logger.error( + { key, lock: existingLock, age: lockAge }, + 'taking old lock by force' + ) + } + } + // take the lock + LockState[key] = lockValue = { created: Date.now() } + return callback(null, true, lockValue) + }, - tryLock(key, callback) { - let lockValue; - if (callback == null) { callback = function(err, gotLock) {}; } - const existingLock = LockState[key]; - if (existingLock != null) { // the lock is already taken, check how old it is - const lockAge = Date.now() - existingLock.created; - if (lockAge < LockManager.MAX_LOCK_HOLD_TIME) { - return callback(null, false); // we didn't get the lock, bail out - } else { - logger.error({key, lock: existingLock, age:lockAge}, "taking old lock by force"); - } - } - // take the lock - LockState[key] = (lockValue = {created: Date.now()}); - return callback(null, true, lockValue); - }, + getLock(key, callback) { + let attempt + if (callback == null) { + callback = function(error, lockValue) {} + } + const startTime = Date.now() + return (attempt = () => + LockManager.tryLock(key, function(error, gotLock, lockValue) { + if (error != null) { + return callback(error) + } + if (gotLock) { + return callback(null, lockValue) + } else if (Date.now() - startTime > LockManager.MAX_LOCK_WAIT_TIME) { + const e = new Error('Lock timeout') + e.key = key + return callback(e) + } else { + return setTimeout(attempt, LockManager.LOCK_TEST_INTERVAL) + } + }))() + }, - getLock(key, callback) { - let attempt; - if (callback == null) { callback = function(error, lockValue) {}; } - const startTime = Date.now(); - return (attempt = () => - LockManager.tryLock(key, function(error, gotLock, lockValue) { - if (error != null) { return callback(error); } - if (gotLock) { - return callback(null, lockValue); - } else if ((Date.now() - startTime) > LockManager.MAX_LOCK_WAIT_TIME) { - const e = new Error("Lock timeout"); - e.key = key; - return callback(e); - } else { - return setTimeout(attempt, LockManager.LOCK_TEST_INTERVAL); - } - }) - )(); - }, + releaseLock(key, lockValue, callback) { + if (callback == null) { + callback = function(error) {} + } + const existingLock = LockState[key] + if (existingLock === lockValue) { + // lockValue is an object, so we can test by reference + delete LockState[key] // our lock, so we can free it + return callback() + } else if (existingLock != null) { + // lock exists but doesn't match ours + logger.error( + { key, lock: existingLock }, + 'tried to release lock taken by force' + ) + return callback() + } else { + logger.error( + { key, lock: existingLock }, + 'tried to release lock that has gone' + ) + return callback() + } + }, - releaseLock(key, lockValue, callback) { - if (callback == null) { callback = function(error) {}; } - const existingLock = LockState[key]; - if (existingLock === lockValue) { // lockValue is an object, so we can test by reference - delete LockState[key]; // our lock, so we can free it - return callback(); - } else if (existingLock != null) { // lock exists but doesn't match ours - logger.error({key, lock: existingLock}, "tried to release lock taken by force"); - return callback(); - } else { - logger.error({key, lock: existingLock}, "tried to release lock that has gone"); - return callback(); - } - }, - - runWithLock(key, runner, callback) { - if (callback == null) { callback = function(error) {}; } - return LockManager.getLock(key, function(error, lockValue) { - if (error != null) { return callback(error); } - return runner((error1, ...args) => - LockManager.releaseLock(key, lockValue, function(error2) { - error = error1 || error2; - if (error != null) { return callback(error); } - return callback(null, ...Array.from(args)); - }) - ); - }); - } -}); + runWithLock(key, runner, callback) { + if (callback == null) { + callback = function(error) {} + } + return LockManager.getLock(key, function(error, lockValue) { + if (error != null) { + return callback(error) + } + return runner((error1, ...args) => + LockManager.releaseLock(key, lockValue, function(error2) { + error = error1 || error2 + if (error != null) { + return callback(error) + } + return callback(null, ...Array.from(args)) + }) + ) + }) + } +} diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index dc04b5dfdf..5ac234b7e0 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -15,469 +15,666 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let DockerRunner, oneHour; -const Settings = require("settings-sharelatex"); -const logger = require("logger-sharelatex"); -const Docker = require("dockerode"); -const dockerode = new Docker(); -const crypto = require("crypto"); -const async = require("async"); -const LockManager = require("./DockerLockManager"); -const fs = require("fs"); -const Path = require('path'); -const _ = require("underscore"); +let DockerRunner, oneHour +const Settings = require('settings-sharelatex') +const logger = require('logger-sharelatex') +const Docker = require('dockerode') +const dockerode = new Docker() +const crypto = require('crypto') +const async = require('async') +const LockManager = require('./DockerLockManager') +const fs = require('fs') +const Path = require('path') +const _ = require('underscore') -logger.info("using docker runner"); +logger.info('using docker runner') -const usingSiblingContainers = () => __guard__(Settings != null ? Settings.path : undefined, x => x.sandboxedCompilesHostDir) != null; +const usingSiblingContainers = () => + __guard__( + Settings != null ? Settings.path : undefined, + x => x.sandboxedCompilesHostDir + ) != null -module.exports = (DockerRunner = { - ERR_NOT_DIRECTORY: new Error("not a directory"), - ERR_TERMINATED: new Error("terminated"), - ERR_EXITED: new Error("exited"), - ERR_TIMED_OUT: new Error("container timed out"), +module.exports = DockerRunner = { + ERR_NOT_DIRECTORY: new Error('not a directory'), + ERR_TERMINATED: new Error('terminated'), + ERR_EXITED: new Error('exited'), + ERR_TIMED_OUT: new Error('container timed out'), - run(project_id, command, directory, image, timeout, environment, callback) { + run(project_id, command, directory, image, timeout, environment, callback) { + let name + if (callback == null) { + callback = function(error, output) {} + } + if (usingSiblingContainers()) { + const _newPath = Settings.path.sandboxedCompilesHostDir + logger.log( + { path: _newPath }, + 'altering bind path for sibling containers' + ) + // Server Pro, example: + // '/var/lib/sharelatex/data/compiles/<project-id>' + // ... becomes ... + // '/opt/sharelatex_data/data/compiles/<project-id>' + directory = Path.join( + Settings.path.sandboxedCompilesHostDir, + Path.basename(directory) + ) + } - let name; - if (callback == null) { callback = function(error, output) {}; } - if (usingSiblingContainers()) { - const _newPath = Settings.path.sandboxedCompilesHostDir; - logger.log({path: _newPath}, "altering bind path for sibling containers"); - // Server Pro, example: - // '/var/lib/sharelatex/data/compiles/<project-id>' - // ... becomes ... - // '/opt/sharelatex_data/data/compiles/<project-id>' - directory = Path.join(Settings.path.sandboxedCompilesHostDir, Path.basename(directory)); - } + const volumes = {} + volumes[directory] = '/compile' - const volumes = {}; - volumes[directory] = "/compile"; + command = Array.from(command).map(arg => + __guardMethod__(arg.toString(), 'replace', o => + o.replace('$COMPILE_DIR', '/compile') + ) + ) + if (image == null) { + ;({ image } = Settings.clsi.docker) + } - command = (Array.from(command).map((arg) => __guardMethod__(arg.toString(), 'replace', o => o.replace('$COMPILE_DIR', "/compile")))); - if ((image == null)) { - ({ image } = Settings.clsi.docker); - } + if (Settings.texliveImageNameOveride != null) { + const img = image.split('/') + image = `${Settings.texliveImageNameOveride}/${img[2]}` + } - if (Settings.texliveImageNameOveride != null) { - const img = image.split("/"); - image = `${Settings.texliveImageNameOveride}/${img[2]}`; - } + const options = DockerRunner._getContainerOptions( + command, + image, + volumes, + timeout, + environment + ) + const fingerprint = DockerRunner._fingerprintContainer(options) + options.name = name = `project-${project_id}-${fingerprint}` - const options = DockerRunner._getContainerOptions(command, image, volumes, timeout, environment); - const fingerprint = DockerRunner._fingerprintContainer(options); - options.name = (name = `project-${project_id}-${fingerprint}`); + // logOptions = _.clone(options) + // logOptions?.HostConfig?.SecurityOpt = "secomp used, removed in logging" + logger.log({ project_id }, 'running docker container') + DockerRunner._runAndWaitForContainer(options, volumes, timeout, function( + error, + output + ) { + if ( + __guard__(error != null ? error.message : undefined, x => + x.match('HTTP code is 500') + ) + ) { + logger.log( + { err: error, project_id }, + 'error running container so destroying and retrying' + ) + return DockerRunner.destroyContainer(name, null, true, function(error) { + if (error != null) { + return callback(error) + } + return DockerRunner._runAndWaitForContainer( + options, + volumes, + timeout, + callback + ) + }) + } else { + return callback(error, output) + } + }) - // logOptions = _.clone(options) - // logOptions?.HostConfig?.SecurityOpt = "secomp used, removed in logging" - logger.log({project_id}, "running docker container"); - DockerRunner._runAndWaitForContainer(options, volumes, timeout, function(error, output) { - if (__guard__(error != null ? error.message : undefined, x => x.match("HTTP code is 500"))) { - logger.log({err: error, project_id}, "error running container so destroying and retrying"); - return DockerRunner.destroyContainer(name, null, true, function(error) { - if (error != null) { return callback(error); } - return DockerRunner._runAndWaitForContainer(options, volumes, timeout, callback); - }); - } else { - return callback(error, output); - } - }); + return name + }, // pass back the container name to allow it to be killed - return name; - }, // pass back the container name to allow it to be killed + kill(container_id, callback) { + if (callback == null) { + callback = function(error) {} + } + logger.log({ container_id }, 'sending kill signal to container') + const container = dockerode.getContainer(container_id) + return container.kill(function(error) { + if ( + error != null && + __guardMethod__(error != null ? error.message : undefined, 'match', o => + o.match(/Cannot kill container .* is not running/) + ) + ) { + logger.warn( + { err: error, container_id }, + 'container not running, continuing' + ) + error = null + } + if (error != null) { + logger.error({ err: error, container_id }, 'error killing container') + return callback(error) + } else { + return callback() + } + }) + }, - kill(container_id, callback) { - if (callback == null) { callback = function(error) {}; } - logger.log({container_id}, "sending kill signal to container"); - const container = dockerode.getContainer(container_id); - return container.kill(function(error) { - if ((error != null) && __guardMethod__(error != null ? error.message : undefined, 'match', o => o.match(/Cannot kill container .* is not running/))) { - logger.warn({err: error, container_id}, "container not running, continuing"); - error = null; - } - if (error != null) { - logger.error({err: error, container_id}, "error killing container"); - return callback(error); - } else { - return callback(); - } - }); - }, + _runAndWaitForContainer(options, volumes, timeout, _callback) { + if (_callback == null) { + _callback = function(error, output) {} + } + const callback = function(...args) { + _callback(...Array.from(args || [])) + // Only call the callback once + return (_callback = function() {}) + } - _runAndWaitForContainer(options, volumes, timeout, _callback) { - if (_callback == null) { _callback = function(error, output) {}; } - const callback = function(...args) { - _callback(...Array.from(args || [])); - // Only call the callback once - return _callback = function() {}; - }; + const { name } = options - const { name } = options; + let streamEnded = false + let containerReturned = false + let output = {} - let streamEnded = false; - let containerReturned = false; - let output = {}; + const callbackIfFinished = function() { + if (streamEnded && containerReturned) { + return callback(null, output) + } + } - const callbackIfFinished = function() { - if (streamEnded && containerReturned) { - return callback(null, output); - } - }; + const attachStreamHandler = function(error, _output) { + if (error != null) { + return callback(error) + } + output = _output + streamEnded = true + return callbackIfFinished() + } - const attachStreamHandler = function(error, _output) { - if (error != null) { return callback(error); } - output = _output; - streamEnded = true; - return callbackIfFinished(); - }; + return DockerRunner.startContainer( + options, + volumes, + attachStreamHandler, + function(error, containerId) { + if (error != null) { + return callback(error) + } - return DockerRunner.startContainer(options, volumes, attachStreamHandler, function(error, containerId) { - if (error != null) { return callback(error); } - - return DockerRunner.waitForContainer(name, timeout, function(error, exitCode) { - let err; - if (error != null) { return callback(error); } - if (exitCode === 137) { // exit status from kill -9 - err = DockerRunner.ERR_TERMINATED; - err.terminated = true; - return callback(err); - } - if (exitCode === 1) { // exit status from chktex - err = DockerRunner.ERR_EXITED; - err.code = exitCode; - return callback(err); - } - containerReturned = true; - __guard__(options != null ? options.HostConfig : undefined, x => x.SecurityOpt = null); // small log line - logger.log({err, exitCode, options}, "docker container has exited"); - return callbackIfFinished(); - }); - }); - }, + return DockerRunner.waitForContainer(name, timeout, function( + error, + exitCode + ) { + let err + if (error != null) { + return callback(error) + } + if (exitCode === 137) { + // exit status from kill -9 + err = DockerRunner.ERR_TERMINATED + err.terminated = true + return callback(err) + } + if (exitCode === 1) { + // exit status from chktex + err = DockerRunner.ERR_EXITED + err.code = exitCode + return callback(err) + } + containerReturned = true + __guard__( + options != null ? options.HostConfig : undefined, + x => (x.SecurityOpt = null) + ) // small log line + logger.log({ err, exitCode, options }, 'docker container has exited') + return callbackIfFinished() + }) + } + ) + }, - _getContainerOptions(command, image, volumes, timeout, environment) { - let m, year; - let key, value, hostVol, dockerVol; - const timeoutInSeconds = timeout / 1000; + _getContainerOptions(command, image, volumes, timeout, environment) { + let m, year + let key, value, hostVol, dockerVol + const timeoutInSeconds = timeout / 1000 - const dockerVolumes = {}; - for (hostVol in volumes) { - dockerVol = volumes[hostVol]; - dockerVolumes[dockerVol] = {}; + const dockerVolumes = {} + for (hostVol in volumes) { + dockerVol = volumes[hostVol] + dockerVolumes[dockerVol] = {} - if (volumes[hostVol].slice(-3).indexOf(":r") === -1) { - volumes[hostVol] = `${dockerVol}:rw`; - } - } + if (volumes[hostVol].slice(-3).indexOf(':r') === -1) { + volumes[hostVol] = `${dockerVol}:rw` + } + } - // merge settings and environment parameter - const env = {}; - for (const src of [Settings.clsi.docker.env, environment || {}]) { - for (key in src) { value = src[key]; env[key] = value; } - } - // set the path based on the image year - if ((m = image.match(/:([0-9]+)\.[0-9]+/))) { - year = m[1]; - } else { - year = "2014"; - } - env.PATH = `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/${year}/bin/x86_64-linux/`; - const options = { - "Cmd" : command, - "Image" : image, - "Volumes" : dockerVolumes, - "WorkingDir" : "/compile", - "NetworkDisabled" : true, - "Memory" : 1024 * 1024 * 1024 * 1024, // 1 Gb - "User" : Settings.clsi.docker.user, - "Env" : (((() => { - const result = []; - for (key in env) { - value = env[key]; - result.push(`${key}=${value}`); - } - return result; - })())), // convert the environment hash to an array - "HostConfig" : { - "Binds": (((() => { - const result1 = []; - for (hostVol in volumes) { - dockerVol = volumes[hostVol]; - result1.push(`${hostVol}:${dockerVol}`); - } - return result1; - })())), - "LogConfig": {"Type": "none", "Config": {}}, - "Ulimits": [{'Name': 'cpu', 'Soft': timeoutInSeconds+5, 'Hard': timeoutInSeconds+10}], - "CapDrop": "ALL", - "SecurityOpt": ["no-new-privileges"] - } - }; + // merge settings and environment parameter + const env = {} + for (const src of [Settings.clsi.docker.env, environment || {}]) { + for (key in src) { + value = src[key] + env[key] = value + } + } + // set the path based on the image year + if ((m = image.match(/:([0-9]+)\.[0-9]+/))) { + year = m[1] + } else { + year = '2014' + } + env.PATH = `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/${year}/bin/x86_64-linux/` + const options = { + Cmd: command, + Image: image, + Volumes: dockerVolumes, + WorkingDir: '/compile', + NetworkDisabled: true, + Memory: 1024 * 1024 * 1024 * 1024, // 1 Gb + User: Settings.clsi.docker.user, + Env: (() => { + const result = [] + for (key in env) { + value = env[key] + result.push(`${key}=${value}`) + } + return result + })(), // convert the environment hash to an array + HostConfig: { + Binds: (() => { + const result1 = [] + for (hostVol in volumes) { + dockerVol = volumes[hostVol] + result1.push(`${hostVol}:${dockerVol}`) + } + return result1 + })(), + LogConfig: { Type: 'none', Config: {} }, + Ulimits: [ + { + Name: 'cpu', + Soft: timeoutInSeconds + 5, + Hard: timeoutInSeconds + 10 + } + ], + CapDrop: 'ALL', + SecurityOpt: ['no-new-privileges'] + } + } + if ( + (Settings.path != null ? Settings.path.synctexBinHostPath : undefined) != + null + ) { + options.HostConfig.Binds.push( + `${Settings.path.synctexBinHostPath}:/opt/synctex:ro` + ) + } - if ((Settings.path != null ? Settings.path.synctexBinHostPath : undefined) != null) { - options.HostConfig.Binds.push(`${Settings.path.synctexBinHostPath}:/opt/synctex:ro`); - } + if (Settings.clsi.docker.seccomp_profile != null) { + options.HostConfig.SecurityOpt.push( + `seccomp=${Settings.clsi.docker.seccomp_profile}` + ) + } - if (Settings.clsi.docker.seccomp_profile != null) { - options.HostConfig.SecurityOpt.push(`seccomp=${Settings.clsi.docker.seccomp_profile}`); - } + return options + }, - return options; - }, + _fingerprintContainer(containerOptions) { + // Yay, Hashing! + const json = JSON.stringify(containerOptions) + return crypto + .createHash('md5') + .update(json) + .digest('hex') + }, - _fingerprintContainer(containerOptions) { - // Yay, Hashing! - const json = JSON.stringify(containerOptions); - return crypto.createHash("md5").update(json).digest("hex"); - }, + startContainer(options, volumes, attachStreamHandler, callback) { + return LockManager.runWithLock( + options.name, + releaseLock => + // Check that volumes exist before starting the container. + // When a container is started with volume pointing to a + // non-existent directory then docker creates the directory but + // with root ownership. + DockerRunner._checkVolumes(options, volumes, function(err) { + if (err != null) { + return releaseLock(err) + } + return DockerRunner._startContainer( + options, + volumes, + attachStreamHandler, + releaseLock + ) + }), - startContainer(options, volumes, attachStreamHandler, callback) { - return LockManager.runWithLock(options.name, releaseLock => - // Check that volumes exist before starting the container. - // When a container is started with volume pointing to a - // non-existent directory then docker creates the directory but - // with root ownership. - DockerRunner._checkVolumes(options, volumes, function(err) { - if (err != null) { return releaseLock(err); } - return DockerRunner._startContainer(options, volumes, attachStreamHandler, releaseLock); - }) - - , callback); - }, + callback + ) + }, - // Check that volumes exist and are directories - _checkVolumes(options, volumes, callback) { - if (callback == null) { callback = function(error, containerName) {}; } - if (usingSiblingContainers()) { - // Server Pro, with sibling-containers active, skip checks - return callback(null); - } + // Check that volumes exist and are directories + _checkVolumes(options, volumes, callback) { + if (callback == null) { + callback = function(error, containerName) {} + } + if (usingSiblingContainers()) { + // Server Pro, with sibling-containers active, skip checks + return callback(null) + } - const checkVolume = (path, cb) => - fs.stat(path, function(err, stats) { - if (err != null) { return cb(err); } - if (!(stats != null ? stats.isDirectory() : undefined)) { return cb(DockerRunner.ERR_NOT_DIRECTORY); } - return cb(); - }) - ; - const jobs = []; - for (const vol in volumes) { - (vol => jobs.push(cb => checkVolume(vol, cb)))(vol); - } - return async.series(jobs, callback); - }, + const checkVolume = (path, cb) => + fs.stat(path, function(err, stats) { + if (err != null) { + return cb(err) + } + if (!(stats != null ? stats.isDirectory() : undefined)) { + return cb(DockerRunner.ERR_NOT_DIRECTORY) + } + return cb() + }) + const jobs = [] + for (const vol in volumes) { + ;(vol => jobs.push(cb => checkVolume(vol, cb)))(vol) + } + return async.series(jobs, callback) + }, - _startContainer(options, volumes, attachStreamHandler, callback) { - if (callback == null) { callback = function(error, output) {}; } - callback = _.once(callback); - const { name } = options; + _startContainer(options, volumes, attachStreamHandler, callback) { + if (callback == null) { + callback = function(error, output) {} + } + callback = _.once(callback) + const { name } = options - logger.log({container_name: name}, "starting container"); - const container = dockerode.getContainer(name); + logger.log({ container_name: name }, 'starting container') + const container = dockerode.getContainer(name) - const createAndStartContainer = () => - dockerode.createContainer(options, function(error, container) { - if (error != null) { return callback(error); } - return startExistingContainer(); - }) - ; + const createAndStartContainer = () => + dockerode.createContainer(options, function(error, container) { + if (error != null) { + return callback(error) + } + return startExistingContainer() + }) + var startExistingContainer = () => + DockerRunner.attachToContainer( + options.name, + attachStreamHandler, + function(error) { + if (error != null) { + return callback(error) + } + return container.start(function(error) { + if ( + error != null && + (error != null ? error.statusCode : undefined) !== 304 + ) { + // already running + return callback(error) + } else { + return callback() + } + }) + } + ) + return container.inspect(function(error, stats) { + if ((error != null ? error.statusCode : undefined) === 404) { + return createAndStartContainer() + } else if (error != null) { + logger.err( + { container_name: name, error }, + 'unable to inspect container to start' + ) + return callback(error) + } else { + return startExistingContainer() + } + }) + }, - var startExistingContainer = () => - DockerRunner.attachToContainer(options.name, attachStreamHandler, function(error){ - if (error != null) { return callback(error); } - return container.start(function(error) { - if ((error != null) && ((error != null ? error.statusCode : undefined) !== 304)) { // already running - return callback(error); - } else { - return callback(); - } - }); - }) - ; + attachToContainer(containerId, attachStreamHandler, attachStartCallback) { + const container = dockerode.getContainer(containerId) + return container.attach({ stdout: 1, stderr: 1, stream: 1 }, function( + error, + stream + ) { + if (error != null) { + logger.error( + { err: error, container_id: containerId }, + 'error attaching to container' + ) + return attachStartCallback(error) + } else { + attachStartCallback() + } - return container.inspect(function(error, stats){ - if ((error != null ? error.statusCode : undefined) === 404) { - return createAndStartContainer(); - } else if (error != null) { - logger.err({container_name: name, error}, "unable to inspect container to start"); - return callback(error); - } else { - return startExistingContainer(); - } - }); - }, + logger.log({ container_id: containerId }, 'attached to container') + const MAX_OUTPUT = 1024 * 1024 // limit output to 1MB + const createStringOutputStream = function(name) { + return { + data: '', + overflowed: false, + write(data) { + if (this.overflowed) { + return + } + if (this.data.length < MAX_OUTPUT) { + return (this.data += data) + } else { + logger.error( + { + container_id: containerId, + length: this.data.length, + maxLen: MAX_OUTPUT + }, + `${name} exceeds max size` + ) + this.data += `(...truncated at ${MAX_OUTPUT} chars...)` + return (this.overflowed = true) + } + } + // kill container if too much output + // docker.containers.kill(containerId, () ->) + } + } - attachToContainer(containerId, attachStreamHandler, attachStartCallback) { - const container = dockerode.getContainer(containerId); - return container.attach({stdout: 1, stderr: 1, stream: 1}, function(error, stream) { - if (error != null) { - logger.error({err: error, container_id: containerId}, "error attaching to container"); - return attachStartCallback(error); - } else { - attachStartCallback(); - } + const stdout = createStringOutputStream('stdout') + const stderr = createStringOutputStream('stderr') + container.modem.demuxStream(stream, stdout, stderr) - logger.log({container_id: containerId}, "attached to container"); + stream.on('error', err => + logger.error( + { err, container_id: containerId }, + 'error reading from container stream' + ) + ) - const MAX_OUTPUT = 1024 * 1024; // limit output to 1MB - const createStringOutputStream = function(name) { - return { - data: "", - overflowed: false, - write(data) { - if (this.overflowed) { return; } - if (this.data.length < MAX_OUTPUT) { - return this.data += data; - } else { - logger.error({container_id: containerId, length: this.data.length, maxLen: MAX_OUTPUT}, `${name} exceeds max size`); - this.data += `(...truncated at ${MAX_OUTPUT} chars...)`; - return this.overflowed = true; - } - } - // kill container if too much output - // docker.containers.kill(containerId, () ->) - }; - }; + return stream.on('end', () => + attachStreamHandler(null, { stdout: stdout.data, stderr: stderr.data }) + ) + }) + }, - const stdout = createStringOutputStream("stdout"); - const stderr = createStringOutputStream("stderr"); + waitForContainer(containerId, timeout, _callback) { + if (_callback == null) { + _callback = function(error, exitCode) {} + } + const callback = function(...args) { + _callback(...Array.from(args || [])) + // Only call the callback once + return (_callback = function() {}) + } - container.modem.demuxStream(stream, stdout, stderr); + const container = dockerode.getContainer(containerId) - stream.on("error", err => logger.error({err, container_id: containerId}, "error reading from container stream")); + let timedOut = false + const timeoutId = setTimeout(function() { + timedOut = true + logger.log( + { container_id: containerId }, + 'timeout reached, killing container' + ) + return container.kill(function() {}) + }, timeout) - return stream.on("end", () => attachStreamHandler(null, {stdout: stdout.data, stderr: stderr.data})); - }); - }, + logger.log({ container_id: containerId }, 'waiting for docker container') + return container.wait(function(error, res) { + if (error != null) { + clearTimeout(timeoutId) + logger.error( + { err: error, container_id: containerId }, + 'error waiting for container' + ) + return callback(error) + } + if (timedOut) { + logger.log({ containerId }, 'docker container timed out') + error = DockerRunner.ERR_TIMED_OUT + error.timedout = true + return callback(error) + } else { + clearTimeout(timeoutId) + logger.log( + { container_id: containerId, exitCode: res.StatusCode }, + 'docker container returned' + ) + return callback(null, res.StatusCode) + } + }) + }, - waitForContainer(containerId, timeout, _callback) { - if (_callback == null) { _callback = function(error, exitCode) {}; } - const callback = function(...args) { - _callback(...Array.from(args || [])); - // Only call the callback once - return _callback = function() {}; - }; + destroyContainer(containerName, containerId, shouldForce, callback) { + // We want the containerName for the lock and, ideally, the + // containerId to delete. There is a bug in the docker.io module + // where if you delete by name and there is an error, it throws an + // async exception, but if you delete by id it just does a normal + // error callback. We fall back to deleting by name if no id is + // supplied. + if (callback == null) { + callback = function(error) {} + } + return LockManager.runWithLock( + containerName, + releaseLock => + DockerRunner._destroyContainer( + containerId || containerName, + shouldForce, + releaseLock + ), + callback + ) + }, - const container = dockerode.getContainer(containerId); + _destroyContainer(containerId, shouldForce, callback) { + if (callback == null) { + callback = function(error) {} + } + logger.log({ container_id: containerId }, 'destroying docker container') + const container = dockerode.getContainer(containerId) + return container.remove({ force: shouldForce === true }, function(error) { + if ( + error != null && + (error != null ? error.statusCode : undefined) === 404 + ) { + logger.warn( + { err: error, container_id: containerId }, + 'container not found, continuing' + ) + error = null + } + if (error != null) { + logger.error( + { err: error, container_id: containerId }, + 'error destroying container' + ) + } else { + logger.log({ container_id: containerId }, 'destroyed container') + } + return callback(error) + }) + }, - let timedOut = false; - const timeoutId = setTimeout(function() { - timedOut = true; - logger.log({container_id: containerId}, "timeout reached, killing container"); - return container.kill(function() {}); - } - , timeout); + // handle expiry of docker containers - logger.log({container_id: containerId}, "waiting for docker container"); - return container.wait(function(error, res) { - if (error != null) { - clearTimeout(timeoutId); - logger.error({err: error, container_id: containerId}, "error waiting for container"); - return callback(error); - } - if (timedOut) { - logger.log({containerId}, "docker container timed out"); - error = DockerRunner.ERR_TIMED_OUT; - error.timedout = true; - return callback(error); - } else { - clearTimeout(timeoutId); - logger.log({container_id: containerId, exitCode: res.StatusCode}, "docker container returned"); - return callback(null, res.StatusCode); - } - }); - }, + MAX_CONTAINER_AGE: + Settings.clsi.docker.maxContainerAge || (oneHour = 60 * 60 * 1000), - destroyContainer(containerName, containerId, shouldForce, callback) { - // We want the containerName for the lock and, ideally, the - // containerId to delete. There is a bug in the docker.io module - // where if you delete by name and there is an error, it throws an - // async exception, but if you delete by id it just does a normal - // error callback. We fall back to deleting by name if no id is - // supplied. - if (callback == null) { callback = function(error) {}; } - return LockManager.runWithLock(containerName, releaseLock => DockerRunner._destroyContainer(containerId || containerName, shouldForce, releaseLock) - , callback); - }, + examineOldContainer(container, callback) { + if (callback == null) { + callback = function(error, name, id, ttl) {} + } + const name = + container.Name || + (container.Names != null ? container.Names[0] : undefined) + const created = container.Created * 1000 // creation time is returned in seconds + const now = Date.now() + const age = now - created + const maxAge = DockerRunner.MAX_CONTAINER_AGE + const ttl = maxAge - age + logger.log( + { containerName: name, created, now, age, maxAge, ttl }, + 'checking whether to destroy container' + ) + return callback(null, name, container.Id, ttl) + }, - _destroyContainer(containerId, shouldForce, callback) { - if (callback == null) { callback = function(error) {}; } - logger.log({container_id: containerId}, "destroying docker container"); - const container = dockerode.getContainer(containerId); - return container.remove({force: shouldForce === true}, function(error) { - if ((error != null) && ((error != null ? error.statusCode : undefined) === 404)) { - logger.warn({err: error, container_id: containerId}, "container not found, continuing"); - error = null; - } - if (error != null) { - logger.error({err: error, container_id: containerId}, "error destroying container"); - } else { - logger.log({container_id: containerId}, "destroyed container"); - } - return callback(error); - }); - }, + destroyOldContainers(callback) { + if (callback == null) { + callback = function(error) {} + } + return dockerode.listContainers({ all: true }, function(error, containers) { + if (error != null) { + return callback(error) + } + const jobs = [] + for (const container of Array.from(containers || [])) { + ;(container => + DockerRunner.examineOldContainer(container, function( + err, + name, + id, + ttl + ) { + if (name.slice(0, 9) === '/project-' && ttl <= 0) { + return jobs.push(cb => + DockerRunner.destroyContainer(name, id, false, () => cb()) + ) + } + }))(container) + } + // Ignore errors because some containers get stuck but + // will be destroyed next time + return async.series(jobs, callback) + }) + }, - // handle expiry of docker containers + startContainerMonitor() { + logger.log( + { maxAge: DockerRunner.MAX_CONTAINER_AGE }, + 'starting container expiry' + ) + // randomise the start time + const randomDelay = Math.floor(Math.random() * 5 * 60 * 1000) + return setTimeout( + () => + setInterval( + () => DockerRunner.destroyOldContainers(), + (oneHour = 60 * 60 * 1000) + ), - MAX_CONTAINER_AGE: Settings.clsi.docker.maxContainerAge || (oneHour = 60 * 60 * 1000), + randomDelay + ) + } +} - examineOldContainer(container, callback) { - if (callback == null) { callback = function(error, name, id, ttl){}; } - const name = container.Name || (container.Names != null ? container.Names[0] : undefined); - const created = container.Created * 1000; // creation time is returned in seconds - const now = Date.now(); - const age = now - created; - const maxAge = DockerRunner.MAX_CONTAINER_AGE; - const ttl = maxAge - age; - logger.log({containerName: name, created, now, age, maxAge, ttl}, "checking whether to destroy container"); - return callback(null, name, container.Id, ttl); - }, - - destroyOldContainers(callback) { - if (callback == null) { callback = function(error) {}; } - return dockerode.listContainers({all: true}, function(error, containers) { - if (error != null) { return callback(error); } - const jobs = []; - for (const container of Array.from(containers || [])) { - (container => - DockerRunner.examineOldContainer(container, function(err, name, id, ttl) { - if ((name.slice(0, 9) === '/project-') && (ttl <= 0)) { - return jobs.push(cb => DockerRunner.destroyContainer(name, id, false, () => cb())); - } - }) - )(container); - } - // Ignore errors because some containers get stuck but - // will be destroyed next time - return async.series(jobs, callback); - }); - }, - - startContainerMonitor() { - logger.log({maxAge: DockerRunner.MAX_CONTAINER_AGE}, "starting container expiry"); - // randomise the start time - const randomDelay = Math.floor(Math.random() * 5 * 60 * 1000); - return setTimeout(() => - setInterval(() => DockerRunner.destroyOldContainers() - , (oneHour = 60 * 60 * 1000)) - - , randomDelay); - } -}); - -DockerRunner.startContainerMonitor(); +DockerRunner.startContainerMonitor() function __guard__(value, transform) { - return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; + return typeof value !== 'undefined' && value !== null + ? transform(value) + : undefined } function __guardMethod__(obj, methodName, transform) { - if (typeof obj !== 'undefined' && obj !== null && typeof obj[methodName] === 'function') { - return transform(obj, methodName); + if ( + typeof obj !== 'undefined' && + obj !== null && + typeof obj[methodName] === 'function' + ) { + return transform(obj, methodName) } else { - return undefined; + return undefined } -} \ No newline at end of file +} diff --git a/services/clsi/app/js/DraftModeManager.js b/services/clsi/app/js/DraftModeManager.js index 79f39ab24d..c8f59aa613 100644 --- a/services/clsi/app/js/DraftModeManager.js +++ b/services/clsi/app/js/DraftModeManager.js @@ -11,34 +11,47 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let DraftModeManager; -const fs = require("fs"); -const logger = require("logger-sharelatex"); +let DraftModeManager +const fs = require('fs') +const logger = require('logger-sharelatex') -module.exports = (DraftModeManager = { - injectDraftMode(filename, callback) { - if (callback == null) { callback = function(error) {}; } - return fs.readFile(filename, "utf8", function(error, content) { - if (error != null) { return callback(error); } - // avoid adding draft mode more than once - if ((content != null ? content.indexOf("\\documentclass\[draft") : undefined) >= 0) { - return callback(); - } - const modified_content = DraftModeManager._injectDraftOption(content); - logger.log({ - content: content.slice(0,1024), // \documentclass is normally v near the top - modified_content: modified_content.slice(0,1024), - filename - }, "injected draft class"); - return fs.writeFile(filename, modified_content, callback); - }); - }, - - _injectDraftOption(content) { - return content - // With existing options (must be first, otherwise both are applied) - .replace(/\\documentclass\[/g, "\\documentclass[draft,") - // Without existing options - .replace(/\\documentclass\{/g, "\\documentclass[draft]{"); - } -}); +module.exports = DraftModeManager = { + injectDraftMode(filename, callback) { + if (callback == null) { + callback = function(error) {} + } + return fs.readFile(filename, 'utf8', function(error, content) { + if (error != null) { + return callback(error) + } + // avoid adding draft mode more than once + if ( + (content != null + ? content.indexOf('\\documentclass[draft') + : undefined) >= 0 + ) { + return callback() + } + const modified_content = DraftModeManager._injectDraftOption(content) + logger.log( + { + content: content.slice(0, 1024), // \documentclass is normally v near the top + modified_content: modified_content.slice(0, 1024), + filename + }, + 'injected draft class' + ) + return fs.writeFile(filename, modified_content, callback) + }) + }, + + _injectDraftOption(content) { + return ( + content + // With existing options (must be first, otherwise both are applied) + .replace(/\\documentclass\[/g, '\\documentclass[draft,') + // Without existing options + .replace(/\\documentclass\{/g, '\\documentclass[draft]{') + ) + } +} diff --git a/services/clsi/app/js/Errors.js b/services/clsi/app/js/Errors.js index e7ace2c2ce..d3a5f5a066 100644 --- a/services/clsi/app/js/Errors.js +++ b/services/clsi/app/js/Errors.js @@ -4,33 +4,33 @@ */ // TODO: This file was created by bulk-decaffeinate. // Fix any style issues and re-enable lint. -let Errors; +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; + const error = new Error(message) + error.name = 'NotFoundError' + error.__proto__ = NotFoundError.prototype + return error +} +NotFoundError.prototype.__proto__ = Error.prototype var FilesOutOfSyncError = function(message) { - const error = new Error(message); - error.name = "FilesOutOfSyncError"; - error.__proto__ = FilesOutOfSyncError.prototype; - return error; -}; -FilesOutOfSyncError.prototype.__proto__ = Error.prototype; + const error = new Error(message) + error.name = 'FilesOutOfSyncError' + error.__proto__ = FilesOutOfSyncError.prototype + return error +} +FilesOutOfSyncError.prototype.__proto__ = Error.prototype var AlreadyCompilingError = function(message) { - const error = new Error(message); - error.name = "AlreadyCompilingError"; - error.__proto__ = AlreadyCompilingError.prototype; - return error; -}; -AlreadyCompilingError.prototype.__proto__ = Error.prototype; + const error = new Error(message) + error.name = 'AlreadyCompilingError' + error.__proto__ = AlreadyCompilingError.prototype + return error +} +AlreadyCompilingError.prototype.__proto__ = Error.prototype -module.exports = (Errors = { - NotFoundError, - FilesOutOfSyncError, - AlreadyCompilingError -}); +module.exports = Errors = { + NotFoundError, + FilesOutOfSyncError, + AlreadyCompilingError +} diff --git a/services/clsi/app/js/LatexRunner.js b/services/clsi/app/js/LatexRunner.js index e569df8d16..972f1fe7c3 100644 --- a/services/clsi/app/js/LatexRunner.js +++ b/services/clsi/app/js/LatexRunner.js @@ -13,119 +13,192 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let LatexRunner; -const Path = require("path"); -const Settings = require("settings-sharelatex"); -const logger = require("logger-sharelatex"); -const Metrics = require("./Metrics"); -const CommandRunner = require("./CommandRunner"); +let LatexRunner +const Path = require('path') +const Settings = require('settings-sharelatex') +const logger = require('logger-sharelatex') +const Metrics = require('./Metrics') +const CommandRunner = require('./CommandRunner') -const ProcessTable = {}; // table of currently running jobs (pids or docker container names) +const ProcessTable = {} // table of currently running jobs (pids or docker container names) -module.exports = (LatexRunner = { - runLatex(project_id, options, callback) { - let command; - if (callback == null) { callback = function(error) {}; } - let {directory, mainFile, compiler, timeout, image, environment, flags} = options; - if (!compiler) { compiler = "pdflatex"; } - if (!timeout) { timeout = 60000; } // milliseconds +module.exports = LatexRunner = { + runLatex(project_id, options, callback) { + let command + if (callback == null) { + callback = function(error) {} + } + let { + directory, + mainFile, + compiler, + timeout, + image, + environment, + flags + } = options + if (!compiler) { + compiler = 'pdflatex' + } + if (!timeout) { + timeout = 60000 + } // milliseconds - logger.log({directory, compiler, timeout, mainFile, environment, flags}, "starting compile"); + logger.log( + { directory, compiler, timeout, mainFile, environment, flags }, + 'starting compile' + ) - // We want to run latexmk on the tex file which we will automatically - // generate from the Rtex/Rmd/md file. - mainFile = mainFile.replace(/\.(Rtex|md|Rmd)$/, ".tex"); + // We want to run latexmk on the tex file which we will automatically + // generate from the Rtex/Rmd/md file. + mainFile = mainFile.replace(/\.(Rtex|md|Rmd)$/, '.tex') - if (compiler === "pdflatex") { - command = LatexRunner._pdflatexCommand(mainFile, flags); - } else if (compiler === "latex") { - command = LatexRunner._latexCommand(mainFile, flags); - } else if (compiler === "xelatex") { - command = LatexRunner._xelatexCommand(mainFile, flags); - } else if (compiler === "lualatex") { - command = LatexRunner._lualatexCommand(mainFile, flags); - } else { - return callback(new Error(`unknown compiler: ${compiler}`)); - } + if (compiler === 'pdflatex') { + command = LatexRunner._pdflatexCommand(mainFile, flags) + } else if (compiler === 'latex') { + command = LatexRunner._latexCommand(mainFile, flags) + } else if (compiler === 'xelatex') { + command = LatexRunner._xelatexCommand(mainFile, flags) + } else if (compiler === 'lualatex') { + command = LatexRunner._lualatexCommand(mainFile, flags) + } else { + return callback(new Error(`unknown compiler: ${compiler}`)) + } - if (Settings.clsi != null ? Settings.clsi.strace : undefined) { - command = ["strace", "-o", "strace", "-ff"].concat(command); - } + if (Settings.clsi != null ? Settings.clsi.strace : undefined) { + command = ['strace', '-o', 'strace', '-ff'].concat(command) + } - const id = `${project_id}`; // record running project under this id + const id = `${project_id}` // record running project under this id - return ProcessTable[id] = CommandRunner.run(project_id, command, directory, image, timeout, environment, function(error, output) { - delete ProcessTable[id]; - if (error != null) { return callback(error); } - const runs = __guard__(__guard__(output != null ? output.stderr : undefined, x1 => x1.match(/^Run number \d+ of .*latex/mg)), x => x.length) || 0; - const failed = (__guard__(output != null ? output.stdout : undefined, x2 => x2.match(/^Latexmk: Errors/m)) != null) ? 1 : 0; - // counters from latexmk output - const stats = {}; - stats["latexmk-errors"] = failed; - stats["latex-runs"] = runs; - stats["latex-runs-with-errors"] = failed ? runs : 0; - stats[`latex-runs-${runs}`] = 1; - stats[`latex-runs-with-errors-${runs}`] = failed ? 1 : 0; - // timing information from /usr/bin/time - const timings = {}; - const stderr = output != null ? output.stderr : undefined; - timings["cpu-percent"] = __guard__(stderr != null ? stderr.match(/Percent of CPU this job got: (\d+)/m) : undefined, x3 => x3[1]) || 0; - timings["cpu-time"] = __guard__(stderr != null ? stderr.match(/User time.*: (\d+.\d+)/m) : undefined, x4 => x4[1]) || 0; - timings["sys-time"] = __guard__(stderr != null ? stderr.match(/System time.*: (\d+.\d+)/m) : undefined, x5 => x5[1]) || 0; - return callback(error, output, stats, timings); - }); - }, + return (ProcessTable[id] = CommandRunner.run( + project_id, + command, + directory, + image, + timeout, + environment, + function(error, output) { + delete ProcessTable[id] + if (error != null) { + return callback(error) + } + const runs = + __guard__( + __guard__(output != null ? output.stderr : undefined, x1 => + x1.match(/^Run number \d+ of .*latex/gm) + ), + x => x.length + ) || 0 + const failed = + __guard__(output != null ? output.stdout : undefined, x2 => + x2.match(/^Latexmk: Errors/m) + ) != null + ? 1 + : 0 + // counters from latexmk output + const stats = {} + stats['latexmk-errors'] = failed + stats['latex-runs'] = runs + stats['latex-runs-with-errors'] = failed ? runs : 0 + stats[`latex-runs-${runs}`] = 1 + stats[`latex-runs-with-errors-${runs}`] = failed ? 1 : 0 + // timing information from /usr/bin/time + const timings = {} + const stderr = output != null ? output.stderr : undefined + timings['cpu-percent'] = + __guard__( + stderr != null + ? stderr.match(/Percent of CPU this job got: (\d+)/m) + : undefined, + x3 => x3[1] + ) || 0 + timings['cpu-time'] = + __guard__( + stderr != null + ? stderr.match(/User time.*: (\d+.\d+)/m) + : undefined, + x4 => x4[1] + ) || 0 + timings['sys-time'] = + __guard__( + stderr != null + ? stderr.match(/System time.*: (\d+.\d+)/m) + : undefined, + x5 => x5[1] + ) || 0 + return callback(error, output, stats, timings) + } + )) + }, - killLatex(project_id, callback) { - if (callback == null) { callback = function(error) {}; } - const id = `${project_id}`; - logger.log({id}, "killing running compile"); - if ((ProcessTable[id] == null)) { - logger.warn({id}, "no such project to kill"); - return callback(null); - } else { - return CommandRunner.kill(ProcessTable[id], callback); - } - }, + killLatex(project_id, callback) { + if (callback == null) { + callback = function(error) {} + } + const id = `${project_id}` + logger.log({ id }, 'killing running compile') + if (ProcessTable[id] == null) { + logger.warn({ id }, 'no such project to kill') + return callback(null) + } else { + return CommandRunner.kill(ProcessTable[id], callback) + } + }, - _latexmkBaseCommand(flags) { - let args = ["latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR", "-synctex=1","-interaction=batchmode"]; - if (flags) { - args = args.concat(flags); - } - return (__guard__(Settings != null ? Settings.clsi : undefined, x => x.latexmkCommandPrefix) || []).concat(args); - }, + _latexmkBaseCommand(flags) { + let args = [ + 'latexmk', + '-cd', + '-f', + '-jobname=output', + '-auxdir=$COMPILE_DIR', + '-outdir=$COMPILE_DIR', + '-synctex=1', + '-interaction=batchmode' + ] + if (flags) { + args = args.concat(flags) + } + return ( + __guard__( + Settings != null ? Settings.clsi : undefined, + x => x.latexmkCommandPrefix + ) || [] + ).concat(args) + }, - _pdflatexCommand(mainFile, flags) { - return LatexRunner._latexmkBaseCommand(flags).concat([ - "-pdf", - Path.join("$COMPILE_DIR", mainFile) - ]); - }, + _pdflatexCommand(mainFile, flags) { + return LatexRunner._latexmkBaseCommand(flags).concat([ + '-pdf', + Path.join('$COMPILE_DIR', mainFile) + ]) + }, - _latexCommand(mainFile, flags) { - return LatexRunner._latexmkBaseCommand(flags).concat([ - "-pdfdvi", - Path.join("$COMPILE_DIR", mainFile) - ]); - }, + _latexCommand(mainFile, flags) { + return LatexRunner._latexmkBaseCommand(flags).concat([ + '-pdfdvi', + Path.join('$COMPILE_DIR', mainFile) + ]) + }, - _xelatexCommand(mainFile, flags) { - return LatexRunner._latexmkBaseCommand(flags).concat([ - "-xelatex", - Path.join("$COMPILE_DIR", mainFile) - ]); - }, - - _lualatexCommand(mainFile, flags) { - return LatexRunner._latexmkBaseCommand(flags).concat([ - "-lualatex", - Path.join("$COMPILE_DIR", mainFile) - ]); - } -}); + _xelatexCommand(mainFile, flags) { + return LatexRunner._latexmkBaseCommand(flags).concat([ + '-xelatex', + Path.join('$COMPILE_DIR', mainFile) + ]) + }, + _lualatexCommand(mainFile, flags) { + return LatexRunner._latexmkBaseCommand(flags).concat([ + '-lualatex', + Path.join('$COMPILE_DIR', mainFile) + ]) + } +} function __guard__(value, transform) { - return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; -} \ No newline at end of file + return typeof value !== 'undefined' && value !== null + ? transform(value) + : undefined +} diff --git a/services/clsi/app/js/LocalCommandRunner.js b/services/clsi/app/js/LocalCommandRunner.js index 24c0d8eafb..61ecd88794 100644 --- a/services/clsi/app/js/LocalCommandRunner.js +++ b/services/clsi/app/js/LocalCommandRunner.js @@ -13,62 +13,79 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let CommandRunner; -const { spawn } = require("child_process"); -const logger = require("logger-sharelatex"); +let CommandRunner +const { spawn } = require('child_process') +const logger = require('logger-sharelatex') -logger.info("using standard command runner"); +logger.info('using standard command runner') -module.exports = (CommandRunner = { - run(project_id, command, directory, image, timeout, environment, callback) { - let key, value; - if (callback == null) { callback = function(error) {}; } - command = (Array.from(command).map((arg) => arg.toString().replace('$COMPILE_DIR', directory))); - logger.log({project_id, command, directory}, "running command"); - logger.warn("timeouts and sandboxing are not enabled with CommandRunner"); +module.exports = CommandRunner = { + run(project_id, command, directory, image, timeout, environment, callback) { + let key, value + if (callback == null) { + callback = function(error) {} + } + command = Array.from(command).map(arg => + arg.toString().replace('$COMPILE_DIR', directory) + ) + logger.log({ project_id, command, directory }, 'running command') + logger.warn('timeouts and sandboxing are not enabled with CommandRunner') - // merge environment settings - const env = {}; - for (key in process.env) { value = process.env[key]; env[key] = value; } - for (key in environment) { value = environment[key]; env[key] = value; } + // merge environment settings + const env = {} + for (key in process.env) { + value = process.env[key] + env[key] = value + } + for (key in environment) { + value = environment[key] + env[key] = value + } - // run command as detached process so it has its own process group (which can be killed if needed) - const proc = spawn(command[0], command.slice(1), {cwd: directory, env}); + // run command as detached process so it has its own process group (which can be killed if needed) + const proc = spawn(command[0], command.slice(1), { cwd: directory, env }) - let stdout = ""; - proc.stdout.on("data", data=> stdout += data); + let stdout = '' + proc.stdout.on('data', data => (stdout += data)) - proc.on("error", function(err){ - logger.err({err, project_id, command, directory}, "error running command"); - return callback(err); - }); + proc.on('error', function(err) { + logger.err( + { err, project_id, command, directory }, + 'error running command' + ) + return callback(err) + }) - proc.on("close", function(code, signal) { - let err; - logger.info({code, signal, project_id}, "command exited"); - if (signal === 'SIGTERM') { // signal from kill method below - err = new Error("terminated"); - err.terminated = true; - return callback(err); - } else if (code === 1) { // exit status from chktex - err = new Error("exited"); - err.code = code; - return callback(err); - } else { - return callback(null, {"stdout": stdout}); - } - }); + proc.on('close', function(code, signal) { + let err + logger.info({ code, signal, project_id }, 'command exited') + if (signal === 'SIGTERM') { + // signal from kill method below + err = new Error('terminated') + err.terminated = true + return callback(err) + } else if (code === 1) { + // exit status from chktex + err = new Error('exited') + err.code = code + return callback(err) + } else { + return callback(null, { stdout: stdout }) + } + }) - return proc.pid; - }, // return process id to allow job to be killed if necessary + return proc.pid + }, // return process id to allow job to be killed if necessary - kill(pid, callback) { - if (callback == null) { callback = function(error) {}; } - try { - process.kill(-pid); // kill all processes in group - } catch (err) { - return callback(err); - } - return callback(); - } -}); + kill(pid, callback) { + if (callback == null) { + callback = function(error) {} + } + try { + process.kill(-pid) // kill all processes in group + } catch (err) { + return callback(err) + } + return callback() + } +} diff --git a/services/clsi/app/js/LockManager.js b/services/clsi/app/js/LockManager.js index 8930fab6ed..2da7da109f 100644 --- a/services/clsi/app/js/LockManager.js +++ b/services/clsi/app/js/LockManager.js @@ -11,46 +11,62 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let LockManager; -const Settings = require('settings-sharelatex'); -const logger = require("logger-sharelatex"); -const Lockfile = require('lockfile'); // from https://github.com/npm/lockfile -const Errors = require("./Errors"); -const fs = require("fs"); -const Path = require("path"); -module.exports = (LockManager = { - LOCK_TEST_INTERVAL: 1000, // 50ms between each test of the lock - MAX_LOCK_WAIT_TIME: 15000, // 10s maximum time to spend trying to get the lock - LOCK_STALE: 5*60*1000, // 5 mins time until lock auto expires +let LockManager +const Settings = require('settings-sharelatex') +const logger = require('logger-sharelatex') +const Lockfile = require('lockfile') // from https://github.com/npm/lockfile +const Errors = require('./Errors') +const fs = require('fs') +const Path = require('path') +module.exports = LockManager = { + LOCK_TEST_INTERVAL: 1000, // 50ms between each test of the lock + MAX_LOCK_WAIT_TIME: 15000, // 10s maximum time to spend trying to get the lock + LOCK_STALE: 5 * 60 * 1000, // 5 mins time until lock auto expires - runWithLock(path, runner, callback) { - if (callback == null) { callback = function(error) {}; } - const lockOpts = { - wait: this.MAX_LOCK_WAIT_TIME, - pollPeriod: this.LOCK_TEST_INTERVAL, - stale: this.LOCK_STALE - }; - return Lockfile.lock(path, lockOpts, function(error) { - if ((error != null ? error.code : undefined) === 'EEXIST') { - return callback(new Errors.AlreadyCompilingError("compile in progress")); - } else if (error != null) { - return fs.lstat(path, (statLockErr, statLock)=> - fs.lstat(Path.dirname(path), (statDirErr, statDir)=> - fs.readdir(Path.dirname(path), function(readdirErr, readdirDir){ - logger.err({error, path, statLock, statLockErr, statDir, statDirErr, readdirErr, readdirDir}, "unable to get lock"); - return callback(error); - }) - ) - ); - } else { - return runner((error1, ...args) => - Lockfile.unlock(path, function(error2) { - error = error1 || error2; - if (error != null) { return callback(error); } - return callback(null, ...Array.from(args)); - }) - ); - } - }); - } -}); + runWithLock(path, runner, callback) { + if (callback == null) { + callback = function(error) {} + } + const lockOpts = { + wait: this.MAX_LOCK_WAIT_TIME, + pollPeriod: this.LOCK_TEST_INTERVAL, + stale: this.LOCK_STALE + } + return Lockfile.lock(path, lockOpts, function(error) { + if ((error != null ? error.code : undefined) === 'EEXIST') { + return callback(new Errors.AlreadyCompilingError('compile in progress')) + } else if (error != null) { + return fs.lstat(path, (statLockErr, statLock) => + fs.lstat(Path.dirname(path), (statDirErr, statDir) => + fs.readdir(Path.dirname(path), function(readdirErr, readdirDir) { + logger.err( + { + error, + path, + statLock, + statLockErr, + statDir, + statDirErr, + readdirErr, + readdirDir + }, + 'unable to get lock' + ) + return callback(error) + }) + ) + ) + } else { + return runner((error1, ...args) => + Lockfile.unlock(path, function(error2) { + error = error1 || error2 + if (error != null) { + return callback(error) + } + return callback(null, ...Array.from(args)) + }) + ) + } + }) + } +} diff --git a/services/clsi/app/js/Metrics.js b/services/clsi/app/js/Metrics.js index 94623da3fa..e9676415ea 100644 --- a/services/clsi/app/js/Metrics.js +++ b/services/clsi/app/js/Metrics.js @@ -1,4 +1,3 @@ // TODO: This file was created by bulk-decaffeinate. // Sanity-check the conversion and remove this comment. -module.exports = require("metrics-sharelatex"); - +module.exports = require('metrics-sharelatex') diff --git a/services/clsi/app/js/OutputCacheManager.js b/services/clsi/app/js/OutputCacheManager.js index b1bda0e8c5..c2c962f1bc 100644 --- a/services/clsi/app/js/OutputCacheManager.js +++ b/services/clsi/app/js/OutputCacheManager.js @@ -13,263 +13,387 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let OutputCacheManager; -const async = require("async"); -const fs = require("fs"); -const fse = require("fs-extra"); -const Path = require("path"); -const logger = require("logger-sharelatex"); -const _ = require("underscore"); -const Settings = require("settings-sharelatex"); -const crypto = require("crypto"); +let OutputCacheManager +const async = require('async') +const fs = require('fs') +const fse = require('fs-extra') +const Path = require('path') +const logger = require('logger-sharelatex') +const _ = require('underscore') +const Settings = require('settings-sharelatex') +const crypto = require('crypto') -const OutputFileOptimiser = require("./OutputFileOptimiser"); +const OutputFileOptimiser = require('./OutputFileOptimiser') -module.exports = (OutputCacheManager = { - CACHE_SUBDIR: '.cache/clsi', - ARCHIVE_SUBDIR: '.archive/clsi', - // build id is HEXDATE-HEXRANDOM from Date.now()and RandomBytes - // for backwards compatibility, make the randombytes part optional - BUILD_REGEX: /^[0-9a-f]+(-[0-9a-f]+)?$/, - CACHE_LIMIT: 2, // maximum number of cache directories - CACHE_AGE: 60*60*1000, // up to one hour old +module.exports = OutputCacheManager = { + CACHE_SUBDIR: '.cache/clsi', + ARCHIVE_SUBDIR: '.archive/clsi', + // build id is HEXDATE-HEXRANDOM from Date.now()and RandomBytes + // for backwards compatibility, make the randombytes part optional + BUILD_REGEX: /^[0-9a-f]+(-[0-9a-f]+)?$/, + CACHE_LIMIT: 2, // maximum number of cache directories + CACHE_AGE: 60 * 60 * 1000, // up to one hour old - path(buildId, file) { - // used by static server, given build id return '.cache/clsi/buildId' - if (buildId.match(OutputCacheManager.BUILD_REGEX)) { - return Path.join(OutputCacheManager.CACHE_SUBDIR, buildId, file); - } else { - // for invalid build id, return top level - return file; - } - }, + path(buildId, file) { + // used by static server, given build id return '.cache/clsi/buildId' + if (buildId.match(OutputCacheManager.BUILD_REGEX)) { + return Path.join(OutputCacheManager.CACHE_SUBDIR, buildId, file) + } else { + // for invalid build id, return top level + return file + } + }, - generateBuildId(callback) { - // generate a secure build id from Date.now() and 8 random bytes in hex - if (callback == null) { callback = function(error, buildId) {}; } - return crypto.randomBytes(8, function(err, buf) { - if (err != null) { return callback(err); } - const random = buf.toString('hex'); - const date = Date.now().toString(16); - return callback(err, `${date}-${random}`); - }); - }, + generateBuildId(callback) { + // generate a secure build id from Date.now() and 8 random bytes in hex + if (callback == null) { + callback = function(error, buildId) {} + } + return crypto.randomBytes(8, function(err, buf) { + if (err != null) { + return callback(err) + } + const random = buf.toString('hex') + const date = Date.now().toString(16) + return callback(err, `${date}-${random}`) + }) + }, - saveOutputFiles(outputFiles, compileDir, callback) { - if (callback == null) { callback = function(error) {}; } - return OutputCacheManager.generateBuildId(function(err, buildId) { - if (err != null) { return callback(err); } - return OutputCacheManager.saveOutputFilesInBuildDir(outputFiles, compileDir, buildId, callback); - }); - }, + saveOutputFiles(outputFiles, compileDir, callback) { + if (callback == null) { + callback = function(error) {} + } + return OutputCacheManager.generateBuildId(function(err, buildId) { + if (err != null) { + return callback(err) + } + return OutputCacheManager.saveOutputFilesInBuildDir( + outputFiles, + compileDir, + buildId, + callback + ) + }) + }, - saveOutputFilesInBuildDir(outputFiles, compileDir, buildId, callback) { - // make a compileDir/CACHE_SUBDIR/build_id directory and - // copy all the output files into it - if (callback == null) { callback = function(error) {}; } - const cacheRoot = Path.join(compileDir, OutputCacheManager.CACHE_SUBDIR); - // Put the files into a new cache subdirectory - const cacheDir = Path.join(compileDir, OutputCacheManager.CACHE_SUBDIR, buildId); - // Is it a per-user compile? check if compile directory is PROJECTID-USERID - const perUser = Path.basename(compileDir).match(/^[0-9a-f]{24}-[0-9a-f]{24}$/); + saveOutputFilesInBuildDir(outputFiles, compileDir, buildId, callback) { + // make a compileDir/CACHE_SUBDIR/build_id directory and + // copy all the output files into it + if (callback == null) { + callback = function(error) {} + } + const cacheRoot = Path.join(compileDir, OutputCacheManager.CACHE_SUBDIR) + // Put the files into a new cache subdirectory + const cacheDir = Path.join( + compileDir, + OutputCacheManager.CACHE_SUBDIR, + buildId + ) + // Is it a per-user compile? check if compile directory is PROJECTID-USERID + const perUser = Path.basename(compileDir).match( + /^[0-9a-f]{24}-[0-9a-f]{24}$/ + ) - // Archive logs in background - if ((Settings.clsi != null ? Settings.clsi.archive_logs : undefined) || (Settings.clsi != null ? Settings.clsi.strace : undefined)) { - OutputCacheManager.archiveLogs(outputFiles, compileDir, buildId, function(err) { - if (err != null) { - return logger.warn({err}, "erroring archiving log files"); - } - }); - } + // Archive logs in background + if ( + (Settings.clsi != null ? Settings.clsi.archive_logs : undefined) || + (Settings.clsi != null ? Settings.clsi.strace : undefined) + ) { + OutputCacheManager.archiveLogs(outputFiles, compileDir, buildId, function( + err + ) { + if (err != null) { + return logger.warn({ err }, 'erroring archiving log files') + } + }) + } - // make the new cache directory - return fse.ensureDir(cacheDir, function(err) { - if (err != null) { - logger.error({err, directory: cacheDir}, "error creating cache directory"); - return callback(err, outputFiles); - } else { - // copy all the output files into the new cache directory - const results = []; - return async.mapSeries(outputFiles, function(file, cb) { - // don't send dot files as output, express doesn't serve them - if (OutputCacheManager._fileIsHidden(file.path)) { - logger.debug({compileDir, path: file.path}, "ignoring dotfile in output"); - return cb(); - } - // copy other files into cache directory if valid - const newFile = _.clone(file); - const [src, dst] = Array.from([Path.join(compileDir, file.path), Path.join(cacheDir, file.path)]); - return OutputCacheManager._checkFileIsSafe(src, function(err, isSafe) { - if (err != null) { return cb(err); } - if (!isSafe) { - return cb(); - } - return OutputCacheManager._checkIfShouldCopy(src, function(err, shouldCopy) { - if (err != null) { return cb(err); } - if (!shouldCopy) { - return cb(); - } - return OutputCacheManager._copyFile(src, dst, function(err) { - if (err != null) { return cb(err); } - newFile.build = buildId; // attach a build id if we cached the file - results.push(newFile); - return cb(); - }); - }); - }); - } - , function(err) { - if (err != null) { - // pass back the original files if we encountered *any* error - callback(err, outputFiles); - // clean up the directory we just created - return fse.remove(cacheDir, function(err) { - if (err != null) { - return logger.error({err, dir: cacheDir}, "error removing cache dir after failure"); - } - }); - } else { - // pass back the list of new files in the cache - callback(err, results); - // let file expiry run in the background, expire all previous files if per-user - return OutputCacheManager.expireOutputFiles(cacheRoot, {keep: buildId, limit: perUser ? 1 : null}); - } - }); - } - }); - }, + // make the new cache directory + return fse.ensureDir(cacheDir, function(err) { + if (err != null) { + logger.error( + { err, directory: cacheDir }, + 'error creating cache directory' + ) + return callback(err, outputFiles) + } else { + // copy all the output files into the new cache directory + const results = [] + return async.mapSeries( + outputFiles, + function(file, cb) { + // don't send dot files as output, express doesn't serve them + if (OutputCacheManager._fileIsHidden(file.path)) { + logger.debug( + { compileDir, path: file.path }, + 'ignoring dotfile in output' + ) + return cb() + } + // copy other files into cache directory if valid + const newFile = _.clone(file) + const [src, dst] = Array.from([ + Path.join(compileDir, file.path), + Path.join(cacheDir, file.path) + ]) + return OutputCacheManager._checkFileIsSafe(src, function( + err, + isSafe + ) { + if (err != null) { + return cb(err) + } + if (!isSafe) { + return cb() + } + return OutputCacheManager._checkIfShouldCopy(src, function( + err, + shouldCopy + ) { + if (err != null) { + return cb(err) + } + if (!shouldCopy) { + return cb() + } + return OutputCacheManager._copyFile(src, dst, function(err) { + if (err != null) { + return cb(err) + } + newFile.build = buildId // attach a build id if we cached the file + results.push(newFile) + return cb() + }) + }) + }) + }, + function(err) { + if (err != null) { + // pass back the original files if we encountered *any* error + callback(err, outputFiles) + // clean up the directory we just created + return fse.remove(cacheDir, function(err) { + if (err != null) { + return logger.error( + { err, dir: cacheDir }, + 'error removing cache dir after failure' + ) + } + }) + } else { + // pass back the list of new files in the cache + callback(err, results) + // let file expiry run in the background, expire all previous files if per-user + return OutputCacheManager.expireOutputFiles(cacheRoot, { + keep: buildId, + limit: perUser ? 1 : null + }) + } + } + ) + } + }) + }, - archiveLogs(outputFiles, compileDir, buildId, callback) { - if (callback == null) { callback = function(error) {}; } - const archiveDir = Path.join(compileDir, OutputCacheManager.ARCHIVE_SUBDIR, buildId); - logger.log({dir: archiveDir}, "archiving log files for project"); - return fse.ensureDir(archiveDir, function(err) { - if (err != null) { return callback(err); } - return async.mapSeries(outputFiles, function(file, cb) { - const [src, dst] = Array.from([Path.join(compileDir, file.path), Path.join(archiveDir, file.path)]); - return OutputCacheManager._checkFileIsSafe(src, function(err, isSafe) { - if (err != null) { return cb(err); } - if (!isSafe) { return cb(); } - return OutputCacheManager._checkIfShouldArchive(src, function(err, shouldArchive) { - if (err != null) { return cb(err); } - if (!shouldArchive) { return cb(); } - return OutputCacheManager._copyFile(src, dst, cb); - }); - }); - } - , callback); - }); - }, + archiveLogs(outputFiles, compileDir, buildId, callback) { + if (callback == null) { + callback = function(error) {} + } + const archiveDir = Path.join( + compileDir, + OutputCacheManager.ARCHIVE_SUBDIR, + buildId + ) + logger.log({ dir: archiveDir }, 'archiving log files for project') + return fse.ensureDir(archiveDir, function(err) { + if (err != null) { + return callback(err) + } + return async.mapSeries( + outputFiles, + function(file, cb) { + const [src, dst] = Array.from([ + Path.join(compileDir, file.path), + Path.join(archiveDir, file.path) + ]) + return OutputCacheManager._checkFileIsSafe(src, function( + err, + isSafe + ) { + if (err != null) { + return cb(err) + } + if (!isSafe) { + return cb() + } + return OutputCacheManager._checkIfShouldArchive(src, function( + err, + shouldArchive + ) { + if (err != null) { + return cb(err) + } + if (!shouldArchive) { + return cb() + } + return OutputCacheManager._copyFile(src, dst, cb) + }) + }) + }, + callback + ) + }) + }, - expireOutputFiles(cacheRoot, options, callback) { - // look in compileDir for build dirs and delete if > N or age of mod time > T - if (callback == null) { callback = function(error) {}; } - return fs.readdir(cacheRoot, function(err, results) { - if (err != null) { - if (err.code === 'ENOENT') { return callback(null); } // cache directory is empty - logger.error({err, project_id: cacheRoot}, "error clearing cache"); - return callback(err); - } + expireOutputFiles(cacheRoot, options, callback) { + // look in compileDir for build dirs and delete if > N or age of mod time > T + if (callback == null) { + callback = function(error) {} + } + return fs.readdir(cacheRoot, function(err, results) { + if (err != null) { + if (err.code === 'ENOENT') { + return callback(null) + } // cache directory is empty + logger.error({ err, project_id: cacheRoot }, 'error clearing cache') + return callback(err) + } - const dirs = results.sort().reverse(); - const currentTime = Date.now(); + const dirs = results.sort().reverse() + const currentTime = Date.now() - const isExpired = function(dir, index) { - if ((options != null ? options.keep : undefined) === dir) { return false; } - // remove any directories over the requested (non-null) limit - if (((options != null ? options.limit : undefined) != null) && (index > options.limit)) { return true; } - // remove any directories over the hard limit - if (index > OutputCacheManager.CACHE_LIMIT) { return true; } - // we can get the build time from the first part of the directory name DDDD-RRRR - // DDDD is date and RRRR is random bytes - const dirTime = parseInt(__guard__(dir.split('-'), x => x[0]), 16); - const age = currentTime - dirTime; - return age > OutputCacheManager.CACHE_AGE; - }; + const isExpired = function(dir, index) { + if ((options != null ? options.keep : undefined) === dir) { + return false + } + // remove any directories over the requested (non-null) limit + if ( + (options != null ? options.limit : undefined) != null && + index > options.limit + ) { + return true + } + // remove any directories over the hard limit + if (index > OutputCacheManager.CACHE_LIMIT) { + return true + } + // we can get the build time from the first part of the directory name DDDD-RRRR + // DDDD is date and RRRR is random bytes + const dirTime = parseInt( + __guard__(dir.split('-'), x => x[0]), + 16 + ) + const age = currentTime - dirTime + return age > OutputCacheManager.CACHE_AGE + } - const toRemove = _.filter(dirs, isExpired); + const toRemove = _.filter(dirs, isExpired) - const removeDir = (dir, cb) => - fse.remove(Path.join(cacheRoot, dir), function(err, result) { - logger.log({cache: cacheRoot, dir}, "removed expired cache dir"); - if (err != null) { - logger.error({err, dir}, "cache remove error"); - } - return cb(err, result); - }) - ; + const removeDir = (dir, cb) => + fse.remove(Path.join(cacheRoot, dir), function(err, result) { + logger.log({ cache: cacheRoot, dir }, 'removed expired cache dir') + if (err != null) { + logger.error({ err, dir }, 'cache remove error') + } + return cb(err, result) + }) + return async.eachSeries( + toRemove, + (dir, cb) => removeDir(dir, cb), + callback + ) + }) + }, - return async.eachSeries(toRemove, (dir, cb) => removeDir(dir, cb) - , callback); - }); - }, + _fileIsHidden(path) { + return (path != null ? path.match(/^\.|\/\./) : undefined) != null + }, - _fileIsHidden(path) { - return ((path != null ? path.match(/^\.|\/\./) : undefined) != null); - }, + _checkFileIsSafe(src, callback) { + // check if we have a valid file to copy into the cache + if (callback == null) { + callback = function(error, isSafe) {} + } + return fs.stat(src, function(err, stats) { + if ((err != null ? err.code : undefined) === 'ENOENT') { + logger.warn( + { err, file: src }, + 'file has disappeared before copying to build cache' + ) + return callback(err, false) + } else if (err != null) { + // some other problem reading the file + logger.error({ err, file: src }, 'stat error for file in cache') + return callback(err, false) + } else if (!stats.isFile()) { + // other filetype - reject it + logger.warn( + { src, stat: stats }, + 'nonfile output - refusing to copy to cache' + ) + return callback(null, false) + } else { + // it's a plain file, ok to copy + return callback(null, true) + } + }) + }, - _checkFileIsSafe(src, callback) { - // check if we have a valid file to copy into the cache - if (callback == null) { callback = function(error, isSafe) {}; } - return fs.stat(src, function(err, stats) { - if ((err != null ? err.code : undefined) === 'ENOENT') { - logger.warn({err, file: src}, "file has disappeared before copying to build cache"); - return callback(err, false); - } else if (err != null) { - // some other problem reading the file - logger.error({err, file: src}, "stat error for file in cache"); - return callback(err, false); - } else if (!stats.isFile()) { - // other filetype - reject it - logger.warn({src, stat: stats}, "nonfile output - refusing to copy to cache"); - return callback(null, false); - } else { - // it's a plain file, ok to copy - return callback(null, true); - } - }); - }, + _copyFile(src, dst, callback) { + // copy output file into the cache + return fse.copy(src, dst, function(err) { + if ((err != null ? err.code : undefined) === 'ENOENT') { + logger.warn( + { err, file: src }, + 'file has disappeared when copying to build cache' + ) + return callback(err, false) + } else if (err != null) { + logger.error({ err, src, dst }, 'copy error for file in cache') + return callback(err) + } else { + if ( + Settings.clsi != null ? Settings.clsi.optimiseInDocker : undefined + ) { + // don't run any optimisations on the pdf when they are done + // in the docker container + return callback() + } else { + // call the optimiser for the file too + return OutputFileOptimiser.optimiseFile(src, dst, callback) + } + } + }) + }, - _copyFile(src, dst, callback) { - // copy output file into the cache - return fse.copy(src, dst, function(err) { - if ((err != null ? err.code : undefined) === 'ENOENT') { - logger.warn({err, file: src}, "file has disappeared when copying to build cache"); - return callback(err, false); - } else if (err != null) { - logger.error({err, src, dst}, "copy error for file in cache"); - return callback(err); - } else { - if ((Settings.clsi != null ? Settings.clsi.optimiseInDocker : undefined)) { - // don't run any optimisations on the pdf when they are done - // in the docker container - return callback(); - } else { - // call the optimiser for the file too - return OutputFileOptimiser.optimiseFile(src, dst, callback); - } - } - }); - }, + _checkIfShouldCopy(src, callback) { + if (callback == null) { + callback = function(err, shouldCopy) {} + } + return callback(null, !Path.basename(src).match(/^strace/)) + }, - _checkIfShouldCopy(src, callback) { - if (callback == null) { callback = function(err, shouldCopy) {}; } - return callback(null, !Path.basename(src).match(/^strace/)); - }, - - _checkIfShouldArchive(src, callback) { - let needle; - if (callback == null) { callback = function(err, shouldCopy) {}; } - if (Path.basename(src).match(/^strace/)) { - return callback(null, true); - } - if ((Settings.clsi != null ? Settings.clsi.archive_logs : undefined) && (needle = Path.basename(src), ["output.log", "output.blg"].includes(needle))) { - return callback(null, true); - } - return callback(null, false); - } -}); + _checkIfShouldArchive(src, callback) { + let needle + if (callback == null) { + callback = function(err, shouldCopy) {} + } + if (Path.basename(src).match(/^strace/)) { + return callback(null, true) + } + if ( + (Settings.clsi != null ? Settings.clsi.archive_logs : undefined) && + ((needle = Path.basename(src)), + ['output.log', 'output.blg'].includes(needle)) + ) { + return callback(null, true) + } + return callback(null, false) + } +} function __guard__(value, transform) { - return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; -} \ No newline at end of file + return typeof value !== 'undefined' && value !== null + ? transform(value) + : undefined +} diff --git a/services/clsi/app/js/OutputFileFinder.js b/services/clsi/app/js/OutputFileFinder.js index 21a758747d..50012b51b7 100644 --- a/services/clsi/app/js/OutputFileFinder.js +++ b/services/clsi/app/js/OutputFileFinder.js @@ -14,73 +14,102 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let OutputFileFinder; -const async = require("async"); -const fs = require("fs"); -const Path = require("path"); -const { spawn } = require("child_process"); -const logger = require("logger-sharelatex"); +let OutputFileFinder +const async = require('async') +const fs = require('fs') +const Path = require('path') +const { spawn } = require('child_process') +const logger = require('logger-sharelatex') -module.exports = (OutputFileFinder = { - findOutputFiles(resources, directory, callback) { - if (callback == null) { callback = function(error, outputFiles, allFiles) {}; } - const incomingResources = {}; - for (const resource of Array.from(resources)) { - incomingResources[resource.path] = true; - } - - return OutputFileFinder._getAllFiles(directory, function(error, allFiles) { - if (allFiles == null) { allFiles = []; } - if (error != null) { - logger.err({err:error}, "error finding all output files"); - return callback(error); - } - const outputFiles = []; - for (const file of Array.from(allFiles)) { - if (!incomingResources[file]) { - outputFiles.push({ - path: file, - type: __guard__(file.match(/\.([^\.]+)$/), x => x[1]) - }); - } - } - return callback(null, outputFiles, allFiles); - }); - }, +module.exports = OutputFileFinder = { + findOutputFiles(resources, directory, callback) { + if (callback == null) { + callback = function(error, outputFiles, allFiles) {} + } + const incomingResources = {} + for (const resource of Array.from(resources)) { + incomingResources[resource.path] = true + } - _getAllFiles(directory, _callback) { - if (_callback == null) { _callback = function(error, fileList) {}; } - const callback = function(error, fileList) { - _callback(error, fileList); - return _callback = function() {}; - }; + return OutputFileFinder._getAllFiles(directory, function(error, allFiles) { + if (allFiles == null) { + allFiles = [] + } + if (error != null) { + logger.err({ err: error }, 'error finding all output files') + return callback(error) + } + const outputFiles = [] + for (const file of Array.from(allFiles)) { + if (!incomingResources[file]) { + outputFiles.push({ + path: file, + type: __guard__(file.match(/\.([^\.]+)$/), x => x[1]) + }) + } + } + return callback(null, outputFiles, allFiles) + }) + }, - // don't include clsi-specific files/directories in the output list - const EXCLUDE_DIRS = ["-name", ".cache", "-o", "-name", ".archive","-o", "-name", ".project-*"]; - const args = [directory, "(", ...Array.from(EXCLUDE_DIRS), ")", "-prune", "-o", "-type", "f", "-print"]; - logger.log({args}, "running find command"); + _getAllFiles(directory, _callback) { + if (_callback == null) { + _callback = function(error, fileList) {} + } + const callback = function(error, fileList) { + _callback(error, fileList) + return (_callback = function() {}) + } - const proc = spawn("find", args); - let stdout = ""; - proc.stdout.on("data", chunk => stdout += chunk.toString()); - proc.on("error", callback); - return proc.on("close", function(code) { - if (code !== 0) { - logger.warn({directory, code}, "find returned error, directory likely doesn't exist"); - return callback(null, []); - } - let fileList = stdout.trim().split("\n"); - fileList = fileList.map(function(file) { - // Strip leading directory - let path; - return path = Path.relative(directory, file); - }); - return callback(null, fileList); - }); - } -}); + // don't include clsi-specific files/directories in the output list + const EXCLUDE_DIRS = [ + '-name', + '.cache', + '-o', + '-name', + '.archive', + '-o', + '-name', + '.project-*' + ] + const args = [ + directory, + '(', + ...Array.from(EXCLUDE_DIRS), + ')', + '-prune', + '-o', + '-type', + 'f', + '-print' + ] + logger.log({ args }, 'running find command') + const proc = spawn('find', args) + let stdout = '' + proc.stdout.on('data', chunk => (stdout += chunk.toString())) + proc.on('error', callback) + return proc.on('close', function(code) { + if (code !== 0) { + logger.warn( + { directory, code }, + "find returned error, directory likely doesn't exist" + ) + return callback(null, []) + } + let fileList = stdout.trim().split('\n') + fileList = fileList.map(function(file) { + // Strip leading directory + let path + return (path = Path.relative(directory, file)) + }) + return callback(null, fileList) + }) + } +} function __guard__(value, transform) { - return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; -} \ No newline at end of file + return typeof value !== 'undefined' && value !== null + ? transform(value) + : undefined +} diff --git a/services/clsi/app/js/OutputFileOptimiser.js b/services/clsi/app/js/OutputFileOptimiser.js index 149d38462e..c0b8cc141a 100644 --- a/services/clsi/app/js/OutputFileOptimiser.js +++ b/services/clsi/app/js/OutputFileOptimiser.js @@ -13,74 +13,92 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let OutputFileOptimiser; -const fs = require("fs"); -const Path = require("path"); -const { spawn } = require("child_process"); -const logger = require("logger-sharelatex"); -const Metrics = require("./Metrics"); -const _ = require("underscore"); +let OutputFileOptimiser +const fs = require('fs') +const Path = require('path') +const { spawn } = require('child_process') +const logger = require('logger-sharelatex') +const Metrics = require('./Metrics') +const _ = require('underscore') -module.exports = (OutputFileOptimiser = { +module.exports = OutputFileOptimiser = { + optimiseFile(src, dst, callback) { + // check output file (src) and see if we can optimise it, storing + // the result in the build directory (dst) + if (callback == null) { + callback = function(error) {} + } + if (src.match(/\/output\.pdf$/)) { + return OutputFileOptimiser.checkIfPDFIsOptimised(src, function( + err, + isOptimised + ) { + if (err != null || isOptimised) { + return callback(null) + } + return OutputFileOptimiser.optimisePDF(src, dst, callback) + }) + } else { + return callback(null) + } + }, - optimiseFile(src, dst, callback) { - // check output file (src) and see if we can optimise it, storing - // the result in the build directory (dst) - if (callback == null) { callback = function(error) {}; } - if (src.match(/\/output\.pdf$/)) { - return OutputFileOptimiser.checkIfPDFIsOptimised(src, function(err, isOptimised) { - if ((err != null) || isOptimised) { return callback(null); } - return OutputFileOptimiser.optimisePDF(src, dst, callback); - }); - } else { - return callback((null)); - } - }, + checkIfPDFIsOptimised(file, callback) { + const SIZE = 16 * 1024 // check the header of the pdf + const result = new Buffer(SIZE) + result.fill(0) // prevent leakage of uninitialised buffer + return fs.open(file, 'r', function(err, fd) { + if (err != null) { + return callback(err) + } + return fs.read(fd, result, 0, SIZE, 0, (errRead, bytesRead, buffer) => + fs.close(fd, function(errClose) { + if (errRead != null) { + return callback(errRead) + } + if (typeof errReadClose !== 'undefined' && errReadClose !== null) { + return callback(errClose) + } + const isOptimised = + buffer.toString('ascii').indexOf('/Linearized 1') >= 0 + return callback(null, isOptimised) + }) + ) + }) + }, - checkIfPDFIsOptimised(file, callback) { - const SIZE = 16*1024; // check the header of the pdf - const result = new Buffer(SIZE); - result.fill(0); // prevent leakage of uninitialised buffer - return fs.open(file, "r", function(err, fd) { - if (err != null) { return callback(err); } - return fs.read(fd, result, 0, SIZE, 0, (errRead, bytesRead, buffer) => - fs.close(fd, function(errClose) { - if (errRead != null) { return callback(errRead); } - if (typeof errReadClose !== 'undefined' && errReadClose !== null) { return callback(errClose); } - const isOptimised = buffer.toString('ascii').indexOf("/Linearized 1") >= 0; - return callback(null, isOptimised); - }) - ); - }); - }, + optimisePDF(src, dst, callback) { + if (callback == null) { + callback = function(error) {} + } + const tmpOutput = dst + '.opt' + const args = ['--linearize', src, tmpOutput] + logger.log({ args }, 'running qpdf command') - optimisePDF(src, dst, callback) { - if (callback == null) { callback = function(error) {}; } - const tmpOutput = dst + '.opt'; - const args = ["--linearize", src, tmpOutput]; - logger.log({args}, "running qpdf command"); - - const timer = new Metrics.Timer("qpdf"); - const proc = spawn("qpdf", args); - let stdout = ""; - proc.stdout.on("data", chunk => stdout += chunk.toString()); - callback = _.once(callback); // avoid double call back for error and close event - proc.on("error", function(err) { - logger.warn({err, args}, "qpdf failed"); - return callback(null); - }); // ignore the error - return proc.on("close", function(code) { - timer.done(); - if (code !== 0) { - logger.warn({code, args}, "qpdf returned error"); - return callback(null); // ignore the error - } - return fs.rename(tmpOutput, dst, function(err) { - if (err != null) { - logger.warn({tmpOutput, dst}, "failed to rename output of qpdf command"); - } - return callback(null); - }); - }); - } // ignore the error -}); + const timer = new Metrics.Timer('qpdf') + const proc = spawn('qpdf', args) + let stdout = '' + proc.stdout.on('data', chunk => (stdout += chunk.toString())) + callback = _.once(callback) // avoid double call back for error and close event + proc.on('error', function(err) { + logger.warn({ err, args }, 'qpdf failed') + return callback(null) + }) // ignore the error + return proc.on('close', function(code) { + timer.done() + if (code !== 0) { + logger.warn({ code, args }, 'qpdf returned error') + return callback(null) // ignore the error + } + return fs.rename(tmpOutput, dst, function(err) { + if (err != null) { + logger.warn( + { tmpOutput, dst }, + 'failed to rename output of qpdf command' + ) + } + return callback(null) + }) + }) + } // ignore the error +} diff --git a/services/clsi/app/js/ProjectPersistenceManager.js b/services/clsi/app/js/ProjectPersistenceManager.js index 856c15667b..8015baa907 100644 --- a/services/clsi/app/js/ProjectPersistenceManager.js +++ b/services/clsi/app/js/ProjectPersistenceManager.js @@ -11,113 +11,153 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let ProjectPersistenceManager; -const UrlCache = require("./UrlCache"); -const CompileManager = require("./CompileManager"); -const db = require("./db"); -const dbQueue = require("./DbQueue"); -const async = require("async"); -const logger = require("logger-sharelatex"); -const oneDay = 24 * 60 * 60 * 1000; -const Settings = require("settings-sharelatex"); +let ProjectPersistenceManager +const UrlCache = require('./UrlCache') +const CompileManager = require('./CompileManager') +const db = require('./db') +const dbQueue = require('./DbQueue') +const async = require('async') +const logger = require('logger-sharelatex') +const oneDay = 24 * 60 * 60 * 1000 +const Settings = require('settings-sharelatex') -module.exports = (ProjectPersistenceManager = { +module.exports = ProjectPersistenceManager = { + EXPIRY_TIMEOUT: Settings.project_cache_length_ms || oneDay * 2.5, - EXPIRY_TIMEOUT: Settings.project_cache_length_ms || (oneDay * 2.5), + markProjectAsJustAccessed(project_id, callback) { + if (callback == null) { + callback = function(error) {} + } + const job = cb => + db.Project.findOrCreate({ where: { project_id } }) + .spread((project, created) => + project + .updateAttributes({ lastAccessed: new Date() }) + .then(() => cb()) + .error(cb) + ) + .error(cb) + return dbQueue.queue.push(job, callback) + }, - markProjectAsJustAccessed(project_id, callback) { - if (callback == null) { callback = function(error) {}; } - const job = cb=> - db.Project.findOrCreate({where: {project_id}}) - .spread( - (project, created) => - project.updateAttributes({lastAccessed: new Date()}) - .then(() => cb()) - .error(cb) - ) - .error(cb) - ; - return dbQueue.queue.push(job, callback); - }, + clearExpiredProjects(callback) { + if (callback == null) { + callback = function(error) {} + } + return ProjectPersistenceManager._findExpiredProjectIds(function( + error, + project_ids + ) { + if (error != null) { + return callback(error) + } + logger.log({ project_ids }, 'clearing expired projects') + const jobs = Array.from(project_ids || []).map(project_id => + (project_id => callback => + ProjectPersistenceManager.clearProjectFromCache(project_id, function( + err + ) { + if (err != null) { + logger.error({ err, project_id }, 'error clearing project') + } + return callback() + }))(project_id) + ) + return async.series(jobs, function(error) { + if (error != null) { + return callback(error) + } + return CompileManager.clearExpiredProjects( + ProjectPersistenceManager.EXPIRY_TIMEOUT, + error => callback() + ) + }) + }) + }, // ignore any errors from deleting directories + clearProject(project_id, user_id, callback) { + if (callback == null) { + callback = function(error) {} + } + logger.log({ project_id, user_id }, 'clearing project for user') + return CompileManager.clearProject(project_id, user_id, function(error) { + if (error != null) { + return callback(error) + } + return ProjectPersistenceManager.clearProjectFromCache( + project_id, + function(error) { + if (error != null) { + return callback(error) + } + return callback() + } + ) + }) + }, - clearExpiredProjects(callback) { - if (callback == null) { callback = function(error) {}; } - return ProjectPersistenceManager._findExpiredProjectIds(function(error, project_ids) { - if (error != null) { return callback(error); } - logger.log({project_ids}, "clearing expired projects"); - const jobs = (Array.from(project_ids || [])).map((project_id) => - (project_id => - callback => - ProjectPersistenceManager.clearProjectFromCache(project_id, function(err) { - if (err != null) { - logger.error({err, project_id}, "error clearing project"); - } - return callback(); - }) - - )(project_id)); - return async.series(jobs, function(error) { - if (error != null) { return callback(error); } - return CompileManager.clearExpiredProjects(ProjectPersistenceManager.EXPIRY_TIMEOUT, error => callback()); - }); - }); - }, // ignore any errors from deleting directories + clearProjectFromCache(project_id, callback) { + if (callback == null) { + callback = function(error) {} + } + logger.log({ project_id }, 'clearing project from cache') + return UrlCache.clearProject(project_id, function(error) { + if (error != null) { + logger.err({ error, project_id }, 'error clearing project from cache') + return callback(error) + } + return ProjectPersistenceManager._clearProjectFromDatabase( + project_id, + function(error) { + if (error != null) { + logger.err( + { error, project_id }, + 'error clearing project from database' + ) + } + return callback(error) + } + ) + }) + }, - clearProject(project_id, user_id, callback) { - if (callback == null) { callback = function(error) {}; } - logger.log({project_id, user_id}, "clearing project for user"); - return CompileManager.clearProject(project_id, user_id, function(error) { - if (error != null) { return callback(error); } - return ProjectPersistenceManager.clearProjectFromCache(project_id, function(error) { - if (error != null) { return callback(error); } - return callback(); - }); - }); - }, + _clearProjectFromDatabase(project_id, callback) { + if (callback == null) { + callback = function(error) {} + } + logger.log({ project_id }, 'clearing project from database') + const job = cb => + db.Project.destroy({ where: { project_id } }) + .then(() => cb()) + .error(cb) + return dbQueue.queue.push(job, callback) + }, - clearProjectFromCache(project_id, callback) { - if (callback == null) { callback = function(error) {}; } - logger.log({project_id}, "clearing project from cache"); - return UrlCache.clearProject(project_id, function(error) { - if (error != null) { - logger.err({error, project_id}, "error clearing project from cache"); - return callback(error); - } - return ProjectPersistenceManager._clearProjectFromDatabase(project_id, function(error) { - if (error != null) { - logger.err({error, project_id}, "error clearing project from database"); - } - return callback(error); - }); - }); - }, + _findExpiredProjectIds(callback) { + if (callback == null) { + callback = function(error, project_ids) {} + } + const job = function(cb) { + const keepProjectsFrom = new Date( + Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT + ) + const q = {} + q[db.op.lt] = keepProjectsFrom + return db.Project.findAll({ where: { lastAccessed: q } }) + .then(projects => + cb( + null, + projects.map(project => project.project_id) + ) + ) + .error(cb) + } - _clearProjectFromDatabase(project_id, callback) { - if (callback == null) { callback = function(error) {}; } - logger.log({project_id}, "clearing project from database"); - const job = cb=> - db.Project.destroy({where: {project_id}}) - .then(() => cb()) - .error(cb) - ; - return dbQueue.queue.push(job, callback); - }, + return dbQueue.queue.push(job, callback) + } +} - - _findExpiredProjectIds(callback) { - if (callback == null) { callback = function(error, project_ids) {}; } - const job = function(cb){ - const keepProjectsFrom = new Date(Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT); - const q = {}; - q[db.op.lt] = keepProjectsFrom; - return db.Project.findAll({where:{lastAccessed:q}}) - .then(projects => cb(null, projects.map(project => project.project_id))).error(cb); - }; - - return dbQueue.queue.push(job, callback); - } -}); - - -logger.log({EXPIRY_TIMEOUT: ProjectPersistenceManager.EXPIRY_TIMEOUT}, "project assets kept timeout"); +logger.log( + { EXPIRY_TIMEOUT: ProjectPersistenceManager.EXPIRY_TIMEOUT }, + 'project assets kept timeout' +) diff --git a/services/clsi/app/js/RequestParser.js b/services/clsi/app/js/RequestParser.js index 6641086a54..acfdc6689d 100644 --- a/services/clsi/app/js/RequestParser.js +++ b/services/clsi/app/js/RequestParser.js @@ -17,177 +17,201 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let RequestParser; -const settings = require("settings-sharelatex"); +let RequestParser +const settings = require('settings-sharelatex') -module.exports = (RequestParser = { - VALID_COMPILERS: ["pdflatex", "latex", "xelatex", "lualatex"], - MAX_TIMEOUT: 600, +module.exports = RequestParser = { + VALID_COMPILERS: ['pdflatex', 'latex', 'xelatex', 'lualatex'], + MAX_TIMEOUT: 600, - parse(body, callback) { - let resource; - if (callback == null) { callback = function(error, data) {}; } - const response = {}; + parse(body, callback) { + let resource + if (callback == null) { + callback = function(error, data) {} + } + const response = {} - if ((body.compile == null)) { - return callback("top level object should have a compile attribute"); - } + if (body.compile == null) { + return callback('top level object should have a compile attribute') + } - const { compile } = body; - if (!compile.options) { compile.options = {}; } + const { compile } = body + if (!compile.options) { + compile.options = {} + } - try { - response.compiler = this._parseAttribute("compiler", - compile.options.compiler, { - validValues: this.VALID_COMPILERS, - default: "pdflatex", - type: "string" - } - ); - response.timeout = this._parseAttribute("timeout", - compile.options.timeout, { - default: RequestParser.MAX_TIMEOUT, - type: "number" - } - ); - response.imageName = this._parseAttribute("imageName", - compile.options.imageName, - {type: "string"}); - response.draft = this._parseAttribute("draft", - compile.options.draft, { - default: false, - type: "boolean" - } - ); - response.check = this._parseAttribute("check", - compile.options.check, - {type: "string"}); - response.flags = this._parseAttribute("flags", - compile.options.flags, { - default: [], - type: "object" - } - ); + try { + response.compiler = this._parseAttribute( + 'compiler', + compile.options.compiler, + { + validValues: this.VALID_COMPILERS, + default: 'pdflatex', + type: 'string' + } + ) + response.timeout = this._parseAttribute( + 'timeout', + compile.options.timeout, + { + default: RequestParser.MAX_TIMEOUT, + type: 'number' + } + ) + response.imageName = this._parseAttribute( + 'imageName', + compile.options.imageName, + { type: 'string' } + ) + response.draft = this._parseAttribute('draft', compile.options.draft, { + default: false, + type: 'boolean' + }) + response.check = this._parseAttribute('check', compile.options.check, { + type: 'string' + }) + response.flags = this._parseAttribute('flags', compile.options.flags, { + default: [], + type: 'object' + }) - // The syncType specifies whether the request contains all - // resources (full) or only those resources to be updated - // in-place (incremental). - response.syncType = this._parseAttribute("syncType", - compile.options.syncType, { - validValues: ["full", "incremental"], - type: "string" - } - ); + // The syncType specifies whether the request contains all + // resources (full) or only those resources to be updated + // in-place (incremental). + response.syncType = this._parseAttribute( + 'syncType', + compile.options.syncType, + { + validValues: ['full', 'incremental'], + type: 'string' + } + ) - // The syncState is an identifier passed in with the request - // which has the property that it changes when any resource is - // added, deleted, moved or renamed. - // - // on syncType full the syncState identifier is passed in and - // stored - // - // on syncType incremental the syncState identifier must match - // the stored value - response.syncState = this._parseAttribute("syncState", - compile.options.syncState, - {type: "string"}); + // The syncState is an identifier passed in with the request + // which has the property that it changes when any resource is + // added, deleted, moved or renamed. + // + // on syncType full the syncState identifier is passed in and + // stored + // + // on syncType incremental the syncState identifier must match + // the stored value + response.syncState = this._parseAttribute( + 'syncState', + compile.options.syncState, + { type: 'string' } + ) - if (response.timeout > RequestParser.MAX_TIMEOUT) { - response.timeout = RequestParser.MAX_TIMEOUT; - } - response.timeout = response.timeout * 1000; // milliseconds + if (response.timeout > RequestParser.MAX_TIMEOUT) { + response.timeout = RequestParser.MAX_TIMEOUT + } + response.timeout = response.timeout * 1000 // milliseconds - response.resources = ((() => { - const result = []; - for (resource of Array.from((compile.resources || []))) { result.push(this._parseResource(resource)); - } - return result; - })()); + response.resources = (() => { + const result = [] + for (resource of Array.from(compile.resources || [])) { + result.push(this._parseResource(resource)) + } + return result + })() - const rootResourcePath = this._parseAttribute("rootResourcePath", - compile.rootResourcePath, { - default: "main.tex", - type: "string" - } - ); - const originalRootResourcePath = rootResourcePath; - const sanitizedRootResourcePath = RequestParser._sanitizePath(rootResourcePath); - response.rootResourcePath = RequestParser._checkPath(sanitizedRootResourcePath); + const rootResourcePath = this._parseAttribute( + 'rootResourcePath', + compile.rootResourcePath, + { + default: 'main.tex', + type: 'string' + } + ) + const originalRootResourcePath = rootResourcePath + const sanitizedRootResourcePath = RequestParser._sanitizePath( + rootResourcePath + ) + response.rootResourcePath = RequestParser._checkPath( + sanitizedRootResourcePath + ) - for (resource of Array.from(response.resources)) { - if (resource.path === originalRootResourcePath) { - resource.path = sanitizedRootResourcePath; - } - } - } catch (error1) { - const error = error1; - return callback(error); - } + for (resource of Array.from(response.resources)) { + if (resource.path === originalRootResourcePath) { + resource.path = sanitizedRootResourcePath + } + } + } catch (error1) { + const error = error1 + return callback(error) + } - return callback(null, response); - }, + return callback(null, response) + }, - _parseResource(resource) { - let modified; - if ((resource.path == null) || (typeof resource.path !== "string")) { - throw "all resources should have a path attribute"; - } + _parseResource(resource) { + let modified + if (resource.path == null || typeof resource.path !== 'string') { + throw 'all resources should have a path attribute' + } - if (resource.modified != null) { - modified = new Date(resource.modified); - if (isNaN(modified.getTime())) { - throw `resource modified date could not be understood: ${resource.modified}`; - } - } + if (resource.modified != null) { + modified = new Date(resource.modified) + if (isNaN(modified.getTime())) { + throw `resource modified date could not be understood: ${resource.modified}` + } + } - if ((resource.url == null) && (resource.content == null)) { - throw "all resources should have either a url or content attribute"; - } - if ((resource.content != null) && (typeof resource.content !== "string")) { - throw "content attribute should be a string"; - } - if ((resource.url != null) && (typeof resource.url !== "string")) { - throw "url attribute should be a string"; - } + if (resource.url == null && resource.content == null) { + throw 'all resources should have either a url or content attribute' + } + if (resource.content != null && typeof resource.content !== 'string') { + throw 'content attribute should be a string' + } + if (resource.url != null && typeof resource.url !== 'string') { + throw 'url attribute should be a string' + } - return { - path: resource.path, - modified, - url: resource.url, - content: resource.content - }; - }, + return { + path: resource.path, + modified, + url: resource.url, + content: resource.content + } + }, - _parseAttribute(name, attribute, options) { - if (attribute != null) { - if (options.validValues != null) { - if (options.validValues.indexOf(attribute) === -1) { - throw `${name} attribute should be one of: ${options.validValues.join(", ")}`; - } - } - if (options.type != null) { - if (typeof attribute !== options.type) { - throw `${name} attribute should be a ${options.type}`; - } - } - } else { - if (options.default != null) { return options.default; } - } - return attribute; - }, + _parseAttribute(name, attribute, options) { + if (attribute != null) { + if (options.validValues != null) { + if (options.validValues.indexOf(attribute) === -1) { + throw `${name} attribute should be one of: ${options.validValues.join( + ', ' + )}` + } + } + if (options.type != null) { + if (typeof attribute !== options.type) { + throw `${name} attribute should be a ${options.type}` + } + } + } else { + if (options.default != null) { + return options.default + } + } + return attribute + }, - _sanitizePath(path) { - // See http://php.net/manual/en/function.escapeshellcmd.php - return path.replace(/[\#\&\;\`\|\*\?\~\<\>\^\(\)\[\]\{\}\$\\\x0A\xFF\x00]/g, ""); - }, + _sanitizePath(path) { + // See http://php.net/manual/en/function.escapeshellcmd.php + return path.replace( + /[\#\&\;\`\|\*\?\~\<\>\^\(\)\[\]\{\}\$\\\x0A\xFF\x00]/g, + '' + ) + }, - _checkPath(path) { - // check that the request does not use a relative path - for (const dir of Array.from(path.split('/'))) { - if (dir === '..') { - throw "relative path in root resource"; - } - } - return path; - } -}); + _checkPath(path) { + // check that the request does not use a relative path + for (const dir of Array.from(path.split('/'))) { + if (dir === '..') { + throw 'relative path in root resource' + } + } + return path + } +} diff --git a/services/clsi/app/js/ResourceStateManager.js b/services/clsi/app/js/ResourceStateManager.js index 45cfdc6118..5a5d811c18 100644 --- a/services/clsi/app/js/ResourceStateManager.js +++ b/services/clsi/app/js/ResourceStateManager.js @@ -13,102 +13,142 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let ResourceStateManager; -const Path = require("path"); -const fs = require("fs"); -const logger = require("logger-sharelatex"); -const settings = require("settings-sharelatex"); -const Errors = require("./Errors"); -const SafeReader = require("./SafeReader"); +let ResourceStateManager +const Path = require('path') +const fs = require('fs') +const logger = require('logger-sharelatex') +const settings = require('settings-sharelatex') +const Errors = require('./Errors') +const SafeReader = require('./SafeReader') -module.exports = (ResourceStateManager = { +module.exports = ResourceStateManager = { + // The sync state is an identifier which must match for an + // incremental update to be allowed. + // + // The initial value is passed in and stored on a full + // compile, along with the list of resources.. + // + // Subsequent incremental compiles must come with the same value - if + // not they will be rejected with a 409 Conflict response. The + // previous list of resources is returned. + // + // An incremental compile can only update existing files with new + // content. The sync state identifier must change if any docs or + // files are moved, added, deleted or renamed. - // The sync state is an identifier which must match for an - // incremental update to be allowed. - // - // The initial value is passed in and stored on a full - // compile, along with the list of resources.. - // - // Subsequent incremental compiles must come with the same value - if - // not they will be rejected with a 409 Conflict response. The - // previous list of resources is returned. - // - // An incremental compile can only update existing files with new - // content. The sync state identifier must change if any docs or - // files are moved, added, deleted or renamed. + SYNC_STATE_FILE: '.project-sync-state', + SYNC_STATE_MAX_SIZE: 128 * 1024, - SYNC_STATE_FILE: ".project-sync-state", - SYNC_STATE_MAX_SIZE: 128*1024, + saveProjectState(state, resources, basePath, callback) { + if (callback == null) { + callback = function(error) {} + } + const stateFile = Path.join(basePath, this.SYNC_STATE_FILE) + if (state == null) { + // remove the file if no state passed in + logger.log({ state, basePath }, 'clearing sync state') + return fs.unlink(stateFile, function(err) { + if (err != null && err.code !== 'ENOENT') { + return callback(err) + } else { + return callback() + } + }) + } else { + logger.log({ state, basePath }, 'writing sync state') + const resourceList = Array.from(resources).map(resource => resource.path) + return fs.writeFile( + stateFile, + [...Array.from(resourceList), `stateHash:${state}`].join('\n'), + callback + ) + } + }, - saveProjectState(state, resources, basePath, callback) { - if (callback == null) { callback = function(error) {}; } - const stateFile = Path.join(basePath, this.SYNC_STATE_FILE); - if ((state == null)) { // remove the file if no state passed in - logger.log({state, basePath}, "clearing sync state"); - return fs.unlink(stateFile, function(err) { - if ((err != null) && (err.code !== 'ENOENT')) { - return callback(err); - } else { - return callback(); - } - }); - } else { - logger.log({state, basePath}, "writing sync state"); - const resourceList = (Array.from(resources).map((resource) => resource.path)); - return fs.writeFile(stateFile, [...Array.from(resourceList), `stateHash:${state}`].join("\n"), callback); - } - }, + checkProjectStateMatches(state, basePath, callback) { + if (callback == null) { + callback = function(error, resources) {} + } + const stateFile = Path.join(basePath, this.SYNC_STATE_FILE) + const size = this.SYNC_STATE_MAX_SIZE + return SafeReader.readFile(stateFile, size, 'utf8', function( + err, + result, + bytesRead + ) { + if (err != null) { + return callback(err) + } + if (bytesRead === size) { + logger.error( + { file: stateFile, size, bytesRead }, + 'project state file truncated' + ) + } + const array = + __guard__(result != null ? result.toString() : undefined, x => + x.split('\n') + ) || [] + const adjustedLength = Math.max(array.length, 1) + const resourceList = array.slice(0, adjustedLength - 1) + const oldState = array[adjustedLength - 1] + const newState = `stateHash:${state}` + logger.log( + { state, oldState, basePath, stateMatches: newState === oldState }, + 'checking sync state' + ) + if (newState !== oldState) { + return callback( + new Errors.FilesOutOfSyncError('invalid state for incremental update') + ) + } else { + const resources = Array.from(resourceList).map(path => ({ path })) + return callback(null, resources) + } + }) + }, - checkProjectStateMatches(state, basePath, callback) { - if (callback == null) { callback = function(error, resources) {}; } - const stateFile = Path.join(basePath, this.SYNC_STATE_FILE); - const size = this.SYNC_STATE_MAX_SIZE; - return SafeReader.readFile(stateFile, size, 'utf8', function(err, result, bytesRead) { - if (err != null) { return callback(err); } - if (bytesRead === size) { - logger.error({file:stateFile, size, bytesRead}, "project state file truncated"); - } - const array = __guard__(result != null ? result.toString() : undefined, x => x.split("\n")) || []; - const adjustedLength = Math.max(array.length, 1); - const resourceList = array.slice(0, adjustedLength - 1); - const oldState = array[adjustedLength - 1]; - const newState = `stateHash:${state}`; - logger.log({state, oldState, basePath, stateMatches: (newState === oldState)}, "checking sync state"); - if (newState !== oldState) { - return callback(new Errors.FilesOutOfSyncError("invalid state for incremental update")); - } else { - const resources = (Array.from(resourceList).map((path) => ({path}))); - return callback(null, resources); - } - }); - }, - - checkResourceFiles(resources, allFiles, basePath, callback) { - // check the paths are all relative to current directory - let file; - if (callback == null) { callback = function(error) {}; } - for (file of Array.from(resources || [])) { - for (const dir of Array.from(__guard__(file != null ? file.path : undefined, x => x.split('/')))) { - if (dir === '..') { - return callback(new Error("relative path in resource file list")); - } - } - } - // check if any of the input files are not present in list of files - const seenFile = {}; - for (file of Array.from(allFiles)) { - seenFile[file] = true; - } - const missingFiles = (Array.from(resources).filter((resource) => !seenFile[resource.path]).map((resource) => resource.path)); - if ((missingFiles != null ? missingFiles.length : undefined) > 0) { - logger.err({missingFiles, basePath, allFiles, resources}, "missing input files for project"); - return callback(new Errors.FilesOutOfSyncError("resource files missing in incremental update")); - } else { - return callback(); - } - } -}); + checkResourceFiles(resources, allFiles, basePath, callback) { + // check the paths are all relative to current directory + let file + if (callback == null) { + callback = function(error) {} + } + for (file of Array.from(resources || [])) { + for (const dir of Array.from( + __guard__(file != null ? file.path : undefined, x => x.split('/')) + )) { + if (dir === '..') { + return callback(new Error('relative path in resource file list')) + } + } + } + // check if any of the input files are not present in list of files + const seenFile = {} + for (file of Array.from(allFiles)) { + seenFile[file] = true + } + const missingFiles = Array.from(resources) + .filter(resource => !seenFile[resource.path]) + .map(resource => resource.path) + if ((missingFiles != null ? missingFiles.length : undefined) > 0) { + logger.err( + { missingFiles, basePath, allFiles, resources }, + 'missing input files for project' + ) + return callback( + new Errors.FilesOutOfSyncError( + 'resource files missing in incremental update' + ) + ) + } else { + return callback() + } + } +} function __guard__(value, transform) { - return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; -} \ No newline at end of file + return typeof value !== 'undefined' && value !== null + ? transform(value) + : undefined +} diff --git a/services/clsi/app/js/ResourceWriter.js b/services/clsi/app/js/ResourceWriter.js index 028fc533d5..ba9706bce2 100644 --- a/services/clsi/app/js/ResourceWriter.js +++ b/services/clsi/app/js/ResourceWriter.js @@ -14,202 +14,339 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let ResourceWriter; -const UrlCache = require("./UrlCache"); -const Path = require("path"); -const fs = require("fs"); -const async = require("async"); -const mkdirp = require("mkdirp"); -const OutputFileFinder = require("./OutputFileFinder"); -const ResourceStateManager = require("./ResourceStateManager"); -const Metrics = require("./Metrics"); -const logger = require("logger-sharelatex"); -const settings = require("settings-sharelatex"); +let ResourceWriter +const UrlCache = require('./UrlCache') +const Path = require('path') +const fs = require('fs') +const async = require('async') +const mkdirp = require('mkdirp') +const OutputFileFinder = require('./OutputFileFinder') +const ResourceStateManager = require('./ResourceStateManager') +const Metrics = require('./Metrics') +const logger = require('logger-sharelatex') +const settings = require('settings-sharelatex') -const parallelFileDownloads = settings.parallelFileDownloads || 1; +const parallelFileDownloads = settings.parallelFileDownloads || 1 -module.exports = (ResourceWriter = { +module.exports = ResourceWriter = { + syncResourcesToDisk(request, basePath, callback) { + if (callback == null) { + callback = function(error, resourceList) {} + } + if (request.syncType === 'incremental') { + logger.log( + { project_id: request.project_id, user_id: request.user_id }, + 'incremental sync' + ) + return ResourceStateManager.checkProjectStateMatches( + request.syncState, + basePath, + function(error, resourceList) { + if (error != null) { + return callback(error) + } + return ResourceWriter._removeExtraneousFiles( + resourceList, + basePath, + function(error, outputFiles, allFiles) { + if (error != null) { + return callback(error) + } + return ResourceStateManager.checkResourceFiles( + resourceList, + allFiles, + basePath, + function(error) { + if (error != null) { + return callback(error) + } + return ResourceWriter.saveIncrementalResourcesToDisk( + request.project_id, + request.resources, + basePath, + function(error) { + if (error != null) { + return callback(error) + } + return callback(null, resourceList) + } + ) + } + ) + } + ) + } + ) + } else { + logger.log( + { project_id: request.project_id, user_id: request.user_id }, + 'full sync' + ) + return this.saveAllResourcesToDisk( + request.project_id, + request.resources, + basePath, + function(error) { + if (error != null) { + return callback(error) + } + return ResourceStateManager.saveProjectState( + request.syncState, + request.resources, + basePath, + function(error) { + if (error != null) { + return callback(error) + } + return callback(null, request.resources) + } + ) + } + ) + } + }, - syncResourcesToDisk(request, basePath, callback) { - if (callback == null) { callback = function(error, resourceList) {}; } - if (request.syncType === "incremental") { - logger.log({project_id: request.project_id, user_id: request.user_id}, "incremental sync"); - return ResourceStateManager.checkProjectStateMatches(request.syncState, basePath, function(error, resourceList) { - if (error != null) { return callback(error); } - return ResourceWriter._removeExtraneousFiles(resourceList, basePath, function(error, outputFiles, allFiles) { - if (error != null) { return callback(error); } - return ResourceStateManager.checkResourceFiles(resourceList, allFiles, basePath, function(error) { - if (error != null) { return callback(error); } - return ResourceWriter.saveIncrementalResourcesToDisk(request.project_id, request.resources, basePath, function(error) { - if (error != null) { return callback(error); } - return callback(null, resourceList); - }); - }); - }); - }); - } else { - logger.log({project_id: request.project_id, user_id: request.user_id}, "full sync"); - return this.saveAllResourcesToDisk(request.project_id, request.resources, basePath, function(error) { - if (error != null) { return callback(error); } - return ResourceStateManager.saveProjectState(request.syncState, request.resources, basePath, function(error) { - if (error != null) { return callback(error); } - return callback(null, request.resources); - }); - }); - } - }, + saveIncrementalResourcesToDisk(project_id, resources, basePath, callback) { + if (callback == null) { + callback = function(error) {} + } + return this._createDirectory(basePath, error => { + if (error != null) { + return callback(error) + } + const jobs = Array.from(resources).map(resource => + (resource => { + return callback => + this._writeResourceToDisk(project_id, resource, basePath, callback) + })(resource) + ) + return async.parallelLimit(jobs, parallelFileDownloads, callback) + }) + }, - saveIncrementalResourcesToDisk(project_id, resources, basePath, callback) { - if (callback == null) { callback = function(error) {}; } - return this._createDirectory(basePath, error => { - if (error != null) { return callback(error); } - const jobs = Array.from(resources).map((resource) => - (resource => { - return callback => this._writeResourceToDisk(project_id, resource, basePath, callback); - })(resource)); - return async.parallelLimit(jobs, parallelFileDownloads, callback); - }); - }, + saveAllResourcesToDisk(project_id, resources, basePath, callback) { + if (callback == null) { + callback = function(error) {} + } + return this._createDirectory(basePath, error => { + if (error != null) { + return callback(error) + } + return this._removeExtraneousFiles(resources, basePath, error => { + if (error != null) { + return callback(error) + } + const jobs = Array.from(resources).map(resource => + (resource => { + return callback => + this._writeResourceToDisk( + project_id, + resource, + basePath, + callback + ) + })(resource) + ) + return async.parallelLimit(jobs, parallelFileDownloads, callback) + }) + }) + }, - saveAllResourcesToDisk(project_id, resources, basePath, callback) { - if (callback == null) { callback = function(error) {}; } - return this._createDirectory(basePath, error => { - if (error != null) { return callback(error); } - return this._removeExtraneousFiles(resources, basePath, error => { - if (error != null) { return callback(error); } - const jobs = Array.from(resources).map((resource) => - (resource => { - return callback => this._writeResourceToDisk(project_id, resource, basePath, callback); - })(resource)); - return async.parallelLimit(jobs, parallelFileDownloads, callback); - }); - }); - }, + _createDirectory(basePath, callback) { + if (callback == null) { + callback = function(error) {} + } + return fs.mkdir(basePath, function(err) { + if (err != null) { + if (err.code === 'EEXIST') { + return callback() + } else { + logger.log({ err, dir: basePath }, 'error creating directory') + return callback(err) + } + } else { + return callback() + } + }) + }, - _createDirectory(basePath, callback) { - if (callback == null) { callback = function(error) {}; } - return fs.mkdir(basePath, function(err) { - if (err != null) { - if (err.code === 'EEXIST') { - return callback(); - } else { - logger.log({err, dir:basePath}, "error creating directory"); - return callback(err); - } - } else { - return callback(); - } - }); - }, + _removeExtraneousFiles(resources, basePath, _callback) { + if (_callback == null) { + _callback = function(error, outputFiles, allFiles) {} + } + const timer = new Metrics.Timer('unlink-output-files') + const callback = function(error, ...result) { + timer.done() + return _callback(error, ...Array.from(result)) + } - _removeExtraneousFiles(resources, basePath, _callback) { - if (_callback == null) { _callback = function(error, outputFiles, allFiles) {}; } - const timer = new Metrics.Timer("unlink-output-files"); - const callback = function(error, ...result) { - timer.done(); - return _callback(error, ...Array.from(result)); - }; + return OutputFileFinder.findOutputFiles(resources, basePath, function( + error, + outputFiles, + allFiles + ) { + if (error != null) { + return callback(error) + } - return OutputFileFinder.findOutputFiles(resources, basePath, function(error, outputFiles, allFiles) { - if (error != null) { return callback(error); } + const jobs = [] + for (const file of Array.from(outputFiles || [])) { + ;(function(file) { + const { path } = file + let should_delete = true + if ( + path.match(/^output\./) || + path.match(/\.aux$/) || + path.match(/^cache\//) + ) { + // knitr cache + should_delete = false + } + if (path.match(/^output-.*/)) { + // Tikz cached figures (default case) + should_delete = false + } + if (path.match(/\.(pdf|dpth|md5)$/)) { + // Tikz cached figures (by extension) + should_delete = false + } + if ( + path.match(/\.(pygtex|pygstyle)$/) || + path.match(/(^|\/)_minted-[^\/]+\//) + ) { + // minted files/directory + should_delete = false + } + if ( + path.match(/\.md\.tex$/) || + path.match(/(^|\/)_markdown_[^\/]+\//) + ) { + // markdown files/directory + should_delete = false + } + if (path.match(/-eps-converted-to\.pdf$/)) { + // Epstopdf generated files + should_delete = false + } + if ( + path === 'output.pdf' || + path === 'output.dvi' || + path === 'output.log' || + path === 'output.xdv' + ) { + should_delete = true + } + if (path === 'output.tex') { + // created by TikzManager if present in output files + should_delete = true + } + if (should_delete) { + return jobs.push(callback => + ResourceWriter._deleteFileIfNotDirectory( + Path.join(basePath, path), + callback + ) + ) + } + })(file) + } - const jobs = []; - for (const file of Array.from(outputFiles || [])) { - (function(file) { - const { path } = file; - let should_delete = true; - if (path.match(/^output\./) || path.match(/\.aux$/) || path.match(/^cache\//)) { // knitr cache - should_delete = false; - } - if (path.match(/^output-.*/)) { // Tikz cached figures (default case) - should_delete = false; - } - if (path.match(/\.(pdf|dpth|md5)$/)) { // Tikz cached figures (by extension) - should_delete = false; - } - if (path.match(/\.(pygtex|pygstyle)$/) || path.match(/(^|\/)_minted-[^\/]+\//)) { // minted files/directory - should_delete = false; - } - if (path.match(/\.md\.tex$/) || path.match(/(^|\/)_markdown_[^\/]+\//)) { // markdown files/directory - should_delete = false; - } - if (path.match(/-eps-converted-to\.pdf$/)) { // Epstopdf generated files - should_delete = false; - } - if ((path === "output.pdf") || (path === "output.dvi") || (path === "output.log") || (path === "output.xdv")) { - should_delete = true; - } - if (path === "output.tex") { // created by TikzManager if present in output files - should_delete = true; - } - if (should_delete) { - return jobs.push(callback => ResourceWriter._deleteFileIfNotDirectory(Path.join(basePath, path), callback)); - } - })(file); - } + return async.series(jobs, function(error) { + if (error != null) { + return callback(error) + } + return callback(null, outputFiles, allFiles) + }) + }) + }, - return async.series(jobs, function(error) { - if (error != null) { return callback(error); } - return callback(null, outputFiles, allFiles); - }); - }); - }, + _deleteFileIfNotDirectory(path, callback) { + if (callback == null) { + callback = function(error) {} + } + return fs.stat(path, function(error, stat) { + if (error != null && error.code === 'ENOENT') { + return callback() + } else if (error != null) { + logger.err( + { err: error, path }, + 'error stating file in deleteFileIfNotDirectory' + ) + return callback(error) + } else if (stat.isFile()) { + return fs.unlink(path, function(error) { + if (error != null) { + logger.err( + { err: error, path }, + 'error removing file in deleteFileIfNotDirectory' + ) + return callback(error) + } else { + return callback() + } + }) + } else { + return callback() + } + }) + }, - _deleteFileIfNotDirectory(path, callback) { - if (callback == null) { callback = function(error) {}; } - return fs.stat(path, function(error, stat) { - if ((error != null) && (error.code === 'ENOENT')) { - return callback(); - } else if (error != null) { - logger.err({err: error, path}, "error stating file in deleteFileIfNotDirectory"); - return callback(error); - } else if (stat.isFile()) { - return fs.unlink(path, function(error) { - if (error != null) { - logger.err({err: error, path}, "error removing file in deleteFileIfNotDirectory"); - return callback(error); - } else { - return callback(); - } - }); - } else { - return callback(); - } - }); - }, + _writeResourceToDisk(project_id, resource, basePath, callback) { + if (callback == null) { + callback = function(error) {} + } + return ResourceWriter.checkPath(basePath, resource.path, function( + error, + path + ) { + if (error != null) { + return callback(error) + } + return mkdirp(Path.dirname(path), function(error) { + if (error != null) { + return callback(error) + } + // TODO: Don't overwrite file if it hasn't been modified + if (resource.url != null) { + return UrlCache.downloadUrlToFile( + project_id, + resource.url, + path, + resource.modified, + function(err) { + if (err != null) { + logger.err( + { + err, + project_id, + path, + resource_url: resource.url, + modified: resource.modified + }, + 'error downloading file for resources' + ) + } + return callback() + } + ) // try and continue compiling even if http resource can not be downloaded at this time + } else { + const process = require('process') + fs.writeFile(path, resource.content, callback) + try { + let result + return (result = fs.lstatSync(path)) + } catch (e) {} + } + }) + }) + }, - _writeResourceToDisk(project_id, resource, basePath, callback) { - if (callback == null) { callback = function(error) {}; } - return ResourceWriter.checkPath(basePath, resource.path, function(error, path) { - if (error != null) { return callback(error); } - return mkdirp(Path.dirname(path), function(error) { - if (error != null) { return callback(error); } - // TODO: Don't overwrite file if it hasn't been modified - if (resource.url != null) { - return UrlCache.downloadUrlToFile(project_id, resource.url, path, resource.modified, function(err){ - if (err != null) { - logger.err({err, project_id, path, resource_url:resource.url, modified:resource.modified}, "error downloading file for resources"); - } - return callback(); - }); // try and continue compiling even if http resource can not be downloaded at this time - } else { - const process = require("process"); - fs.writeFile(path, resource.content, callback); - try { - let result; - return result = fs.lstatSync(path); - } catch (e) {} - } - }); - }); - }, - - checkPath(basePath, resourcePath, callback) { - const path = Path.normalize(Path.join(basePath, resourcePath)); - if (path.slice(0, basePath.length + 1) !== (basePath + "/")) { - return callback(new Error("resource path is outside root directory")); - } else { - return callback(null, path); - } - } -}); + checkPath(basePath, resourcePath, callback) { + const path = Path.normalize(Path.join(basePath, resourcePath)) + if (path.slice(0, basePath.length + 1) !== basePath + '/') { + return callback(new Error('resource path is outside root directory')) + } else { + return callback(null, path) + } + } +} diff --git a/services/clsi/app/js/SafeReader.js b/services/clsi/app/js/SafeReader.js index 2fd599b301..d909e37e73 100644 --- a/services/clsi/app/js/SafeReader.js +++ b/services/clsi/app/js/SafeReader.js @@ -12,36 +12,49 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let SafeReader; -const fs = require("fs"); -const logger = require("logger-sharelatex"); +let SafeReader +const fs = require('fs') +const logger = require('logger-sharelatex') -module.exports = (SafeReader = { +module.exports = SafeReader = { + // safely read up to size bytes from a file and return result as a + // string - // safely read up to size bytes from a file and return result as a - // string + readFile(file, size, encoding, callback) { + if (callback == null) { + callback = function(error, result) {} + } + return fs.open(file, 'r', function(err, fd) { + if (err != null && err.code === 'ENOENT') { + return callback() + } + if (err != null) { + return callback(err) + } - readFile(file, size, encoding, callback) { - if (callback == null) { callback = function(error, result) {}; } - return fs.open(file, 'r', function(err, fd) { - if ((err != null) && (err.code === 'ENOENT')) { return callback(); } - if (err != null) { return callback(err); } - - // safely return always closing the file - const callbackWithClose = (err, ...result) => - fs.close(fd, function(err1) { - if (err != null) { return callback(err); } - if (err1 != null) { return callback(err1); } - return callback(null, ...Array.from(result)); - }) - ; - - const buff = new Buffer(size, 0); // fill with zeros - return fs.read(fd, buff, 0, buff.length, 0, function(err, bytesRead, buffer) { - if (err != null) { return callbackWithClose(err); } - const result = buffer.toString(encoding, 0, bytesRead); - return callbackWithClose(null, result, bytesRead); - }); - }); - } -}); + // safely return always closing the file + const callbackWithClose = (err, ...result) => + fs.close(fd, function(err1) { + if (err != null) { + return callback(err) + } + if (err1 != null) { + return callback(err1) + } + return callback(null, ...Array.from(result)) + }) + const buff = new Buffer(size, 0) // fill with zeros + return fs.read(fd, buff, 0, buff.length, 0, function( + err, + bytesRead, + buffer + ) { + if (err != null) { + return callbackWithClose(err) + } + const result = buffer.toString(encoding, 0, bytesRead) + return callbackWithClose(null, result, bytesRead) + }) + }) + } +} diff --git a/services/clsi/app/js/StaticServerForbidSymlinks.js b/services/clsi/app/js/StaticServerForbidSymlinks.js index 8ac3e489be..999ae2065f 100644 --- a/services/clsi/app/js/StaticServerForbidSymlinks.js +++ b/services/clsi/app/js/StaticServerForbidSymlinks.js @@ -14,59 +14,81 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let ForbidSymlinks; -const Path = require("path"); -const fs = require("fs"); -const Settings = require("settings-sharelatex"); -const logger = require("logger-sharelatex"); -const url = require("url"); +let ForbidSymlinks +const Path = require('path') +const fs = require('fs') +const Settings = require('settings-sharelatex') +const logger = require('logger-sharelatex') +const url = require('url') -module.exports = (ForbidSymlinks = function(staticFn, root, options) { - const expressStatic = staticFn(root, options); - const basePath = Path.resolve(root); - return function(req, res, next) { - let file, project_id, result; - const path = __guard__(url.parse(req.url), x => x.pathname); - // check that the path is of the form /project_id_or_name/path/to/file.log - if (result = path.match(/^\/?([a-zA-Z0-9_-]+)\/(.*)/)) { - project_id = result[1]; - file = result[2]; - } else { - logger.warn({path}, "unrecognized file request"); - return res.sendStatus(404); - } - // check that the file does not use a relative path - for (const dir of Array.from(file.split('/'))) { - if (dir === '..') { - logger.warn({path}, "attempt to use a relative path"); - return res.sendStatus(404); - } - } - // check that the requested path is normalized - const requestedFsPath = `${basePath}/${project_id}/${file}`; - if (requestedFsPath !== Path.normalize(requestedFsPath)) { - logger.error({path: requestedFsPath}, "requestedFsPath is not normalized"); - return res.sendStatus(404); - } - // check that the requested path is not a symlink - return fs.realpath(requestedFsPath, function(err, realFsPath){ - if (err != null) { - if (err.code === 'ENOENT') { - return res.sendStatus(404); - } else { - logger.error({err, requestedFsPath, realFsPath, path: req.params[0], project_id: req.params.project_id}, "error checking file access"); - return res.sendStatus(500); - } - } else if (requestedFsPath !== realFsPath) { - logger.warn({requestedFsPath, realFsPath, path: req.params[0], project_id: req.params.project_id}, "trying to access a different file (symlink), aborting"); - return res.sendStatus(404); - } else { - return expressStatic(req, res, next); - } - }); - }; -}); +module.exports = ForbidSymlinks = function(staticFn, root, options) { + const expressStatic = staticFn(root, options) + const basePath = Path.resolve(root) + return function(req, res, next) { + let file, project_id, result + const path = __guard__(url.parse(req.url), x => x.pathname) + // check that the path is of the form /project_id_or_name/path/to/file.log + if ((result = path.match(/^\/?([a-zA-Z0-9_-]+)\/(.*)/))) { + project_id = result[1] + file = result[2] + } else { + logger.warn({ path }, 'unrecognized file request') + return res.sendStatus(404) + } + // check that the file does not use a relative path + for (const dir of Array.from(file.split('/'))) { + if (dir === '..') { + logger.warn({ path }, 'attempt to use a relative path') + return res.sendStatus(404) + } + } + // check that the requested path is normalized + const requestedFsPath = `${basePath}/${project_id}/${file}` + if (requestedFsPath !== Path.normalize(requestedFsPath)) { + logger.error( + { path: requestedFsPath }, + 'requestedFsPath is not normalized' + ) + return res.sendStatus(404) + } + // check that the requested path is not a symlink + return fs.realpath(requestedFsPath, function(err, realFsPath) { + if (err != null) { + if (err.code === 'ENOENT') { + return res.sendStatus(404) + } else { + logger.error( + { + err, + requestedFsPath, + realFsPath, + path: req.params[0], + project_id: req.params.project_id + }, + 'error checking file access' + ) + return res.sendStatus(500) + } + } else if (requestedFsPath !== realFsPath) { + logger.warn( + { + requestedFsPath, + realFsPath, + path: req.params[0], + project_id: req.params.project_id + }, + 'trying to access a different file (symlink), aborting' + ) + return res.sendStatus(404) + } else { + return expressStatic(req, res, next) + } + }) + } +} function __guard__(value, transform) { - return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; -} \ No newline at end of file + return typeof value !== 'undefined' && value !== null + ? transform(value) + : undefined +} diff --git a/services/clsi/app/js/TikzManager.js b/services/clsi/app/js/TikzManager.js index 9fa4a93604..3c57873553 100644 --- a/services/clsi/app/js/TikzManager.js +++ b/services/clsi/app/js/TikzManager.js @@ -11,52 +11,84 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let TikzManager; -const fs = require("fs"); -const Path = require("path"); -const ResourceWriter = require("./ResourceWriter"); -const SafeReader = require("./SafeReader"); -const logger = require("logger-sharelatex"); +let TikzManager +const fs = require('fs') +const Path = require('path') +const ResourceWriter = require('./ResourceWriter') +const SafeReader = require('./SafeReader') +const logger = require('logger-sharelatex') // for \tikzexternalize or pstool to work the main file needs to match the // jobname. Since we set the -jobname to output, we have to create a // copy of the main file as 'output.tex'. -module.exports = (TikzManager = { +module.exports = TikzManager = { + checkMainFile(compileDir, mainFile, resources, callback) { + // if there's already an output.tex file, we don't want to touch it + if (callback == null) { + callback = function(error, needsMainFile) {} + } + for (const resource of Array.from(resources)) { + if (resource.path === 'output.tex') { + logger.log({ compileDir, mainFile }, 'output.tex already in resources') + return callback(null, false) + } + } + // if there's no output.tex, see if we are using tikz/pgf or pstool in the main file + return ResourceWriter.checkPath(compileDir, mainFile, function( + error, + path + ) { + if (error != null) { + return callback(error) + } + return SafeReader.readFile(path, 65536, 'utf8', function(error, content) { + if (error != null) { + return callback(error) + } + const usesTikzExternalize = + (content != null + ? content.indexOf('\\tikzexternalize') + : undefined) >= 0 + const usesPsTool = + (content != null ? content.indexOf('{pstool}') : undefined) >= 0 + logger.log( + { compileDir, mainFile, usesTikzExternalize, usesPsTool }, + 'checked for packages needing main file as output.tex' + ) + const needsMainFile = usesTikzExternalize || usesPsTool + return callback(null, needsMainFile) + }) + }) + }, - checkMainFile(compileDir, mainFile, resources, callback) { - // if there's already an output.tex file, we don't want to touch it - if (callback == null) { callback = function(error, needsMainFile) {}; } - for (const resource of Array.from(resources)) { - if (resource.path === "output.tex") { - logger.log({compileDir, mainFile}, "output.tex already in resources"); - return callback(null, false); - } - } - // if there's no output.tex, see if we are using tikz/pgf or pstool in the main file - return ResourceWriter.checkPath(compileDir, mainFile, function(error, path) { - if (error != null) { return callback(error); } - return SafeReader.readFile(path, 65536, "utf8", function(error, content) { - if (error != null) { return callback(error); } - const usesTikzExternalize = (content != null ? content.indexOf("\\tikzexternalize") : undefined) >= 0; - const usesPsTool = (content != null ? content.indexOf("{pstool}") : undefined) >= 0; - logger.log({compileDir, mainFile, usesTikzExternalize, usesPsTool}, "checked for packages needing main file as output.tex"); - const needsMainFile = (usesTikzExternalize || usesPsTool); - return callback(null, needsMainFile); - }); - }); - }, - - injectOutputFile(compileDir, mainFile, callback) { - if (callback == null) { callback = function(error) {}; } - return ResourceWriter.checkPath(compileDir, mainFile, function(error, path) { - if (error != null) { return callback(error); } - return fs.readFile(path, "utf8", function(error, content) { - if (error != null) { return callback(error); } - logger.log({compileDir, mainFile}, "copied file to output.tex as project uses packages which require it"); - // use wx flag to ensure that output file does not already exist - return fs.writeFile(Path.join(compileDir, "output.tex"), content, {flag:'wx'}, callback); - }); - }); - } -}); + injectOutputFile(compileDir, mainFile, callback) { + if (callback == null) { + callback = function(error) {} + } + return ResourceWriter.checkPath(compileDir, mainFile, function( + error, + path + ) { + if (error != null) { + return callback(error) + } + return fs.readFile(path, 'utf8', function(error, content) { + if (error != null) { + return callback(error) + } + logger.log( + { compileDir, mainFile }, + 'copied file to output.tex as project uses packages which require it' + ) + // use wx flag to ensure that output file does not already exist + return fs.writeFile( + Path.join(compileDir, 'output.tex'), + content, + { flag: 'wx' }, + callback + ) + }) + }) + } +} diff --git a/services/clsi/app/js/UrlCache.js b/services/clsi/app/js/UrlCache.js index ade815b2cf..babdf9cf4c 100644 --- a/services/clsi/app/js/UrlCache.js +++ b/services/clsi/app/js/UrlCache.js @@ -12,185 +12,267 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let UrlCache; -const db = require("./db"); -const dbQueue = require("./DbQueue"); -const UrlFetcher = require("./UrlFetcher"); -const Settings = require("settings-sharelatex"); -const crypto = require("crypto"); -const fs = require("fs"); -const logger = require("logger-sharelatex"); -const async = require("async"); +let UrlCache +const db = require('./db') +const dbQueue = require('./DbQueue') +const UrlFetcher = require('./UrlFetcher') +const Settings = require('settings-sharelatex') +const crypto = require('crypto') +const fs = require('fs') +const logger = require('logger-sharelatex') +const async = require('async') -module.exports = (UrlCache = { - downloadUrlToFile(project_id, url, destPath, lastModified, callback) { - if (callback == null) { callback = function(error) {}; } - return UrlCache._ensureUrlIsInCache(project_id, url, lastModified, (error, pathToCachedUrl) => { - if (error != null) { return callback(error); } - return UrlCache._copyFile(pathToCachedUrl, destPath, function(error) { - if (error != null) { - return UrlCache._clearUrlDetails(project_id, url, () => callback(error)); - } else { - return callback(error); - } - }); - }); - }, +module.exports = UrlCache = { + downloadUrlToFile(project_id, url, destPath, lastModified, callback) { + if (callback == null) { + callback = function(error) {} + } + return UrlCache._ensureUrlIsInCache( + project_id, + url, + lastModified, + (error, pathToCachedUrl) => { + if (error != null) { + return callback(error) + } + return UrlCache._copyFile(pathToCachedUrl, destPath, function(error) { + if (error != null) { + return UrlCache._clearUrlDetails(project_id, url, () => + callback(error) + ) + } else { + return callback(error) + } + }) + } + ) + }, - clearProject(project_id, callback) { - if (callback == null) { callback = function(error) {}; } - return UrlCache._findAllUrlsInProject(project_id, function(error, urls) { - logger.log({project_id, url_count: urls.length}, "clearing project URLs"); - if (error != null) { return callback(error); } - const jobs = (Array.from(urls || [])).map((url) => - (url => - callback => - UrlCache._clearUrlFromCache(project_id, url, function(error) { - if (error != null) { - logger.error({err: error, project_id, url}, "error clearing project URL"); - } - return callback(); - }) - - )(url)); - return async.series(jobs, callback); - }); - }, + clearProject(project_id, callback) { + if (callback == null) { + callback = function(error) {} + } + return UrlCache._findAllUrlsInProject(project_id, function(error, urls) { + logger.log( + { project_id, url_count: urls.length }, + 'clearing project URLs' + ) + if (error != null) { + return callback(error) + } + const jobs = Array.from(urls || []).map(url => + (url => callback => + UrlCache._clearUrlFromCache(project_id, url, function(error) { + if (error != null) { + logger.error( + { err: error, project_id, url }, + 'error clearing project URL' + ) + } + return callback() + }))(url) + ) + return async.series(jobs, callback) + }) + }, - _ensureUrlIsInCache(project_id, url, lastModified, callback) { - if (callback == null) { callback = function(error, pathOnDisk) {}; } - if (lastModified != null) { - // MYSQL only stores dates to an accuracy of a second but the incoming lastModified might have milliseconds. - // So round down to seconds - lastModified = new Date(Math.floor(lastModified.getTime() / 1000) * 1000); - } - return UrlCache._doesUrlNeedDownloading(project_id, url, lastModified, (error, needsDownloading) => { - if (error != null) { return callback(error); } - if (needsDownloading) { - logger.log({url, lastModified}, "downloading URL"); - return UrlFetcher.pipeUrlToFile(url, UrlCache._cacheFilePathForUrl(project_id, url), error => { - if (error != null) { return callback(error); } - return UrlCache._updateOrCreateUrlDetails(project_id, url, lastModified, error => { - if (error != null) { return callback(error); } - return callback(null, UrlCache._cacheFilePathForUrl(project_id, url)); - }); - }); - } else { - logger.log({url, lastModified}, "URL is up to date in cache"); - return callback(null, UrlCache._cacheFilePathForUrl(project_id, url)); - } - }); - }, - - _doesUrlNeedDownloading(project_id, url, lastModified, callback) { - if (callback == null) { callback = function(error, needsDownloading) {}; } - if ((lastModified == null)) { - return callback(null, true); - } - return UrlCache._findUrlDetails(project_id, url, function(error, urlDetails) { - if (error != null) { return callback(error); } - if ((urlDetails == null) || (urlDetails.lastModified == null) || (urlDetails.lastModified.getTime() < lastModified.getTime())) { - return callback(null, true); - } else { - return callback(null, false); - } - }); - }, + _ensureUrlIsInCache(project_id, url, lastModified, callback) { + if (callback == null) { + callback = function(error, pathOnDisk) {} + } + if (lastModified != null) { + // MYSQL only stores dates to an accuracy of a second but the incoming lastModified might have milliseconds. + // So round down to seconds + lastModified = new Date(Math.floor(lastModified.getTime() / 1000) * 1000) + } + return UrlCache._doesUrlNeedDownloading( + project_id, + url, + lastModified, + (error, needsDownloading) => { + if (error != null) { + return callback(error) + } + if (needsDownloading) { + logger.log({ url, lastModified }, 'downloading URL') + return UrlFetcher.pipeUrlToFile( + url, + UrlCache._cacheFilePathForUrl(project_id, url), + error => { + if (error != null) { + return callback(error) + } + return UrlCache._updateOrCreateUrlDetails( + project_id, + url, + lastModified, + error => { + if (error != null) { + return callback(error) + } + return callback( + null, + UrlCache._cacheFilePathForUrl(project_id, url) + ) + } + ) + } + ) + } else { + logger.log({ url, lastModified }, 'URL is up to date in cache') + return callback(null, UrlCache._cacheFilePathForUrl(project_id, url)) + } + } + ) + }, - _cacheFileNameForUrl(project_id, url) { - return project_id + ":" + crypto.createHash("md5").update(url).digest("hex"); - }, + _doesUrlNeedDownloading(project_id, url, lastModified, callback) { + if (callback == null) { + callback = function(error, needsDownloading) {} + } + if (lastModified == null) { + return callback(null, true) + } + return UrlCache._findUrlDetails(project_id, url, function( + error, + urlDetails + ) { + if (error != null) { + return callback(error) + } + if ( + urlDetails == null || + urlDetails.lastModified == null || + urlDetails.lastModified.getTime() < lastModified.getTime() + ) { + return callback(null, true) + } else { + return callback(null, false) + } + }) + }, - _cacheFilePathForUrl(project_id, url) { - return `${Settings.path.clsiCacheDir}/${UrlCache._cacheFileNameForUrl(project_id, url)}`; - }, + _cacheFileNameForUrl(project_id, url) { + return ( + project_id + + ':' + + crypto + .createHash('md5') + .update(url) + .digest('hex') + ) + }, - _copyFile(from, to, _callback) { - if (_callback == null) { _callback = function(error) {}; } - const callbackOnce = function(error) { - if (error != null) { - logger.error({err: error, from, to}, "error copying file from cache"); - } - _callback(error); - return _callback = function() {}; - }; - const writeStream = fs.createWriteStream(to); - const readStream = fs.createReadStream(from); - writeStream.on("error", callbackOnce); - readStream.on("error", callbackOnce); - writeStream.on("close", callbackOnce); - return writeStream.on("open", () => readStream.pipe(writeStream)); - }, + _cacheFilePathForUrl(project_id, url) { + return `${Settings.path.clsiCacheDir}/${UrlCache._cacheFileNameForUrl( + project_id, + url + )}` + }, - _clearUrlFromCache(project_id, url, callback) { - if (callback == null) { callback = function(error) {}; } - return UrlCache._clearUrlDetails(project_id, url, function(error) { - if (error != null) { return callback(error); } - return UrlCache._deleteUrlCacheFromDisk(project_id, url, function(error) { - if (error != null) { return callback(error); } - return callback(null); - }); - }); - }, + _copyFile(from, to, _callback) { + if (_callback == null) { + _callback = function(error) {} + } + const callbackOnce = function(error) { + if (error != null) { + logger.error({ err: error, from, to }, 'error copying file from cache') + } + _callback(error) + return (_callback = function() {}) + } + const writeStream = fs.createWriteStream(to) + const readStream = fs.createReadStream(from) + writeStream.on('error', callbackOnce) + readStream.on('error', callbackOnce) + writeStream.on('close', callbackOnce) + return writeStream.on('open', () => readStream.pipe(writeStream)) + }, - _deleteUrlCacheFromDisk(project_id, url, callback) { - if (callback == null) { callback = function(error) {}; } - return fs.unlink(UrlCache._cacheFilePathForUrl(project_id, url), function(error) { - if ((error != null) && (error.code !== 'ENOENT')) { // no error if the file isn't present - return callback(error); - } else { - return callback(); - } - }); - }, + _clearUrlFromCache(project_id, url, callback) { + if (callback == null) { + callback = function(error) {} + } + return UrlCache._clearUrlDetails(project_id, url, function(error) { + if (error != null) { + return callback(error) + } + return UrlCache._deleteUrlCacheFromDisk(project_id, url, function(error) { + if (error != null) { + return callback(error) + } + return callback(null) + }) + }) + }, - _findUrlDetails(project_id, url, callback) { - if (callback == null) { callback = function(error, urlDetails) {}; } - const job = cb=> - db.UrlCache.find({where: { url, project_id }}) - .then(urlDetails => cb(null, urlDetails)) - .error(cb) - ; - return dbQueue.queue.push(job, callback); - }, + _deleteUrlCacheFromDisk(project_id, url, callback) { + if (callback == null) { + callback = function(error) {} + } + return fs.unlink(UrlCache._cacheFilePathForUrl(project_id, url), function( + error + ) { + if (error != null && error.code !== 'ENOENT') { + // no error if the file isn't present + return callback(error) + } else { + return callback() + } + }) + }, - _updateOrCreateUrlDetails(project_id, url, lastModified, callback) { - if (callback == null) { callback = function(error) {}; } - const job = cb=> - db.UrlCache.findOrCreate({where: {url, project_id}}) - .spread( - (urlDetails, created) => - urlDetails.updateAttributes({lastModified}) - .then(() => cb()) - .error(cb) - ) - .error(cb) - ; - return dbQueue.queue.push(job, callback); - }, + _findUrlDetails(project_id, url, callback) { + if (callback == null) { + callback = function(error, urlDetails) {} + } + const job = cb => + db.UrlCache.find({ where: { url, project_id } }) + .then(urlDetails => cb(null, urlDetails)) + .error(cb) + return dbQueue.queue.push(job, callback) + }, - _clearUrlDetails(project_id, url, callback) { - if (callback == null) { callback = function(error) {}; } - const job = cb=> - db.UrlCache.destroy({where: {url, project_id}}) - .then(() => cb(null)) - .error(cb) - ; - return dbQueue.queue.push(job, callback); - }, + _updateOrCreateUrlDetails(project_id, url, lastModified, callback) { + if (callback == null) { + callback = function(error) {} + } + const job = cb => + db.UrlCache.findOrCreate({ where: { url, project_id } }) + .spread((urlDetails, created) => + urlDetails + .updateAttributes({ lastModified }) + .then(() => cb()) + .error(cb) + ) + .error(cb) + return dbQueue.queue.push(job, callback) + }, + _clearUrlDetails(project_id, url, callback) { + if (callback == null) { + callback = function(error) {} + } + const job = cb => + db.UrlCache.destroy({ where: { url, project_id } }) + .then(() => cb(null)) + .error(cb) + return dbQueue.queue.push(job, callback) + }, - _findAllUrlsInProject(project_id, callback) { - if (callback == null) { callback = function(error, urls) {}; } - const job = cb=> - db.UrlCache.findAll({where: { project_id }}) - .then( - urlEntries => cb(null, urlEntries.map(entry => entry.url))) - .error(cb) - ; - return dbQueue.queue.push(job, callback); - } -}); - - - + _findAllUrlsInProject(project_id, callback) { + if (callback == null) { + callback = function(error, urls) {} + } + const job = cb => + db.UrlCache.findAll({ where: { project_id } }) + .then(urlEntries => + cb( + null, + urlEntries.map(entry => entry.url) + ) + ) + .error(cb) + return dbQueue.queue.push(job, callback) + } +} diff --git a/services/clsi/app/js/UrlFetcher.js b/services/clsi/app/js/UrlFetcher.js index fec397c1de..19c681ca1f 100644 --- a/services/clsi/app/js/UrlFetcher.js +++ b/services/clsi/app/js/UrlFetcher.js @@ -12,85 +12,109 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let UrlFetcher; -const request = require("request").defaults({jar: false}); -const fs = require("fs"); -const logger = require("logger-sharelatex"); -const settings = require("settings-sharelatex"); -const URL = require('url'); +let UrlFetcher +const request = require('request').defaults({ jar: false }) +const fs = require('fs') +const logger = require('logger-sharelatex') +const settings = require('settings-sharelatex') +const URL = require('url') -const oneMinute = 60 * 1000; +const oneMinute = 60 * 1000 -module.exports = (UrlFetcher = { - pipeUrlToFile(url, filePath, _callback) { - if (_callback == null) { _callback = function(error) {}; } - const callbackOnce = function(error) { - if (timeoutHandler != null) { clearTimeout(timeoutHandler); } - _callback(error); - return _callback = function() {}; - }; +module.exports = UrlFetcher = { + pipeUrlToFile(url, filePath, _callback) { + if (_callback == null) { + _callback = function(error) {} + } + const callbackOnce = function(error) { + if (timeoutHandler != null) { + clearTimeout(timeoutHandler) + } + _callback(error) + return (_callback = function() {}) + } - if (settings.filestoreDomainOveride != null) { - const p = URL.parse(url).path; - url = `${settings.filestoreDomainOveride}${p}`; - } - var timeoutHandler = setTimeout(function() { - timeoutHandler = null; - logger.error({url, filePath}, "Timed out downloading file to cache"); - return callbackOnce(new Error(`Timed out downloading file to cache ${url}`)); - } - // FIXME: maybe need to close fileStream here - , 3 * oneMinute); + if (settings.filestoreDomainOveride != null) { + const p = URL.parse(url).path + url = `${settings.filestoreDomainOveride}${p}` + } + var timeoutHandler = setTimeout( + function() { + timeoutHandler = null + logger.error({ url, filePath }, 'Timed out downloading file to cache') + return callbackOnce( + new Error(`Timed out downloading file to cache ${url}`) + ) + }, + // FIXME: maybe need to close fileStream here + 3 * oneMinute + ) - logger.log({url, filePath}, "started downloading url to cache"); - const urlStream = request.get({url, timeout: oneMinute}); - urlStream.pause(); // stop data flowing until we are ready + logger.log({ url, filePath }, 'started downloading url to cache') + const urlStream = request.get({ url, timeout: oneMinute }) + urlStream.pause() // stop data flowing until we are ready - // attach handlers before setting up pipes - urlStream.on("error", function(error) { - logger.error({err: error, url, filePath}, "error downloading url"); - return callbackOnce(error || new Error(`Something went wrong downloading the URL ${url}`)); - }); + // attach handlers before setting up pipes + urlStream.on('error', function(error) { + logger.error({ err: error, url, filePath }, 'error downloading url') + return callbackOnce( + error || new Error(`Something went wrong downloading the URL ${url}`) + ) + }) - urlStream.on("end", () => logger.log({url, filePath}, "finished downloading file into cache")); + urlStream.on('end', () => + logger.log({ url, filePath }, 'finished downloading file into cache') + ) - return urlStream.on("response", function(res) { - if ((res.statusCode >= 200) && (res.statusCode < 300)) { - const fileStream = fs.createWriteStream(filePath); + return urlStream.on('response', function(res) { + if (res.statusCode >= 200 && res.statusCode < 300) { + const fileStream = fs.createWriteStream(filePath) - // attach handlers before setting up pipes - fileStream.on('error', function(error) { - logger.error({err: error, url, filePath}, "error writing file into cache"); - return fs.unlink(filePath, function(err) { - if (err != null) { - logger.err({err, filePath}, "error deleting file from cache"); - } - return callbackOnce(error); - }); - }); + // attach handlers before setting up pipes + fileStream.on('error', function(error) { + logger.error( + { err: error, url, filePath }, + 'error writing file into cache' + ) + return fs.unlink(filePath, function(err) { + if (err != null) { + logger.err({ err, filePath }, 'error deleting file from cache') + } + return callbackOnce(error) + }) + }) - fileStream.on('finish', function() { - logger.log({url, filePath}, "finished writing file into cache"); - return callbackOnce(); - }); + fileStream.on('finish', function() { + logger.log({ url, filePath }, 'finished writing file into cache') + return callbackOnce() + }) - fileStream.on('pipe', () => logger.log({url, filePath}, "piping into filestream")); + fileStream.on('pipe', () => + logger.log({ url, filePath }, 'piping into filestream') + ) - urlStream.pipe(fileStream); - return urlStream.resume(); // now we are ready to handle the data - } else { - logger.error({statusCode: res.statusCode, url, filePath}, "unexpected status code downloading url to cache"); - // https://nodejs.org/api/http.html#http_class_http_clientrequest - // If you add a 'response' event handler, then you must consume - // the data from the response object, either by calling - // response.read() whenever there is a 'readable' event, or by - // adding a 'data' handler, or by calling the .resume() - // method. Until the data is consumed, the 'end' event will not - // fire. Also, until the data is read it will consume memory - // that can eventually lead to a 'process out of memory' error. - urlStream.resume(); // discard the data - return callbackOnce(new Error(`URL returned non-success status code: ${res.statusCode} ${url}`)); - } - }); - } -}); + urlStream.pipe(fileStream) + return urlStream.resume() // now we are ready to handle the data + } else { + logger.error( + { statusCode: res.statusCode, url, filePath }, + 'unexpected status code downloading url to cache' + ) + // https://nodejs.org/api/http.html#http_class_http_clientrequest + // If you add a 'response' event handler, then you must consume + // the data from the response object, either by calling + // response.read() whenever there is a 'readable' event, or by + // adding a 'data' handler, or by calling the .resume() + // method. Until the data is consumed, the 'end' event will not + // fire. Also, until the data is read it will consume memory + // that can eventually lead to a 'process out of memory' error. + urlStream.resume() // discard the data + return callbackOnce( + new Error( + `URL returned non-success status code: ${res.statusCode} ${url}` + ) + ) + } + }) + } +} diff --git a/services/clsi/app/js/db.js b/services/clsi/app/js/db.js index c5dd980040..c749af253c 100644 --- a/services/clsi/app/js/db.js +++ b/services/clsi/app/js/db.js @@ -8,57 +8,60 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const Sequelize = require("sequelize"); -const Settings = require("settings-sharelatex"); -const _ = require("underscore"); -const logger = require("logger-sharelatex"); +const Sequelize = require('sequelize') +const Settings = require('settings-sharelatex') +const _ = require('underscore') +const logger = require('logger-sharelatex') -const options = _.extend({logging:false}, Settings.mysql.clsi); +const options = _.extend({ logging: false }, Settings.mysql.clsi) -logger.log({dbPath:Settings.mysql.clsi.storage}, "connecting to db"); +logger.log({ dbPath: Settings.mysql.clsi.storage }, 'connecting to db') const sequelize = new Sequelize( - Settings.mysql.clsi.database, - Settings.mysql.clsi.username, - Settings.mysql.clsi.password, - options -); + Settings.mysql.clsi.database, + Settings.mysql.clsi.username, + Settings.mysql.clsi.password, + options +) -if (Settings.mysql.clsi.dialect === "sqlite") { - logger.log("running PRAGMA journal_mode=WAL;"); - sequelize.query("PRAGMA journal_mode=WAL;"); - sequelize.query("PRAGMA synchronous=OFF;"); - sequelize.query("PRAGMA read_uncommitted = true;"); +if (Settings.mysql.clsi.dialect === 'sqlite') { + logger.log('running PRAGMA journal_mode=WAL;') + sequelize.query('PRAGMA journal_mode=WAL;') + sequelize.query('PRAGMA synchronous=OFF;') + sequelize.query('PRAGMA read_uncommitted = true;') } module.exports = { - UrlCache: sequelize.define("UrlCache", { - url: Sequelize.STRING, - project_id: Sequelize.STRING, - lastModified: Sequelize.DATE - }, { - indexes: [ - {fields: ['url', 'project_id']}, - {fields: ['project_id']} - ] - }), + UrlCache: sequelize.define( + 'UrlCache', + { + url: Sequelize.STRING, + project_id: Sequelize.STRING, + lastModified: Sequelize.DATE + }, + { + indexes: [{ fields: ['url', 'project_id'] }, { fields: ['project_id'] }] + } + ), - Project: sequelize.define("Project", { - project_id: {type: Sequelize.STRING, primaryKey: true}, - lastAccessed: Sequelize.DATE - }, { - indexes: [ - {fields: ['lastAccessed']} - ] - }), + Project: sequelize.define( + 'Project', + { + project_id: { type: Sequelize.STRING, primaryKey: true }, + lastAccessed: Sequelize.DATE + }, + { + indexes: [{ fields: ['lastAccessed'] }] + } + ), - op: Sequelize.Op, - - sync() { - logger.log({dbPath:Settings.mysql.clsi.storage}, "syncing db schema"); - return sequelize.sync() - .then(() => logger.log("db sync complete")).catch(err=> console.log(err, "error syncing")); - } -}; + op: Sequelize.Op, - + sync() { + logger.log({ dbPath: Settings.mysql.clsi.storage }, 'syncing db schema') + return sequelize + .sync() + .then(() => logger.log('db sync complete')) + .catch(err => console.log(err, 'error syncing')) + } +} From e57097afb4234fdd72de02c44192c97b2423222e Mon Sep 17 00:00:00 2001 From: decaffeinate <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:14:56 +0100 Subject: [PATCH 521/754] decaffeinate: Rename CompileControllerTests.coffee and 17 other files from .coffee to .js --- .../{CompileControllerTests.coffee => CompileControllerTests.js} | 0 .../coffee/{CompileManagerTests.coffee => CompileManagerTests.js} | 0 .../{ContentTypeMapperTests.coffee => ContentTypeMapperTests.js} | 0 .../{DockerLockManagerTests.coffee => DockerLockManagerTests.js} | 0 .../coffee/{DockerRunnerTests.coffee => DockerRunnerTests.js} | 0 .../{DraftModeManagerTests.coffee => DraftModeManagerTests.js} | 0 .../unit/coffee/{LatexRunnerTests.coffee => LatexRunnerTests.js} | 0 .../unit/coffee/{LockManagerTests.coffee => LockManagerTests.js} | 0 .../{OutputFileFinderTests.coffee => OutputFileFinderTests.js} | 0 ...utputFileOptimiserTests.coffee => OutputFileOptimiserTests.js} | 0 ...tenceManagerTests.coffee => ProjectPersistenceManagerTests.js} | 0 .../coffee/{RequestParserTests.coffee => RequestParserTests.js} | 0 ...ourceStateManagerTests.coffee => ResourceStateManagerTests.js} | 0 .../coffee/{ResourceWriterTests.coffee => ResourceWriterTests.js} | 0 ...bidSymlinksTests.coffee => StaticServerForbidSymlinksTests.js} | 0 .../clsi/test/unit/coffee/{TikzManager.coffee => TikzManager.js} | 0 .../test/unit/coffee/{UrlCacheTests.coffee => UrlCacheTests.js} | 0 .../unit/coffee/{UrlFetcherTests.coffee => UrlFetcherTests.js} | 0 18 files changed, 0 insertions(+), 0 deletions(-) rename services/clsi/test/unit/coffee/{CompileControllerTests.coffee => CompileControllerTests.js} (100%) rename services/clsi/test/unit/coffee/{CompileManagerTests.coffee => CompileManagerTests.js} (100%) rename services/clsi/test/unit/coffee/{ContentTypeMapperTests.coffee => ContentTypeMapperTests.js} (100%) rename services/clsi/test/unit/coffee/{DockerLockManagerTests.coffee => DockerLockManagerTests.js} (100%) rename services/clsi/test/unit/coffee/{DockerRunnerTests.coffee => DockerRunnerTests.js} (100%) rename services/clsi/test/unit/coffee/{DraftModeManagerTests.coffee => DraftModeManagerTests.js} (100%) rename services/clsi/test/unit/coffee/{LatexRunnerTests.coffee => LatexRunnerTests.js} (100%) rename services/clsi/test/unit/coffee/{LockManagerTests.coffee => LockManagerTests.js} (100%) rename services/clsi/test/unit/coffee/{OutputFileFinderTests.coffee => OutputFileFinderTests.js} (100%) rename services/clsi/test/unit/coffee/{OutputFileOptimiserTests.coffee => OutputFileOptimiserTests.js} (100%) rename services/clsi/test/unit/coffee/{ProjectPersistenceManagerTests.coffee => ProjectPersistenceManagerTests.js} (100%) rename services/clsi/test/unit/coffee/{RequestParserTests.coffee => RequestParserTests.js} (100%) rename services/clsi/test/unit/coffee/{ResourceStateManagerTests.coffee => ResourceStateManagerTests.js} (100%) rename services/clsi/test/unit/coffee/{ResourceWriterTests.coffee => ResourceWriterTests.js} (100%) rename services/clsi/test/unit/coffee/{StaticServerForbidSymlinksTests.coffee => StaticServerForbidSymlinksTests.js} (100%) rename services/clsi/test/unit/coffee/{TikzManager.coffee => TikzManager.js} (100%) rename services/clsi/test/unit/coffee/{UrlCacheTests.coffee => UrlCacheTests.js} (100%) rename services/clsi/test/unit/coffee/{UrlFetcherTests.coffee => UrlFetcherTests.js} (100%) diff --git a/services/clsi/test/unit/coffee/CompileControllerTests.coffee b/services/clsi/test/unit/coffee/CompileControllerTests.js similarity index 100% rename from services/clsi/test/unit/coffee/CompileControllerTests.coffee rename to services/clsi/test/unit/coffee/CompileControllerTests.js diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.coffee b/services/clsi/test/unit/coffee/CompileManagerTests.js similarity index 100% rename from services/clsi/test/unit/coffee/CompileManagerTests.coffee rename to services/clsi/test/unit/coffee/CompileManagerTests.js diff --git a/services/clsi/test/unit/coffee/ContentTypeMapperTests.coffee b/services/clsi/test/unit/coffee/ContentTypeMapperTests.js similarity index 100% rename from services/clsi/test/unit/coffee/ContentTypeMapperTests.coffee rename to services/clsi/test/unit/coffee/ContentTypeMapperTests.js diff --git a/services/clsi/test/unit/coffee/DockerLockManagerTests.coffee b/services/clsi/test/unit/coffee/DockerLockManagerTests.js similarity index 100% rename from services/clsi/test/unit/coffee/DockerLockManagerTests.coffee rename to services/clsi/test/unit/coffee/DockerLockManagerTests.js diff --git a/services/clsi/test/unit/coffee/DockerRunnerTests.coffee b/services/clsi/test/unit/coffee/DockerRunnerTests.js similarity index 100% rename from services/clsi/test/unit/coffee/DockerRunnerTests.coffee rename to services/clsi/test/unit/coffee/DockerRunnerTests.js diff --git a/services/clsi/test/unit/coffee/DraftModeManagerTests.coffee b/services/clsi/test/unit/coffee/DraftModeManagerTests.js similarity index 100% rename from services/clsi/test/unit/coffee/DraftModeManagerTests.coffee rename to services/clsi/test/unit/coffee/DraftModeManagerTests.js diff --git a/services/clsi/test/unit/coffee/LatexRunnerTests.coffee b/services/clsi/test/unit/coffee/LatexRunnerTests.js similarity index 100% rename from services/clsi/test/unit/coffee/LatexRunnerTests.coffee rename to services/clsi/test/unit/coffee/LatexRunnerTests.js diff --git a/services/clsi/test/unit/coffee/LockManagerTests.coffee b/services/clsi/test/unit/coffee/LockManagerTests.js similarity index 100% rename from services/clsi/test/unit/coffee/LockManagerTests.coffee rename to services/clsi/test/unit/coffee/LockManagerTests.js diff --git a/services/clsi/test/unit/coffee/OutputFileFinderTests.coffee b/services/clsi/test/unit/coffee/OutputFileFinderTests.js similarity index 100% rename from services/clsi/test/unit/coffee/OutputFileFinderTests.coffee rename to services/clsi/test/unit/coffee/OutputFileFinderTests.js diff --git a/services/clsi/test/unit/coffee/OutputFileOptimiserTests.coffee b/services/clsi/test/unit/coffee/OutputFileOptimiserTests.js similarity index 100% rename from services/clsi/test/unit/coffee/OutputFileOptimiserTests.coffee rename to services/clsi/test/unit/coffee/OutputFileOptimiserTests.js diff --git a/services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.coffee b/services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.js similarity index 100% rename from services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.coffee rename to services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.js diff --git a/services/clsi/test/unit/coffee/RequestParserTests.coffee b/services/clsi/test/unit/coffee/RequestParserTests.js similarity index 100% rename from services/clsi/test/unit/coffee/RequestParserTests.coffee rename to services/clsi/test/unit/coffee/RequestParserTests.js diff --git a/services/clsi/test/unit/coffee/ResourceStateManagerTests.coffee b/services/clsi/test/unit/coffee/ResourceStateManagerTests.js similarity index 100% rename from services/clsi/test/unit/coffee/ResourceStateManagerTests.coffee rename to services/clsi/test/unit/coffee/ResourceStateManagerTests.js diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.coffee b/services/clsi/test/unit/coffee/ResourceWriterTests.js similarity index 100% rename from services/clsi/test/unit/coffee/ResourceWriterTests.coffee rename to services/clsi/test/unit/coffee/ResourceWriterTests.js diff --git a/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.coffee b/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.js similarity index 100% rename from services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.coffee rename to services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.js diff --git a/services/clsi/test/unit/coffee/TikzManager.coffee b/services/clsi/test/unit/coffee/TikzManager.js similarity index 100% rename from services/clsi/test/unit/coffee/TikzManager.coffee rename to services/clsi/test/unit/coffee/TikzManager.js diff --git a/services/clsi/test/unit/coffee/UrlCacheTests.coffee b/services/clsi/test/unit/coffee/UrlCacheTests.js similarity index 100% rename from services/clsi/test/unit/coffee/UrlCacheTests.coffee rename to services/clsi/test/unit/coffee/UrlCacheTests.js diff --git a/services/clsi/test/unit/coffee/UrlFetcherTests.coffee b/services/clsi/test/unit/coffee/UrlFetcherTests.js similarity index 100% rename from services/clsi/test/unit/coffee/UrlFetcherTests.coffee rename to services/clsi/test/unit/coffee/UrlFetcherTests.js From 37b4e96de40ed0318e1daeba5507f7fed680c568 Mon Sep 17 00:00:00 2001 From: decaffeinate <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:15:08 +0100 Subject: [PATCH 522/754] decaffeinate: Convert CompileControllerTests.coffee and 17 other files to JS --- .../unit/coffee/CompileControllerTests.js | 418 ++++---- .../test/unit/coffee/CompileManagerTests.js | 646 ++++++------ .../unit/coffee/ContentTypeMapperTests.js | 100 +- .../unit/coffee/DockerLockManagerTests.js | 303 +++--- .../test/unit/coffee/DockerRunnerTests.js | 947 ++++++++++-------- .../test/unit/coffee/DraftModeManagerTests.js | 124 ++- .../clsi/test/unit/coffee/LatexRunnerTests.js | 162 +-- .../clsi/test/unit/coffee/LockManagerTests.js | 108 +- .../test/unit/coffee/OutputFileFinderTests.js | 122 ++- .../unit/coffee/OutputFileOptimiserTests.js | 198 ++-- .../coffee/ProjectPersistenceManagerTests.js | 120 ++- .../test/unit/coffee/RequestParserTests.js | 535 ++++++---- .../unit/coffee/ResourceStateManagerTests.js | 210 ++-- .../test/unit/coffee/ResourceWriterTests.js | 567 ++++++----- .../coffee/StaticServerForbidSymlinksTests.js | 295 +++--- services/clsi/test/unit/coffee/TikzManager.js | 229 +++-- .../clsi/test/unit/coffee/UrlCacheTests.js | 374 ++++--- .../clsi/test/unit/coffee/UrlFetcherTests.js | 234 +++-- 18 files changed, 3291 insertions(+), 2401 deletions(-) diff --git a/services/clsi/test/unit/coffee/CompileControllerTests.js b/services/clsi/test/unit/coffee/CompileControllerTests.js index 034adfcda7..1defed70d1 100644 --- a/services/clsi/test/unit/coffee/CompileControllerTests.js +++ b/services/clsi/test/unit/coffee/CompileControllerTests.js @@ -1,217 +1,269 @@ -SandboxedModule = require('sandboxed-module') -sinon = require('sinon') -require('chai').should() -modulePath = require('path').join __dirname, '../../../app/js/CompileController' -tk = require("timekeeper") +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module'); +const sinon = require('sinon'); +require('chai').should(); +const modulePath = require('path').join(__dirname, '../../../app/js/CompileController'); +const tk = require("timekeeper"); -describe "CompileController", -> - beforeEach -> - @CompileController = SandboxedModule.require modulePath, requires: - "./CompileManager": @CompileManager = {} - "./RequestParser": @RequestParser = {} - "settings-sharelatex": @Settings = - apis: - clsi: +describe("CompileController", function() { + beforeEach(function() { + this.CompileController = SandboxedModule.require(modulePath, { requires: { + "./CompileManager": (this.CompileManager = {}), + "./RequestParser": (this.RequestParser = {}), + "settings-sharelatex": (this.Settings = { + apis: { + clsi: { url: "http://clsi.example.com" - "./ProjectPersistenceManager": @ProjectPersistenceManager = {} - "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub(), err:sinon.stub(), warn: sinon.stub()} - @Settings.externalUrl = "http://www.example.com" - @req = {} - @res = {} - @next = sinon.stub() + } + } + }), + "./ProjectPersistenceManager": (this.ProjectPersistenceManager = {}), + "logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub(), err:sinon.stub(), warn: sinon.stub()}) + } + }); + this.Settings.externalUrl = "http://www.example.com"; + this.req = {}; + this.res = {}; + return this.next = sinon.stub(); + }); - describe "compile", -> - beforeEach -> - @req.body = { + describe("compile", function() { + beforeEach(function() { + this.req.body = { compile: "mock-body" - } - @req.params = - project_id: @project_id = "project-id-123" - @request = { + }; + this.req.params = + {project_id: (this.project_id = "project-id-123")}; + this.request = { compile: "mock-parsed-request" - } - @request_with_project_id = - compile: @request.compile - project_id: @project_id - @output_files = [{ - path: "output.pdf" - type: "pdf" + }; + this.request_with_project_id = { + compile: this.request.compile, + project_id: this.project_id + }; + this.output_files = [{ + path: "output.pdf", + type: "pdf", build: 1234 }, { - path: "output.log" - type: "log" + path: "output.log", + type: "log", build: 1234 - }] - @RequestParser.parse = sinon.stub().callsArgWith(1, null, @request) - @ProjectPersistenceManager.markProjectAsJustAccessed = sinon.stub().callsArg(1) - @res.status = sinon.stub().returnsThis() - @res.send = sinon.stub() + }]; + this.RequestParser.parse = sinon.stub().callsArgWith(1, null, this.request); + this.ProjectPersistenceManager.markProjectAsJustAccessed = sinon.stub().callsArg(1); + this.res.status = sinon.stub().returnsThis(); + return this.res.send = sinon.stub(); + }); - describe "successfully", -> - beforeEach -> - @CompileManager.doCompileWithLock = sinon.stub().callsArgWith(1, null, @output_files) - @CompileController.compile @req, @res + describe("successfully", function() { + beforeEach(function() { + this.CompileManager.doCompileWithLock = sinon.stub().callsArgWith(1, null, this.output_files); + return this.CompileController.compile(this.req, this.res); + }); - it "should parse the request", -> - @RequestParser.parse - .calledWith(@req.body) - .should.equal true + it("should parse the request", function() { + return this.RequestParser.parse + .calledWith(this.req.body) + .should.equal(true); + }); - it "should run the compile for the specified project", -> - @CompileManager.doCompileWithLock - .calledWith(@request_with_project_id) - .should.equal true + it("should run the compile for the specified project", function() { + return this.CompileManager.doCompileWithLock + .calledWith(this.request_with_project_id) + .should.equal(true); + }); - it "should mark the project as accessed", -> - @ProjectPersistenceManager.markProjectAsJustAccessed - .calledWith(@project_id) - .should.equal true + it("should mark the project as accessed", function() { + return this.ProjectPersistenceManager.markProjectAsJustAccessed + .calledWith(this.project_id) + .should.equal(true); + }); - it "should return the JSON response", -> - @res.status.calledWith(200).should.equal true - @res.send - .calledWith( - compile: - status: "success" - error: null - outputFiles: @output_files.map (file) => - url: "#{@Settings.apis.clsi.url}/project/#{@project_id}/build/#{file.build}/output/#{file.path}" - path: file.path - type: file.type - build: file.build - ) - .should.equal true + return it("should return the JSON response", function() { + this.res.status.calledWith(200).should.equal(true); + return this.res.send + .calledWith({ + compile: { + status: "success", + error: null, + outputFiles: this.output_files.map(file => { + return { + url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`, + path: file.path, + type: file.type, + build: file.build + }; + }) + } + }) + .should.equal(true); + }); + }); - describe "with an error", -> - beforeEach -> - @CompileManager.doCompileWithLock = sinon.stub().callsArgWith(1, new Error(@message = "error message"), null) - @CompileController.compile @req, @res + describe("with an error", function() { + beforeEach(function() { + this.CompileManager.doCompileWithLock = sinon.stub().callsArgWith(1, new Error(this.message = "error message"), null); + return this.CompileController.compile(this.req, this.res); + }); - it "should return the JSON response with the error", -> - @res.status.calledWith(500).should.equal true - @res.send - .calledWith( - compile: - status: "error" - error: @message + return it("should return the JSON response with the error", function() { + this.res.status.calledWith(500).should.equal(true); + return this.res.send + .calledWith({ + compile: { + status: "error", + error: this.message, outputFiles: [] - ) - .should.equal true + } + }) + .should.equal(true); + }); + }); - describe "when the request times out", -> - beforeEach -> - @error = new Error(@message = "container timed out") - @error.timedout = true - @CompileManager.doCompileWithLock = sinon.stub().callsArgWith(1, @error, null) - @CompileController.compile @req, @res + describe("when the request times out", function() { + beforeEach(function() { + this.error = new Error(this.message = "container timed out"); + this.error.timedout = true; + this.CompileManager.doCompileWithLock = sinon.stub().callsArgWith(1, this.error, null); + return this.CompileController.compile(this.req, this.res); + }); - it "should return the JSON response with the timeout status", -> - @res.status.calledWith(200).should.equal true - @res.send - .calledWith( - compile: - status: "timedout" - error: @message + return it("should return the JSON response with the timeout status", function() { + this.res.status.calledWith(200).should.equal(true); + return this.res.send + .calledWith({ + compile: { + status: "timedout", + error: this.message, outputFiles: [] - ) - .should.equal true + } + }) + .should.equal(true); + }); + }); - describe "when the request returns no output files", -> - beforeEach -> - @CompileManager.doCompileWithLock = sinon.stub().callsArgWith(1, null, []) - @CompileController.compile @req, @res + return describe("when the request returns no output files", function() { + beforeEach(function() { + this.CompileManager.doCompileWithLock = sinon.stub().callsArgWith(1, null, []); + return this.CompileController.compile(this.req, this.res); + }); - it "should return the JSON response with the failure status", -> - @res.status.calledWith(200).should.equal true - @res.send - .calledWith( - compile: - error: null - status: "failure" + return it("should return the JSON response with the failure status", function() { + this.res.status.calledWith(200).should.equal(true); + return this.res.send + .calledWith({ + compile: { + error: null, + status: "failure", outputFiles: [] - ) - .should.equal true + } + }) + .should.equal(true); + }); + }); + }); - describe "syncFromCode", -> - beforeEach -> - @file = "main.tex" - @line = 42 - @column = 5 - @project_id = "mock-project-id" - @req.params = - project_id: @project_id - @req.query = - file: @file - line: @line.toString() - column: @column.toString() - @res.json = sinon.stub() + describe("syncFromCode", function() { + beforeEach(function() { + this.file = "main.tex"; + this.line = 42; + this.column = 5; + this.project_id = "mock-project-id"; + this.req.params = + {project_id: this.project_id}; + this.req.query = { + file: this.file, + line: this.line.toString(), + column: this.column.toString() + }; + this.res.json = sinon.stub(); - @CompileManager.syncFromCode = sinon.stub().callsArgWith(5, null, @pdfPositions = ["mock-positions"]) - @CompileController.syncFromCode @req, @res, @next + this.CompileManager.syncFromCode = sinon.stub().callsArgWith(5, null, (this.pdfPositions = ["mock-positions"])); + return this.CompileController.syncFromCode(this.req, this.res, this.next); + }); - it "should find the corresponding location in the PDF", -> - @CompileManager.syncFromCode - .calledWith(@project_id, undefined, @file, @line, @column) - .should.equal true + it("should find the corresponding location in the PDF", function() { + return this.CompileManager.syncFromCode + .calledWith(this.project_id, undefined, this.file, this.line, this.column) + .should.equal(true); + }); - it "should return the positions", -> - @res.json - .calledWith( - pdf: @pdfPositions - ) - .should.equal true + return it("should return the positions", function() { + return this.res.json + .calledWith({ + pdf: this.pdfPositions + }) + .should.equal(true); + }); + }); - describe "syncFromPdf", -> - beforeEach -> - @page = 5 - @h = 100.23 - @v = 45.67 - @project_id = "mock-project-id" - @req.params = - project_id: @project_id - @req.query = - page: @page.toString() - h: @h.toString() - v: @v.toString() - @res.json = sinon.stub() + describe("syncFromPdf", function() { + beforeEach(function() { + this.page = 5; + this.h = 100.23; + this.v = 45.67; + this.project_id = "mock-project-id"; + this.req.params = + {project_id: this.project_id}; + this.req.query = { + page: this.page.toString(), + h: this.h.toString(), + v: this.v.toString() + }; + this.res.json = sinon.stub(); - @CompileManager.syncFromPdf = sinon.stub().callsArgWith(5, null, @codePositions = ["mock-positions"]) - @CompileController.syncFromPdf @req, @res, @next + this.CompileManager.syncFromPdf = sinon.stub().callsArgWith(5, null, (this.codePositions = ["mock-positions"])); + return this.CompileController.syncFromPdf(this.req, this.res, this.next); + }); - it "should find the corresponding location in the code", -> - @CompileManager.syncFromPdf - .calledWith(@project_id, undefined, @page, @h, @v) - .should.equal true + it("should find the corresponding location in the code", function() { + return this.CompileManager.syncFromPdf + .calledWith(this.project_id, undefined, this.page, this.h, this.v) + .should.equal(true); + }); - it "should return the positions", -> - @res.json - .calledWith( - code: @codePositions - ) - .should.equal true + return it("should return the positions", function() { + return this.res.json + .calledWith({ + code: this.codePositions + }) + .should.equal(true); + }); + }); - describe "wordcount", -> - beforeEach -> - @file = "main.tex" - @project_id = "mock-project-id" - @req.params = - project_id: @project_id - @req.query = - file: @file - image: @image = "example.com/image" - @res.json = sinon.stub() + return describe("wordcount", function() { + beforeEach(function() { + this.file = "main.tex"; + this.project_id = "mock-project-id"; + this.req.params = + {project_id: this.project_id}; + this.req.query = { + file: this.file, + image: (this.image = "example.com/image") + }; + this.res.json = sinon.stub(); - @CompileManager.wordcount = sinon.stub().callsArgWith(4, null, @texcount = ["mock-texcount"]) - @CompileController.wordcount @req, @res, @next + this.CompileManager.wordcount = sinon.stub().callsArgWith(4, null, (this.texcount = ["mock-texcount"])); + return this.CompileController.wordcount(this.req, this.res, this.next); + }); - it "should return the word count of a file", -> - @CompileManager.wordcount - .calledWith(@project_id, undefined, @file, @image) - .should.equal true + it("should return the word count of a file", function() { + return this.CompileManager.wordcount + .calledWith(this.project_id, undefined, this.file, this.image) + .should.equal(true); + }); - it "should return the texcount info", -> - @res.json - .calledWith( - texcount: @texcount - ) - .should.equal true + return it("should return the texcount info", function() { + return this.res.json + .calledWith({ + texcount: this.texcount + }) + .should.equal(true); + }); + }); +}); diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.js b/services/clsi/test/unit/coffee/CompileManagerTests.js index c4b0f85941..5675ac155b 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.js +++ b/services/clsi/test/unit/coffee/CompileManagerTests.js @@ -1,356 +1,426 @@ -SandboxedModule = require('sandboxed-module') -sinon = require('sinon') -require('chai').should() -modulePath = require('path').join __dirname, '../../../app/js/CompileManager' -tk = require("timekeeper") -EventEmitter = require("events").EventEmitter -Path = require "path" +/* + * 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 SandboxedModule = require('sandboxed-module'); +const sinon = require('sinon'); +require('chai').should(); +const modulePath = require('path').join(__dirname, '../../../app/js/CompileManager'); +const tk = require("timekeeper"); +const { EventEmitter } = require("events"); +const Path = require("path"); -describe "CompileManager", -> - beforeEach -> - @CompileManager = SandboxedModule.require modulePath, requires: - "./LatexRunner": @LatexRunner = {} - "./ResourceWriter": @ResourceWriter = {} - "./OutputFileFinder": @OutputFileFinder = {} - "./OutputCacheManager": @OutputCacheManager = {} - "settings-sharelatex": @Settings = - path: +describe("CompileManager", function() { + beforeEach(function() { + this.CompileManager = SandboxedModule.require(modulePath, { requires: { + "./LatexRunner": (this.LatexRunner = {}), + "./ResourceWriter": (this.ResourceWriter = {}), + "./OutputFileFinder": (this.OutputFileFinder = {}), + "./OutputCacheManager": (this.OutputCacheManager = {}), + "settings-sharelatex": (this.Settings = { + path: { compilesDir: "/compiles/dir" - synctexBaseDir: -> "/compile" - clsi: - docker: + }, + synctexBaseDir() { return "/compile"; }, + clsi: { + docker: { image: "SOMEIMAGE" + } + } + }), - "logger-sharelatex": @logger = { log: sinon.stub() , info:->} - "child_process": @child_process = {} - "./CommandRunner": @CommandRunner = {} - "./DraftModeManager": @DraftModeManager = {} - "./TikzManager": @TikzManager = {} - "./LockManager": @LockManager = {} - "fs": @fs = {} - "fs-extra": @fse = { ensureDir: sinon.stub().callsArg(1) } - @callback = sinon.stub() - @project_id = "project-id-123" - @user_id = "1234" - describe "doCompileWithLock", -> - beforeEach -> - @request = - resources: @resources = "mock-resources" - project_id: @project_id - user_id: @user_id - @output_files = ["foo", "bar"] - @Settings.compileDir = "compiles" - @compileDir = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}" - @CompileManager.doCompile = sinon.stub().callsArgWith(1, null, @output_files) - @LockManager.runWithLock = (lockFile, runner, callback) -> - runner (err, result...) -> - callback(err, result...) + "logger-sharelatex": (this.logger = { log: sinon.stub() , info() {}}), + "child_process": (this.child_process = {}), + "./CommandRunner": (this.CommandRunner = {}), + "./DraftModeManager": (this.DraftModeManager = {}), + "./TikzManager": (this.TikzManager = {}), + "./LockManager": (this.LockManager = {}), + "fs": (this.fs = {}), + "fs-extra": (this.fse = { ensureDir: sinon.stub().callsArg(1) }) + } + }); + this.callback = sinon.stub(); + this.project_id = "project-id-123"; + return this.user_id = "1234"; + }); + describe("doCompileWithLock", function() { + beforeEach(function() { + this.request = { + resources: (this.resources = "mock-resources"), + project_id: this.project_id, + user_id: this.user_id + }; + this.output_files = ["foo", "bar"]; + this.Settings.compileDir = "compiles"; + this.compileDir = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`; + this.CompileManager.doCompile = sinon.stub().callsArgWith(1, null, this.output_files); + return this.LockManager.runWithLock = (lockFile, runner, callback) => + runner((err, ...result) => callback(err, ...Array.from(result))) + ; + }); - describe "when the project is not locked", -> - beforeEach -> - @CompileManager.doCompileWithLock @request, @callback + describe("when the project is not locked", function() { + beforeEach(function() { + return this.CompileManager.doCompileWithLock(this.request, this.callback); + }); - it "should ensure that the compile directory exists", -> - @fse.ensureDir.calledWith(@compileDir) - .should.equal true + it("should ensure that the compile directory exists", function() { + return this.fse.ensureDir.calledWith(this.compileDir) + .should.equal(true); + }); - it "should call doCompile with the request", -> - @CompileManager.doCompile - .calledWith(@request) - .should.equal true + it("should call doCompile with the request", function() { + return this.CompileManager.doCompile + .calledWith(this.request) + .should.equal(true); + }); - it "should call the callback with the output files", -> - @callback.calledWithExactly(null, @output_files) - .should.equal true + return it("should call the callback with the output files", function() { + return this.callback.calledWithExactly(null, this.output_files) + .should.equal(true); + }); + }); - describe "when the project is locked", -> - beforeEach -> - @error = new Error("locked") - @LockManager.runWithLock = (lockFile, runner, callback) => - callback(@error) - @CompileManager.doCompileWithLock @request, @callback + return describe("when the project is locked", function() { + beforeEach(function() { + this.error = new Error("locked"); + this.LockManager.runWithLock = (lockFile, runner, callback) => { + return callback(this.error); + }; + return this.CompileManager.doCompileWithLock(this.request, this.callback); + }); - it "should ensure that the compile directory exists", -> - @fse.ensureDir.calledWith(@compileDir) - .should.equal true + it("should ensure that the compile directory exists", function() { + return this.fse.ensureDir.calledWith(this.compileDir) + .should.equal(true); + }); - it "should not call doCompile with the request", -> - @CompileManager.doCompile - .called.should.equal false + it("should not call doCompile with the request", function() { + return this.CompileManager.doCompile + .called.should.equal(false); + }); - it "should call the callback with the error", -> - @callback.calledWithExactly(@error) - .should.equal true + return it("should call the callback with the error", function() { + return this.callback.calledWithExactly(this.error) + .should.equal(true); + }); + }); + }); - describe "doCompile", -> - beforeEach -> - @output_files = [{ - path: "output.log" + describe("doCompile", function() { + beforeEach(function() { + this.output_files = [{ + path: "output.log", type: "log" }, { - path: "output.pdf" + path: "output.pdf", type: "pdf" - }] - @build_files = [{ - path: "output.log" - type: "log" + }]; + this.build_files = [{ + path: "output.log", + type: "log", build: 1234 }, { - path: "output.pdf" - type: "pdf" + path: "output.pdf", + type: "pdf", build: 1234 - }] - @request = - resources: @resources = "mock-resources" - rootResourcePath: @rootResourcePath = "main.tex" - project_id: @project_id - user_id: @user_id - compiler: @compiler = "pdflatex" - timeout: @timeout = 42000 - imageName: @image = "example.com/image" - flags: @flags = ["-file-line-error"] - @env = {} - @Settings.compileDir = "compiles" - @compileDir = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}" - @ResourceWriter.syncResourcesToDisk = sinon.stub().callsArgWith(2, null, @resources) - @LatexRunner.runLatex = sinon.stub().callsArg(2) - @OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, @output_files) - @OutputCacheManager.saveOutputFiles = sinon.stub().callsArgWith(2, null, @build_files) - @DraftModeManager.injectDraftMode = sinon.stub().callsArg(1) - @TikzManager.checkMainFile = sinon.stub().callsArg(3, false) + }]; + this.request = { + resources: (this.resources = "mock-resources"), + rootResourcePath: (this.rootResourcePath = "main.tex"), + project_id: this.project_id, + user_id: this.user_id, + compiler: (this.compiler = "pdflatex"), + timeout: (this.timeout = 42000), + imageName: (this.image = "example.com/image"), + flags: (this.flags = ["-file-line-error"]) + }; + this.env = {}; + this.Settings.compileDir = "compiles"; + this.compileDir = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`; + this.ResourceWriter.syncResourcesToDisk = sinon.stub().callsArgWith(2, null, this.resources); + this.LatexRunner.runLatex = sinon.stub().callsArg(2); + this.OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, this.output_files); + this.OutputCacheManager.saveOutputFiles = sinon.stub().callsArgWith(2, null, this.build_files); + this.DraftModeManager.injectDraftMode = sinon.stub().callsArg(1); + return this.TikzManager.checkMainFile = sinon.stub().callsArg(3, false); + }); - describe "normally", -> - beforeEach -> - @CompileManager.doCompile @request, @callback + describe("normally", function() { + beforeEach(function() { + return this.CompileManager.doCompile(this.request, this.callback); + }); - it "should write the resources to disk", -> - @ResourceWriter.syncResourcesToDisk - .calledWith(@request, @compileDir) - .should.equal true + it("should write the resources to disk", function() { + return this.ResourceWriter.syncResourcesToDisk + .calledWith(this.request, this.compileDir) + .should.equal(true); + }); - it "should run LaTeX", -> - @LatexRunner.runLatex - .calledWith("#{@project_id}-#{@user_id}", { - directory: @compileDir - mainFile: @rootResourcePath - compiler: @compiler - timeout: @timeout - image: @image - flags: @flags - environment: @env + it("should run LaTeX", function() { + return this.LatexRunner.runLatex + .calledWith(`${this.project_id}-${this.user_id}`, { + directory: this.compileDir, + mainFile: this.rootResourcePath, + compiler: this.compiler, + timeout: this.timeout, + image: this.image, + flags: this.flags, + environment: this.env }) - .should.equal true + .should.equal(true); + }); - it "should find the output files", -> - @OutputFileFinder.findOutputFiles - .calledWith(@resources, @compileDir) - .should.equal true + it("should find the output files", function() { + return this.OutputFileFinder.findOutputFiles + .calledWith(this.resources, this.compileDir) + .should.equal(true); + }); - it "should return the output files", -> - @callback.calledWith(null, @build_files).should.equal true + it("should return the output files", function() { + return this.callback.calledWith(null, this.build_files).should.equal(true); + }); - it "should not inject draft mode by default", -> - @DraftModeManager.injectDraftMode.called.should.equal false + return it("should not inject draft mode by default", function() { + return this.DraftModeManager.injectDraftMode.called.should.equal(false); + }); + }); - describe "with draft mode", -> - beforeEach -> - @request.draft = true - @CompileManager.doCompile @request, @callback + describe("with draft mode", function() { + beforeEach(function() { + this.request.draft = true; + return this.CompileManager.doCompile(this.request, this.callback); + }); - it "should inject the draft mode header", -> - @DraftModeManager.injectDraftMode - .calledWith(@compileDir + "/" + @rootResourcePath) - .should.equal true + return it("should inject the draft mode header", function() { + return this.DraftModeManager.injectDraftMode + .calledWith(this.compileDir + "/" + this.rootResourcePath) + .should.equal(true); + }); + }); - describe "with a check option", -> - beforeEach -> - @request.check = "error" - @CompileManager.doCompile @request, @callback + describe("with a check option", function() { + beforeEach(function() { + this.request.check = "error"; + return this.CompileManager.doCompile(this.request, this.callback); + }); - it "should run chktex", -> - @LatexRunner.runLatex - .calledWith("#{@project_id}-#{@user_id}", { - directory: @compileDir - mainFile: @rootResourcePath - compiler: @compiler - timeout: @timeout - image: @image - flags: @flags + return it("should run chktex", function() { + return this.LatexRunner.runLatex + .calledWith(`${this.project_id}-${this.user_id}`, { + directory: this.compileDir, + mainFile: this.rootResourcePath, + compiler: this.compiler, + timeout: this.timeout, + image: this.image, + flags: this.flags, environment: {'CHKTEX_OPTIONS': '-nall -e9 -e10 -w15 -w16', 'CHKTEX_EXIT_ON_ERROR':1, 'CHKTEX_ULIMIT_OPTIONS': '-t 5 -v 64000'} }) - .should.equal true + .should.equal(true); + }); + }); - describe "with a knitr file and check options", -> - beforeEach -> - @request.rootResourcePath = "main.Rtex" - @request.check = "error" - @CompileManager.doCompile @request, @callback + return describe("with a knitr file and check options", function() { + beforeEach(function() { + this.request.rootResourcePath = "main.Rtex"; + this.request.check = "error"; + return this.CompileManager.doCompile(this.request, this.callback); + }); - it "should not run chktex", -> - @LatexRunner.runLatex - .calledWith("#{@project_id}-#{@user_id}", { - directory: @compileDir - mainFile: "main.Rtex" - compiler: @compiler - timeout: @timeout - image: @image - flags: @flags - environment: @env + return it("should not run chktex", function() { + return this.LatexRunner.runLatex + .calledWith(`${this.project_id}-${this.user_id}`, { + directory: this.compileDir, + mainFile: "main.Rtex", + compiler: this.compiler, + timeout: this.timeout, + image: this.image, + flags: this.flags, + environment: this.env }) - .should.equal true + .should.equal(true); + }); + }); + }); - describe "clearProject", -> - describe "succesfully", -> - beforeEach -> - @Settings.compileDir = "compiles" - @fs.lstat = sinon.stub().callsArgWith(1, null,{isDirectory: ()->true}) - @proc = new EventEmitter() - @proc.stdout = new EventEmitter() - @proc.stderr = new EventEmitter() - @child_process.spawn = sinon.stub().returns(@proc) - @CompileManager.clearProject @project_id, @user_id, @callback - @proc.emit "close", 0 + describe("clearProject", function() { + describe("succesfully", function() { + beforeEach(function() { + this.Settings.compileDir = "compiles"; + this.fs.lstat = sinon.stub().callsArgWith(1, null,{isDirectory(){ return true; }}); + this.proc = new EventEmitter(); + this.proc.stdout = new EventEmitter(); + this.proc.stderr = new EventEmitter(); + this.child_process.spawn = sinon.stub().returns(this.proc); + this.CompileManager.clearProject(this.project_id, this.user_id, this.callback); + return this.proc.emit("close", 0); + }); - it "should remove the project directory", -> - @child_process.spawn - .calledWith("rm", ["-r", "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}"]) - .should.equal true + it("should remove the project directory", function() { + return this.child_process.spawn + .calledWith("rm", ["-r", `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`]) + .should.equal(true); + }); - it "should call the callback", -> - @callback.called.should.equal true + return it("should call the callback", function() { + return this.callback.called.should.equal(true); + }); + }); - describe "with a non-success status code", -> - beforeEach -> - @Settings.compileDir = "compiles" - @fs.lstat = sinon.stub().callsArgWith(1, null,{isDirectory: ()->true}) - @proc = new EventEmitter() - @proc.stdout = new EventEmitter() - @proc.stderr = new EventEmitter() - @child_process.spawn = sinon.stub().returns(@proc) - @CompileManager.clearProject @project_id, @user_id, @callback - @proc.stderr.emit "data", @error = "oops" - @proc.emit "close", 1 + return describe("with a non-success status code", function() { + beforeEach(function() { + this.Settings.compileDir = "compiles"; + this.fs.lstat = sinon.stub().callsArgWith(1, null,{isDirectory(){ return true; }}); + this.proc = new EventEmitter(); + this.proc.stdout = new EventEmitter(); + this.proc.stderr = new EventEmitter(); + this.child_process.spawn = sinon.stub().returns(this.proc); + this.CompileManager.clearProject(this.project_id, this.user_id, this.callback); + this.proc.stderr.emit("data", (this.error = "oops")); + return this.proc.emit("close", 1); + }); - it "should remove the project directory", -> - @child_process.spawn - .calledWith("rm", ["-r", "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}"]) - .should.equal true + it("should remove the project directory", function() { + return this.child_process.spawn + .calledWith("rm", ["-r", `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`]) + .should.equal(true); + }); - it "should call the callback with an error from the stderr", -> - @callback + return it("should call the callback with an error from the stderr", function() { + this.callback .calledWith(new Error()) - .should.equal true + .should.equal(true); - @callback.args[0][0].message.should.equal "rm -r #{@Settings.path.compilesDir}/#{@project_id}-#{@user_id} failed: #{@error}" + return this.callback.args[0][0].message.should.equal(`rm -r ${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id} failed: ${this.error}`); + }); + }); + }); - describe "syncing", -> - beforeEach -> - @page = 1 - @h = 42.23 - @v = 87.56 - @width = 100.01 - @height = 234.56 - @line = 5 - @column = 3 - @file_name = "main.tex" - @child_process.execFile = sinon.stub() - @Settings.path.synctexBaseDir = (project_id) => "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}" + describe("syncing", function() { + beforeEach(function() { + this.page = 1; + this.h = 42.23; + this.v = 87.56; + this.width = 100.01; + this.height = 234.56; + this.line = 5; + this.column = 3; + this.file_name = "main.tex"; + this.child_process.execFile = sinon.stub(); + return this.Settings.path.synctexBaseDir = project_id => `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`; + }); - describe "syncFromCode", -> - beforeEach -> - @fs.stat = sinon.stub().callsArgWith(1, null,{isFile: ()->true}) - @stdout = "NODE\t#{@page}\t#{@h}\t#{@v}\t#{@width}\t#{@height}\n" - @CommandRunner.run = sinon.stub().callsArgWith(6, null, {stdout:@stdout}) - @CompileManager.syncFromCode @project_id, @user_id, @file_name, @line, @column, @callback + describe("syncFromCode", function() { + beforeEach(function() { + this.fs.stat = sinon.stub().callsArgWith(1, null,{isFile(){ return true; }}); + this.stdout = `NODE\t${this.page}\t${this.h}\t${this.v}\t${this.width}\t${this.height}\n`; + this.CommandRunner.run = sinon.stub().callsArgWith(6, null, {stdout:this.stdout}); + return this.CompileManager.syncFromCode(this.project_id, this.user_id, this.file_name, this.line, this.column, this.callback); + }); - it "should execute the synctex binary", -> - bin_path = Path.resolve(__dirname + "/../../../bin/synctex") - synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/output.pdf" - file_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/#{@file_name}" - @CommandRunner.run + it("should execute the synctex binary", function() { + const bin_path = Path.resolve(__dirname + "/../../../bin/synctex"); + const synctex_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/output.pdf`; + const file_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/${this.file_name}`; + return this.CommandRunner.run .calledWith( - "#{@project_id}-#{@user_id}", - ['/opt/synctex', 'code', synctex_path, file_path, @line, @column], - "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}", - @Settings.clsi.docker.image, + `${this.project_id}-${this.user_id}`, + ['/opt/synctex', 'code', synctex_path, file_path, this.line, this.column], + `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, + this.Settings.clsi.docker.image, 60000, {} - ).should.equal true + ).should.equal(true); + }); - it "should call the callback with the parsed output", -> - @callback + return it("should call the callback with the parsed output", function() { + return this.callback .calledWith(null, [{ - page: @page - h: @h - v: @v - height: @height - width: @width + page: this.page, + h: this.h, + v: this.v, + height: this.height, + width: this.width }]) - .should.equal true + .should.equal(true); + }); + }); - describe "syncFromPdf", -> - beforeEach -> - @fs.stat = sinon.stub().callsArgWith(1, null,{isFile: ()->true}) - @stdout = "NODE\t#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/#{@file_name}\t#{@line}\t#{@column}\n" - @CommandRunner.run = sinon.stub().callsArgWith(6, null, {stdout:@stdout}) - @CompileManager.syncFromPdf @project_id, @user_id, @page, @h, @v, @callback + return describe("syncFromPdf", function() { + beforeEach(function() { + this.fs.stat = sinon.stub().callsArgWith(1, null,{isFile(){ return true; }}); + this.stdout = `NODE\t${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/${this.file_name}\t${this.line}\t${this.column}\n`; + this.CommandRunner.run = sinon.stub().callsArgWith(6, null, {stdout:this.stdout}); + return this.CompileManager.syncFromPdf(this.project_id, this.user_id, this.page, this.h, this.v, this.callback); + }); - it "should execute the synctex binary", -> - bin_path = Path.resolve(__dirname + "/../../../bin/synctex") - synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/output.pdf" - @CommandRunner.run + it("should execute the synctex binary", function() { + const bin_path = Path.resolve(__dirname + "/../../../bin/synctex"); + const synctex_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/output.pdf`; + return this.CommandRunner.run .calledWith( - "#{@project_id}-#{@user_id}", - ['/opt/synctex', "pdf", synctex_path, @page, @h, @v], - "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}", - @Settings.clsi.docker.image, + `${this.project_id}-${this.user_id}`, + ['/opt/synctex', "pdf", synctex_path, this.page, this.h, this.v], + `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, + this.Settings.clsi.docker.image, 60000, - {}).should.equal true + {}).should.equal(true); + }); - it "should call the callback with the parsed output", -> - @callback + return it("should call the callback with the parsed output", function() { + return this.callback .calledWith(null, [{ - file: @file_name - line: @line - column: @column + file: this.file_name, + line: this.line, + column: this.column }]) - .should.equal true + .should.equal(true); + }); + }); + }); - describe "wordcount", -> - beforeEach -> - @CommandRunner.run = sinon.stub().callsArg(6) - @fs.readFile = sinon.stub().callsArgWith(2, null, @stdout = "Encoding: ascii\nWords in text: 2") - @callback = sinon.stub() + return describe("wordcount", function() { + beforeEach(function() { + this.CommandRunner.run = sinon.stub().callsArg(6); + this.fs.readFile = sinon.stub().callsArgWith(2, null, (this.stdout = "Encoding: ascii\nWords in text: 2")); + this.callback = sinon.stub(); - @project_id - @timeout = 60 * 1000 - @file_name = "main.tex" - @Settings.path.compilesDir = "/local/compile/directory" - @image = "example.com/image" + this.project_id; + this.timeout = 60 * 1000; + this.file_name = "main.tex"; + this.Settings.path.compilesDir = "/local/compile/directory"; + this.image = "example.com/image"; - @CompileManager.wordcount @project_id, @user_id, @file_name, @image, @callback + return this.CompileManager.wordcount(this.project_id, this.user_id, this.file_name, this.image, this.callback); + }); - it "should run the texcount command", -> - @directory = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}" - @file_path = "$COMPILE_DIR/#{@file_name}" - @command =[ "texcount", "-nocol", "-inc", @file_path, "-out=" + @file_path + ".wc"] + it("should run the texcount command", function() { + this.directory = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`; + this.file_path = `$COMPILE_DIR/${this.file_name}`; + this.command =[ "texcount", "-nocol", "-inc", this.file_path, `-out=${this.file_path}.wc`]; - @CommandRunner.run - .calledWith("#{@project_id}-#{@user_id}", @command, @directory, @image, @timeout, {}) - .should.equal true + return this.CommandRunner.run + .calledWith(`${this.project_id}-${this.user_id}`, this.command, this.directory, this.image, this.timeout, {}) + .should.equal(true); + }); - it "should call the callback with the parsed output", -> - @callback + return it("should call the callback with the parsed output", function() { + return this.callback .calledWith(null, { - encode: "ascii" - textWords: 2 - headWords: 0 - outside: 0 - headers: 0 - elements: 0 - mathInline: 0 - mathDisplay: 0 - errors: 0 + encode: "ascii", + textWords: 2, + headWords: 0, + outside: 0, + headers: 0, + elements: 0, + mathInline: 0, + mathDisplay: 0, + errors: 0, messages: "" }) - .should.equal true + .should.equal(true); + }); + }); +}); diff --git a/services/clsi/test/unit/coffee/ContentTypeMapperTests.js b/services/clsi/test/unit/coffee/ContentTypeMapperTests.js index 2439120b16..64a6091453 100644 --- a/services/clsi/test/unit/coffee/ContentTypeMapperTests.js +++ b/services/clsi/test/unit/coffee/ContentTypeMapperTests.js @@ -1,55 +1,75 @@ -SandboxedModule = require('sandboxed-module') -sinon = require('sinon') -require('chai').should() -modulePath = require('path').join __dirname, '../../../app/js/ContentTypeMapper' +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module'); +const sinon = require('sinon'); +require('chai').should(); +const modulePath = require('path').join(__dirname, '../../../app/js/ContentTypeMapper'); -describe 'ContentTypeMapper', -> +describe('ContentTypeMapper', function() { - beforeEach -> - @ContentTypeMapper = SandboxedModule.require modulePath + beforeEach(function() { + return this.ContentTypeMapper = SandboxedModule.require(modulePath); + }); - describe 'map', -> + return describe('map', function() { - it 'should map .txt to text/plain', -> - content_type = @ContentTypeMapper.map('example.txt') - content_type.should.equal 'text/plain' + it('should map .txt to text/plain', function() { + const content_type = this.ContentTypeMapper.map('example.txt'); + return content_type.should.equal('text/plain'); + }); - it 'should map .csv to text/csv', -> - content_type = @ContentTypeMapper.map('example.csv') - content_type.should.equal 'text/csv' + it('should map .csv to text/csv', function() { + const content_type = this.ContentTypeMapper.map('example.csv'); + return content_type.should.equal('text/csv'); + }); - it 'should map .pdf to application/pdf', -> - content_type = @ContentTypeMapper.map('example.pdf') - content_type.should.equal 'application/pdf' + it('should map .pdf to application/pdf', function() { + const content_type = this.ContentTypeMapper.map('example.pdf'); + return content_type.should.equal('application/pdf'); + }); - it 'should fall back to octet-stream', -> - content_type = @ContentTypeMapper.map('example.unknown') - content_type.should.equal 'application/octet-stream' + it('should fall back to octet-stream', function() { + const content_type = this.ContentTypeMapper.map('example.unknown'); + return content_type.should.equal('application/octet-stream'); + }); - describe 'coercing web files to plain text', -> + describe('coercing web files to plain text', function() { - it 'should map .js to plain text', -> - content_type = @ContentTypeMapper.map('example.js') - content_type.should.equal 'text/plain' + it('should map .js to plain text', function() { + const content_type = this.ContentTypeMapper.map('example.js'); + return content_type.should.equal('text/plain'); + }); - it 'should map .html to plain text', -> - content_type = @ContentTypeMapper.map('example.html') - content_type.should.equal 'text/plain' + it('should map .html to plain text', function() { + const content_type = this.ContentTypeMapper.map('example.html'); + return content_type.should.equal('text/plain'); + }); - it 'should map .css to plain text', -> - content_type = @ContentTypeMapper.map('example.css') - content_type.should.equal 'text/plain' + return it('should map .css to plain text', function() { + const content_type = this.ContentTypeMapper.map('example.css'); + return content_type.should.equal('text/plain'); + }); + }); - describe 'image files', -> + return describe('image files', function() { - it 'should map .png to image/png', -> - content_type = @ContentTypeMapper.map('example.png') - content_type.should.equal 'image/png' + it('should map .png to image/png', function() { + const content_type = this.ContentTypeMapper.map('example.png'); + return content_type.should.equal('image/png'); + }); - it 'should map .jpeg to image/jpeg', -> - content_type = @ContentTypeMapper.map('example.jpeg') - content_type.should.equal 'image/jpeg' + it('should map .jpeg to image/jpeg', function() { + const content_type = this.ContentTypeMapper.map('example.jpeg'); + return content_type.should.equal('image/jpeg'); + }); - it 'should map .svg to text/plain to protect against XSS (SVG can execute JS)', -> - content_type = @ContentTypeMapper.map('example.svg') - content_type.should.equal 'text/plain' + return it('should map .svg to text/plain to protect against XSS (SVG can execute JS)', function() { + const content_type = this.ContentTypeMapper.map('example.svg'); + return content_type.should.equal('text/plain'); + }); + }); + }); +}); diff --git a/services/clsi/test/unit/coffee/DockerLockManagerTests.js b/services/clsi/test/unit/coffee/DockerLockManagerTests.js index 6161bec36d..5ef3ca2c8e 100644 --- a/services/clsi/test/unit/coffee/DockerLockManagerTests.js +++ b/services/clsi/test/unit/coffee/DockerLockManagerTests.js @@ -1,145 +1,188 @@ -SandboxedModule = require('sandboxed-module') -sinon = require('sinon') -require('chai').should() -require "coffee-script" -modulePath = require('path').join __dirname, '../../../app/coffee/DockerLockManager' +/* + * 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 SandboxedModule = require('sandboxed-module'); +const sinon = require('sinon'); +require('chai').should(); +require("coffee-script"); +const modulePath = require('path').join(__dirname, '../../../app/coffee/DockerLockManager'); -describe "LockManager", -> - beforeEach -> - @LockManager = SandboxedModule.require modulePath, requires: - "settings-sharelatex": @Settings = - clsi: docker: {} - "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } +describe("LockManager", function() { + beforeEach(function() { + return this.LockManager = SandboxedModule.require(modulePath, { requires: { + "settings-sharelatex": (this.Settings = + {clsi: {docker: {}}}), + "logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub() }) + } + });}); - describe "runWithLock", -> - describe "with a single lock", -> - beforeEach (done) -> - @callback = sinon.stub() - @LockManager.runWithLock "lock-one", (releaseLock) -> - setTimeout () -> - releaseLock(null, "hello", "world") - , 100 - , (err, args...) => - @callback(err,args...) - done() + return describe("runWithLock", function() { + describe("with a single lock", function() { + beforeEach(function(done) { + this.callback = sinon.stub(); + return this.LockManager.runWithLock("lock-one", releaseLock => + setTimeout(() => releaseLock(null, "hello", "world") + , 100) + + , (err, ...args) => { + this.callback(err,...Array.from(args)); + return done(); + }); + }); - it "should call the callback", -> - @callback.calledWith(null,"hello","world").should.equal true + return it("should call the callback", function() { + return this.callback.calledWith(null,"hello","world").should.equal(true); + }); + }); - describe "with two locks", -> - beforeEach (done) -> - @callback1 = sinon.stub() - @callback2 = sinon.stub() - @LockManager.runWithLock "lock-one", (releaseLock) -> - setTimeout () -> - releaseLock(null, "hello", "world","one") - , 100 - , (err, args...) => - @callback1(err,args...) - @LockManager.runWithLock "lock-two", (releaseLock) -> - setTimeout () -> - releaseLock(null, "hello", "world","two") - , 200 - , (err, args...) => - @callback2(err,args...) - done() + describe("with two locks", function() { + beforeEach(function(done) { + this.callback1 = sinon.stub(); + this.callback2 = sinon.stub(); + this.LockManager.runWithLock("lock-one", releaseLock => + setTimeout(() => releaseLock(null, "hello", "world","one") + , 100) + + , (err, ...args) => { + return this.callback1(err,...Array.from(args)); + }); + return this.LockManager.runWithLock("lock-two", releaseLock => + setTimeout(() => releaseLock(null, "hello", "world","two") + , 200) + + , (err, ...args) => { + this.callback2(err,...Array.from(args)); + return done(); + }); + }); - it "should call the first callback", -> - @callback1.calledWith(null,"hello","world","one").should.equal true + it("should call the first callback", function() { + return this.callback1.calledWith(null,"hello","world","one").should.equal(true); + }); - it "should call the second callback", -> - @callback2.calledWith(null,"hello","world","two").should.equal true + return it("should call the second callback", function() { + return this.callback2.calledWith(null,"hello","world","two").should.equal(true); + }); + }); - describe "with lock contention", -> - describe "where the first lock is released quickly", -> - beforeEach (done) -> - @LockManager.MAX_LOCK_WAIT_TIME = 1000 - @LockManager.LOCK_TEST_INTERVAL = 100 - @callback1 = sinon.stub() - @callback2 = sinon.stub() - @LockManager.runWithLock "lock", (releaseLock) -> - setTimeout () -> - releaseLock(null, "hello", "world","one") - , 100 - , (err, args...) => - @callback1(err,args...) - @LockManager.runWithLock "lock", (releaseLock) -> - setTimeout () -> - releaseLock(null, "hello", "world","two") - , 200 - , (err, args...) => - @callback2(err,args...) - done() + return describe("with lock contention", function() { + describe("where the first lock is released quickly", function() { + beforeEach(function(done) { + this.LockManager.MAX_LOCK_WAIT_TIME = 1000; + this.LockManager.LOCK_TEST_INTERVAL = 100; + this.callback1 = sinon.stub(); + this.callback2 = sinon.stub(); + this.LockManager.runWithLock("lock", releaseLock => + setTimeout(() => releaseLock(null, "hello", "world","one") + , 100) + + , (err, ...args) => { + return this.callback1(err,...Array.from(args)); + }); + return this.LockManager.runWithLock("lock", releaseLock => + setTimeout(() => releaseLock(null, "hello", "world","two") + , 200) + + , (err, ...args) => { + this.callback2(err,...Array.from(args)); + return done(); + }); + }); - it "should call the first callback", -> - @callback1.calledWith(null,"hello","world","one").should.equal true + it("should call the first callback", function() { + return this.callback1.calledWith(null,"hello","world","one").should.equal(true); + }); - it "should call the second callback", -> - @callback2.calledWith(null,"hello","world","two").should.equal true + return it("should call the second callback", function() { + return this.callback2.calledWith(null,"hello","world","two").should.equal(true); + }); + }); - describe "where the first lock is held longer than the waiting time", -> - beforeEach (done) -> - @LockManager.MAX_LOCK_HOLD_TIME = 10000 - @LockManager.MAX_LOCK_WAIT_TIME = 1000 - @LockManager.LOCK_TEST_INTERVAL = 100 - @callback1 = sinon.stub() - @callback2 = sinon.stub() - doneOne = doneTwo = false - finish = (key) -> - doneOne = true if key is 1 - doneTwo = true if key is 2 - done() if doneOne and doneTwo - @LockManager.runWithLock "lock", (releaseLock) -> - setTimeout () -> - releaseLock(null, "hello", "world","one") - , 1100 - , (err, args...) => - @callback1(err,args...) - finish(1) - @LockManager.runWithLock "lock", (releaseLock) -> - setTimeout () -> - releaseLock(null, "hello", "world","two") - , 100 - , (err, args...) => - @callback2(err,args...) - finish(2) + describe("where the first lock is held longer than the waiting time", function() { + beforeEach(function(done) { + let doneTwo; + this.LockManager.MAX_LOCK_HOLD_TIME = 10000; + this.LockManager.MAX_LOCK_WAIT_TIME = 1000; + this.LockManager.LOCK_TEST_INTERVAL = 100; + this.callback1 = sinon.stub(); + this.callback2 = sinon.stub(); + let doneOne = (doneTwo = false); + const finish = function(key) { + if (key === 1) { doneOne = true; } + if (key === 2) { doneTwo = true; } + if (doneOne && doneTwo) { return done(); } + }; + this.LockManager.runWithLock("lock", releaseLock => + setTimeout(() => releaseLock(null, "hello", "world","one") + , 1100) + + , (err, ...args) => { + this.callback1(err,...Array.from(args)); + return finish(1); + }); + return this.LockManager.runWithLock("lock", releaseLock => + setTimeout(() => releaseLock(null, "hello", "world","two") + , 100) + + , (err, ...args) => { + this.callback2(err,...Array.from(args)); + return finish(2); + }); + }); - it "should call the first callback", -> - @callback1.calledWith(null,"hello","world","one").should.equal true + it("should call the first callback", function() { + return this.callback1.calledWith(null,"hello","world","one").should.equal(true); + }); - it "should call the second callback with an error", -> - error = sinon.match.instanceOf Error - @callback2.calledWith(error).should.equal true + return it("should call the second callback with an error", function() { + const error = sinon.match.instanceOf(Error); + return this.callback2.calledWith(error).should.equal(true); + }); + }); - describe "where the first lock is held longer than the max holding time", -> - beforeEach (done) -> - @LockManager.MAX_LOCK_HOLD_TIME = 1000 - @LockManager.MAX_LOCK_WAIT_TIME = 2000 - @LockManager.LOCK_TEST_INTERVAL = 100 - @callback1 = sinon.stub() - @callback2 = sinon.stub() - doneOne = doneTwo = false - finish = (key) -> - doneOne = true if key is 1 - doneTwo = true if key is 2 - done() if doneOne and doneTwo - @LockManager.runWithLock "lock", (releaseLock) -> - setTimeout () -> - releaseLock(null, "hello", "world","one") - , 1500 - , (err, args...) => - @callback1(err,args...) - finish(1) - @LockManager.runWithLock "lock", (releaseLock) -> - setTimeout () -> - releaseLock(null, "hello", "world","two") - , 100 - , (err, args...) => - @callback2(err,args...) - finish(2) + return describe("where the first lock is held longer than the max holding time", function() { + beforeEach(function(done) { + let doneTwo; + this.LockManager.MAX_LOCK_HOLD_TIME = 1000; + this.LockManager.MAX_LOCK_WAIT_TIME = 2000; + this.LockManager.LOCK_TEST_INTERVAL = 100; + this.callback1 = sinon.stub(); + this.callback2 = sinon.stub(); + let doneOne = (doneTwo = false); + const finish = function(key) { + if (key === 1) { doneOne = true; } + if (key === 2) { doneTwo = true; } + if (doneOne && doneTwo) { return done(); } + }; + this.LockManager.runWithLock("lock", releaseLock => + setTimeout(() => releaseLock(null, "hello", "world","one") + , 1500) + + , (err, ...args) => { + this.callback1(err,...Array.from(args)); + return finish(1); + }); + return this.LockManager.runWithLock("lock", releaseLock => + setTimeout(() => releaseLock(null, "hello", "world","two") + , 100) + + , (err, ...args) => { + this.callback2(err,...Array.from(args)); + return finish(2); + }); + }); - it "should call the first callback", -> - @callback1.calledWith(null,"hello","world","one").should.equal true + it("should call the first callback", function() { + return this.callback1.calledWith(null,"hello","world","one").should.equal(true); + }); - it "should call the second callback", -> - @callback2.calledWith(null,"hello","world","two").should.equal true + return it("should call the second callback", function() { + return this.callback2.calledWith(null,"hello","world","two").should.equal(true); + }); + }); + }); + }); +}); diff --git a/services/clsi/test/unit/coffee/DockerRunnerTests.js b/services/clsi/test/unit/coffee/DockerRunnerTests.js index 307ffde18a..79ac5df1e5 100644 --- a/services/clsi/test/unit/coffee/DockerRunnerTests.js +++ b/services/clsi/test/unit/coffee/DockerRunnerTests.js @@ -1,509 +1,656 @@ -SandboxedModule = require('sandboxed-module') -sinon = require('sinon') -require('chai').should() -expect = require('chai').expect -require "coffee-script" -modulePath = require('path').join __dirname, '../../../app/coffee/DockerRunner' -Path = require "path" +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS206: Consider reworking classes to avoid initClass + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module'); +const sinon = require('sinon'); +require('chai').should(); +const { expect } = require('chai'); +require("coffee-script"); +const modulePath = require('path').join(__dirname, '../../../app/coffee/DockerRunner'); +const Path = require("path"); -describe "DockerRunner", -> - beforeEach -> - @container = container = {} - @DockerRunner = SandboxedModule.require modulePath, requires: - "settings-sharelatex": @Settings = - clsi: docker: {} +describe("DockerRunner", function() { + beforeEach(function() { + let container, Docker, Timer; + this.container = (container = {}); + this.DockerRunner = SandboxedModule.require(modulePath, { requires: { + "settings-sharelatex": (this.Settings = { + clsi: { docker: {} + }, path: {} - "logger-sharelatex": @logger = { + }), + "logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub(), info: sinon.stub(), warn: sinon.stub() + }), + "dockerode": (Docker = (function() { + Docker = class Docker { + static initClass() { + this.prototype.getContainer = sinon.stub().returns(container); + this.prototype.createContainer = sinon.stub().yields(null, container); + this.prototype.listContainers = sinon.stub(); + } + }; + Docker.initClass(); + return Docker; + })()), + "fs": (this.fs = { stat: sinon.stub().yields(null,{isDirectory(){ return true; }}) }), + "./Metrics": { + Timer: (Timer = class Timer { + done() {} + }) + }, + "./LockManager": { + runWithLock(key, runner, callback) { return runner(callback); } } - "dockerode": class Docker - getContainer: sinon.stub().returns(container) - createContainer: sinon.stub().yields(null, container) - listContainers: sinon.stub() - "fs": @fs = { stat: sinon.stub().yields(null,{isDirectory:()->true}) } - "./Metrics": - Timer: class Timer - done: () -> - "./LockManager": - runWithLock: (key, runner, callback) -> runner(callback) - @Docker = Docker - @getContainer = Docker::getContainer - @createContainer = Docker::createContainer - @listContainers = Docker::listContainers + } + } + ); + this.Docker = Docker; + this.getContainer = Docker.prototype.getContainer; + this.createContainer = Docker.prototype.createContainer; + this.listContainers = Docker.prototype.listContainers; - @directory = "/local/compile/directory" - @mainFile = "main-file.tex" - @compiler = "pdflatex" - @image = "example.com/sharelatex/image:2016.2" - @env = {} - @callback = sinon.stub() - @project_id = "project-id-123" - @volumes = - "/local/compile/directory": "/compile" - @Settings.clsi.docker.image = @defaultImage = "default-image" - @Settings.clsi.docker.env = PATH: "mock-path" + this.directory = "/local/compile/directory"; + this.mainFile = "main-file.tex"; + this.compiler = "pdflatex"; + this.image = "example.com/sharelatex/image:2016.2"; + this.env = {}; + this.callback = sinon.stub(); + this.project_id = "project-id-123"; + this.volumes = + {"/local/compile/directory": "/compile"}; + this.Settings.clsi.docker.image = (this.defaultImage = "default-image"); + return this.Settings.clsi.docker.env = {PATH: "mock-path"}; + }); - describe "run", -> - beforeEach (done)-> - @DockerRunner._getContainerOptions = sinon.stub().returns(@options = {mockoptions: "foo"}) - @DockerRunner._fingerprintContainer = sinon.stub().returns(@fingerprint = "fingerprint") + describe("run", function() { + beforeEach(function(done){ + this.DockerRunner._getContainerOptions = sinon.stub().returns(this.options = {mockoptions: "foo"}); + this.DockerRunner._fingerprintContainer = sinon.stub().returns(this.fingerprint = "fingerprint"); - @name = "project-#{@project_id}-#{@fingerprint}" + this.name = `project-${this.project_id}-${this.fingerprint}`; - @command = ["mock", "command", "--outdir=$COMPILE_DIR"] - @command_with_dir = ["mock", "command", "--outdir=/compile"] - @timeout = 42000 - done() + this.command = ["mock", "command", "--outdir=$COMPILE_DIR"]; + this.command_with_dir = ["mock", "command", "--outdir=/compile"]; + this.timeout = 42000; + return done(); + }); - describe "successfully", -> - beforeEach (done)-> - @DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, @output = "mock-output") - @DockerRunner.run @project_id, @command, @directory, @image, @timeout, @env, (err, output)=> - @callback(err, output) - done() + describe("successfully", function() { + beforeEach(function(done){ + this.DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, (this.output = "mock-output")); + return this.DockerRunner.run(this.project_id, this.command, this.directory, this.image, this.timeout, this.env, (err, output)=> { + this.callback(err, output); + return done(); + }); + }); - it "should generate the options for the container", -> - @DockerRunner._getContainerOptions - .calledWith(@command_with_dir, @image, @volumes, @timeout) - .should.equal true + it("should generate the options for the container", function() { + return this.DockerRunner._getContainerOptions + .calledWith(this.command_with_dir, this.image, this.volumes, this.timeout) + .should.equal(true); + }); - it "should generate the fingerprint from the returned options", -> - @DockerRunner._fingerprintContainer - .calledWith(@options) - .should.equal true + it("should generate the fingerprint from the returned options", function() { + return this.DockerRunner._fingerprintContainer + .calledWith(this.options) + .should.equal(true); + }); - it "should do the run", -> - @DockerRunner._runAndWaitForContainer - .calledWith(@options, @volumes, @timeout) - .should.equal true + it("should do the run", function() { + return this.DockerRunner._runAndWaitForContainer + .calledWith(this.options, this.volumes, this.timeout) + .should.equal(true); + }); - it "should call the callback", -> - @callback.calledWith(null, @output).should.equal true + return it("should call the callback", function() { + return this.callback.calledWith(null, this.output).should.equal(true); + }); + }); - describe 'when path.sandboxedCompilesHostDir is set', -> + describe('when path.sandboxedCompilesHostDir is set', function() { - beforeEach -> - @Settings.path.sandboxedCompilesHostDir = '/some/host/dir/compiles' - @directory = '/var/lib/sharelatex/data/compiles/xyz' - @DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, @output = "mock-output") - @DockerRunner.run @project_id, @command, @directory, @image, @timeout, @env, @callback + beforeEach(function() { + this.Settings.path.sandboxedCompilesHostDir = '/some/host/dir/compiles'; + this.directory = '/var/lib/sharelatex/data/compiles/xyz'; + this.DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, (this.output = "mock-output")); + return this.DockerRunner.run(this.project_id, this.command, this.directory, this.image, this.timeout, this.env, this.callback); + }); - it 'should re-write the bind directory', -> - volumes = @DockerRunner._runAndWaitForContainer.lastCall.args[1] - expect(volumes).to.deep.equal { + it('should re-write the bind directory', function() { + const volumes = this.DockerRunner._runAndWaitForContainer.lastCall.args[1]; + return expect(volumes).to.deep.equal({ '/some/host/dir/compiles/xyz': '/compile' - } + }); + }); - it "should call the callback", -> - @callback.calledWith(null, @output).should.equal true + return it("should call the callback", function() { + return this.callback.calledWith(null, this.output).should.equal(true); + }); + }); - describe "when the run throws an error", -> - beforeEach -> - firstTime = true - @output = "mock-output" - @DockerRunner._runAndWaitForContainer = (options, volumes, timeout, callback = (error, output)->) => - if firstTime - firstTime = false - callback new Error("HTTP code is 500 which indicates error: server error") - else - callback(null, @output) - sinon.spy @DockerRunner, "_runAndWaitForContainer" - @DockerRunner.destroyContainer = sinon.stub().callsArg(3) - @DockerRunner.run @project_id, @command, @directory, @image, @timeout, @env, @callback + describe("when the run throws an error", function() { + beforeEach(function() { + let firstTime = true; + this.output = "mock-output"; + this.DockerRunner._runAndWaitForContainer = (options, volumes, timeout, callback) => { + if (callback == null) { callback = function(error, output){}; } + if (firstTime) { + firstTime = false; + return callback(new Error("HTTP code is 500 which indicates error: server error")); + } else { + return callback(null, this.output); + } + }; + sinon.spy(this.DockerRunner, "_runAndWaitForContainer"); + this.DockerRunner.destroyContainer = sinon.stub().callsArg(3); + return this.DockerRunner.run(this.project_id, this.command, this.directory, this.image, this.timeout, this.env, this.callback); + }); - it "should do the run twice", -> - @DockerRunner._runAndWaitForContainer - .calledTwice.should.equal true + it("should do the run twice", function() { + return this.DockerRunner._runAndWaitForContainer + .calledTwice.should.equal(true); + }); - it "should destroy the container in between", -> - @DockerRunner.destroyContainer - .calledWith(@name, null) - .should.equal true + it("should destroy the container in between", function() { + return this.DockerRunner.destroyContainer + .calledWith(this.name, null) + .should.equal(true); + }); - it "should call the callback", -> - @callback.calledWith(null, @output).should.equal true + return it("should call the callback", function() { + return this.callback.calledWith(null, this.output).should.equal(true); + }); + }); - describe "with no image", -> - beforeEach -> - @DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, @output = "mock-output") - @DockerRunner.run @project_id, @command, @directory, null, @timeout, @env, @callback + describe("with no image", function() { + beforeEach(function() { + this.DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, (this.output = "mock-output")); + return this.DockerRunner.run(this.project_id, this.command, this.directory, null, this.timeout, this.env, this.callback); + }); - it "should use the default image", -> - @DockerRunner._getContainerOptions - .calledWith(@command_with_dir, @defaultImage, @volumes, @timeout) - .should.equal true + return it("should use the default image", function() { + return this.DockerRunner._getContainerOptions + .calledWith(this.command_with_dir, this.defaultImage, this.volumes, this.timeout) + .should.equal(true); + }); + }); - describe "with image override", -> - beforeEach -> - @Settings.texliveImageNameOveride = "overrideimage.com/something" - @DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, @output = "mock-output") - @DockerRunner.run @project_id, @command, @directory, @image, @timeout, @env, @callback + return describe("with image override", function() { + beforeEach(function() { + this.Settings.texliveImageNameOveride = "overrideimage.com/something"; + this.DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, (this.output = "mock-output")); + return this.DockerRunner.run(this.project_id, this.command, this.directory, this.image, this.timeout, this.env, this.callback); + }); - it "should use the override and keep the tag", -> - image = @DockerRunner._getContainerOptions.args[0][1] - image.should.equal "overrideimage.com/something/image:2016.2" + return it("should use the override and keep the tag", function() { + const image = this.DockerRunner._getContainerOptions.args[0][1]; + return image.should.equal("overrideimage.com/something/image:2016.2"); + }); + }); + }); - describe "_runAndWaitForContainer", -> - beforeEach -> - @options = {mockoptions: "foo", name: @name = "mock-name"} - @DockerRunner.startContainer = (options, volumes, attachStreamHandler, callback) => - attachStreamHandler(null, @output = "mock-output") - callback(null, @containerId = "container-id") - sinon.spy @DockerRunner, "startContainer" - @DockerRunner.waitForContainer = sinon.stub().callsArgWith(2, null, @exitCode = 42) - @DockerRunner._runAndWaitForContainer @options, @volumes, @timeout, @callback + describe("_runAndWaitForContainer", function() { + beforeEach(function() { + this.options = {mockoptions: "foo", name: (this.name = "mock-name")}; + this.DockerRunner.startContainer = (options, volumes, attachStreamHandler, callback) => { + attachStreamHandler(null, (this.output = "mock-output")); + return callback(null, (this.containerId = "container-id")); + }; + sinon.spy(this.DockerRunner, "startContainer"); + this.DockerRunner.waitForContainer = sinon.stub().callsArgWith(2, null, (this.exitCode = 42)); + return this.DockerRunner._runAndWaitForContainer(this.options, this.volumes, this.timeout, this.callback); + }); - it "should create/start the container", -> - @DockerRunner.startContainer - .calledWith(@options, @volumes) - .should.equal true + it("should create/start the container", function() { + return this.DockerRunner.startContainer + .calledWith(this.options, this.volumes) + .should.equal(true); + }); - it "should wait for the container to finish", -> - @DockerRunner.waitForContainer - .calledWith(@name, @timeout) - .should.equal true + it("should wait for the container to finish", function() { + return this.DockerRunner.waitForContainer + .calledWith(this.name, this.timeout) + .should.equal(true); + }); - it "should call the callback with the output", -> - @callback.calledWith(null, @output).should.equal true + return it("should call the callback with the output", function() { + return this.callback.calledWith(null, this.output).should.equal(true); + }); + }); - describe "startContainer", -> - beforeEach -> - @attachStreamHandler = sinon.stub() - @attachStreamHandler.cock = true - @options = {mockoptions: "foo", name: "mock-name"} - @container.inspect = sinon.stub().callsArgWith(0) - @DockerRunner.attachToContainer = (containerId, attachStreamHandler, cb)=> - attachStreamHandler() - cb() - sinon.spy @DockerRunner, "attachToContainer" + describe("startContainer", function() { + beforeEach(function() { + this.attachStreamHandler = sinon.stub(); + this.attachStreamHandler.cock = true; + this.options = {mockoptions: "foo", name: "mock-name"}; + this.container.inspect = sinon.stub().callsArgWith(0); + this.DockerRunner.attachToContainer = (containerId, attachStreamHandler, cb)=> { + attachStreamHandler(); + return cb(); + }; + return sinon.spy(this.DockerRunner, "attachToContainer"); + }); - describe "when the container exists", -> - beforeEach -> - @container.inspect = sinon.stub().callsArgWith(0) - @container.start = sinon.stub().yields() + describe("when the container exists", function() { + beforeEach(function() { + this.container.inspect = sinon.stub().callsArgWith(0); + this.container.start = sinon.stub().yields(); - @DockerRunner.startContainer @options, @volumes, @callback, -> + return this.DockerRunner.startContainer(this.options, this.volumes, this.callback, function() {}); + }); - it "should start the container with the given name", -> - @getContainer - .calledWith(@options.name) - .should.equal true - @container.start + it("should start the container with the given name", function() { + this.getContainer + .calledWith(this.options.name) + .should.equal(true); + return this.container.start .called - .should.equal true + .should.equal(true); + }); - it "should not try to create the container", -> - @createContainer.called.should.equal false + it("should not try to create the container", function() { + return this.createContainer.called.should.equal(false); + }); - it "should attach to the container", -> - @DockerRunner.attachToContainer.called.should.equal true + it("should attach to the container", function() { + return this.DockerRunner.attachToContainer.called.should.equal(true); + }); - it "should call the callback", -> - @callback.called.should.equal true + it("should call the callback", function() { + return this.callback.called.should.equal(true); + }); - it "should attach before the container starts", -> - sinon.assert.callOrder(@DockerRunner.attachToContainer, @container.start) + return it("should attach before the container starts", function() { + return sinon.assert.callOrder(this.DockerRunner.attachToContainer, this.container.start); + }); + }); - describe "when the container does not exist", -> - beforeEach ()-> - exists = false - @container.start = sinon.stub().yields() - @container.inspect = sinon.stub().callsArgWith(0, {statusCode:404}) - @DockerRunner.startContainer @options, @volumes, @attachStreamHandler, @callback + describe("when the container does not exist", function() { + beforeEach(function(){ + const exists = false; + this.container.start = sinon.stub().yields(); + this.container.inspect = sinon.stub().callsArgWith(0, {statusCode:404}); + return this.DockerRunner.startContainer(this.options, this.volumes, this.attachStreamHandler, this.callback); + }); - it "should create the container", -> - @createContainer - .calledWith(@options) - .should.equal true + it("should create the container", function() { + return this.createContainer + .calledWith(this.options) + .should.equal(true); + }); - it "should call the callback and stream handler", -> - @attachStreamHandler.called.should.equal true - @callback.called.should.equal true + it("should call the callback and stream handler", function() { + this.attachStreamHandler.called.should.equal(true); + return this.callback.called.should.equal(true); + }); - it "should attach to the container", -> - @DockerRunner.attachToContainer.called.should.equal true + it("should attach to the container", function() { + return this.DockerRunner.attachToContainer.called.should.equal(true); + }); - it "should attach before the container starts", -> - sinon.assert.callOrder(@DockerRunner.attachToContainer, @container.start) + return it("should attach before the container starts", function() { + return sinon.assert.callOrder(this.DockerRunner.attachToContainer, this.container.start); + }); + }); - describe "when the container is already running", -> - beforeEach -> - error = new Error("HTTP code is 304 which indicates error: server error - start: Cannot start container #{@name}: The container MOCKID is already running.") - error.statusCode = 304 - @container.start = sinon.stub().yields(error) - @container.inspect = sinon.stub().callsArgWith(0) - @DockerRunner.startContainer @options, @volumes, @attachStreamHandler, @callback + describe("when the container is already running", function() { + beforeEach(function() { + const error = new Error(`HTTP code is 304 which indicates error: server error - start: Cannot start container ${this.name}: The container MOCKID is already running.`); + error.statusCode = 304; + this.container.start = sinon.stub().yields(error); + this.container.inspect = sinon.stub().callsArgWith(0); + return this.DockerRunner.startContainer(this.options, this.volumes, this.attachStreamHandler, this.callback); + }); - it "should not try to create the container", -> - @createContainer.called.should.equal false + it("should not try to create the container", function() { + return this.createContainer.called.should.equal(false); + }); - it "should call the callback and stream handler without an error", -> - @attachStreamHandler.called.should.equal true - @callback.called.should.equal true + return it("should call the callback and stream handler without an error", function() { + this.attachStreamHandler.called.should.equal(true); + return this.callback.called.should.equal(true); + }); + }); - describe "when a volume does not exist", -> - beforeEach ()-> - @fs.stat = sinon.stub().yields(new Error("no such path")) - @DockerRunner.startContainer @options, @volumes, @attachStreamHandler, @callback + describe("when a volume does not exist", function() { + beforeEach(function(){ + this.fs.stat = sinon.stub().yields(new Error("no such path")); + return this.DockerRunner.startContainer(this.options, this.volumes, this.attachStreamHandler, this.callback); + }); - it "should not try to create the container", -> - @createContainer.called.should.equal false + it("should not try to create the container", function() { + return this.createContainer.called.should.equal(false); + }); - it "should call the callback with an error", -> - @callback.calledWith(new Error()).should.equal true + return it("should call the callback with an error", function() { + return this.callback.calledWith(new Error()).should.equal(true); + }); + }); - describe "when a volume exists but is not a directory", -> - beforeEach -> - @fs.stat = sinon.stub().yields(null, {isDirectory: () -> return false}) - @DockerRunner.startContainer @options, @volumes, @attachStreamHandler, @callback + describe("when a volume exists but is not a directory", function() { + beforeEach(function() { + this.fs.stat = sinon.stub().yields(null, {isDirectory() { return false; }}); + return this.DockerRunner.startContainer(this.options, this.volumes, this.attachStreamHandler, this.callback); + }); - it "should not try to create the container", -> - @createContainer.called.should.equal false + it("should not try to create the container", function() { + return this.createContainer.called.should.equal(false); + }); - it "should call the callback with an error", -> - @callback.calledWith(new Error()).should.equal true + return it("should call the callback with an error", function() { + return this.callback.calledWith(new Error()).should.equal(true); + }); + }); - describe "when a volume does not exist, but sibling-containers are used", -> - beforeEach -> - @fs.stat = sinon.stub().yields(new Error("no such path")) - @Settings.path.sandboxedCompilesHostDir = '/some/path' - @container.start = sinon.stub().yields() - @DockerRunner.startContainer @options, @volumes, @callback + describe("when a volume does not exist, but sibling-containers are used", function() { + beforeEach(function() { + this.fs.stat = sinon.stub().yields(new Error("no such path")); + this.Settings.path.sandboxedCompilesHostDir = '/some/path'; + this.container.start = sinon.stub().yields(); + return this.DockerRunner.startContainer(this.options, this.volumes, this.callback); + }); - afterEach -> - delete @Settings.path.sandboxedCompilesHostDir + afterEach(function() { + return delete this.Settings.path.sandboxedCompilesHostDir; + }); - it "should start the container with the given name", -> - @getContainer - .calledWith(@options.name) - .should.equal true - @container.start + it("should start the container with the given name", function() { + this.getContainer + .calledWith(this.options.name) + .should.equal(true); + return this.container.start .called - .should.equal true + .should.equal(true); + }); - it "should not try to create the container", -> - @createContainer.called.should.equal false + it("should not try to create the container", function() { + return this.createContainer.called.should.equal(false); + }); - it "should call the callback", -> - @callback.called.should.equal true - @callback.calledWith(new Error()).should.equal false + return it("should call the callback", function() { + this.callback.called.should.equal(true); + return this.callback.calledWith(new Error()).should.equal(false); + }); + }); - describe "when the container tries to be created, but already has been (race condition)", -> + return describe("when the container tries to be created, but already has been (race condition)", function() {}); + }); - describe "waitForContainer", -> - beforeEach -> - @containerId = "container-id" - @timeout = 5000 - @container.wait = sinon.stub().yields(null, StatusCode: @statusCode = 42) - @container.kill = sinon.stub().yields() + describe("waitForContainer", function() { + beforeEach(function() { + this.containerId = "container-id"; + this.timeout = 5000; + this.container.wait = sinon.stub().yields(null, {StatusCode: (this.statusCode = 42)}); + return this.container.kill = sinon.stub().yields(); + }); - describe "when the container returns in time", -> - beforeEach -> - @DockerRunner.waitForContainer @containerId, @timeout, @callback + describe("when the container returns in time", function() { + beforeEach(function() { + return this.DockerRunner.waitForContainer(this.containerId, this.timeout, this.callback); + }); - it "should wait for the container", -> - @getContainer - .calledWith(@containerId) - .should.equal true - @container.wait + it("should wait for the container", function() { + this.getContainer + .calledWith(this.containerId) + .should.equal(true); + return this.container.wait .called - .should.equal true + .should.equal(true); + }); - it "should call the callback with the exit", -> - @callback - .calledWith(null, @statusCode) - .should.equal true + return it("should call the callback with the exit", function() { + return this.callback + .calledWith(null, this.statusCode) + .should.equal(true); + }); + }); - describe "when the container does not return before the timeout", -> - beforeEach (done) -> - @container.wait = (callback = (error, exitCode) ->) -> - setTimeout () -> - callback(null, StatusCode: 42) - , 100 - @timeout = 5 - @DockerRunner.waitForContainer @containerId, @timeout, (args...) => - @callback(args...) - done() + return describe("when the container does not return before the timeout", function() { + beforeEach(function(done) { + this.container.wait = function(callback) { + if (callback == null) { callback = function(error, exitCode) {}; } + return setTimeout(() => callback(null, {StatusCode: 42}) + , 100); + }; + this.timeout = 5; + return this.DockerRunner.waitForContainer(this.containerId, this.timeout, (...args) => { + this.callback(...Array.from(args || [])); + return done(); + }); + }); - it "should call kill on the container", -> - @getContainer - .calledWith(@containerId) - .should.equal true - @container.kill + it("should call kill on the container", function() { + this.getContainer + .calledWith(this.containerId) + .should.equal(true); + return this.container.kill .called - .should.equal true + .should.equal(true); + }); - it "should call the callback with an error", -> - error = new Error("container timed out") - error.timedout = true - @callback + return it("should call the callback with an error", function() { + const error = new Error("container timed out"); + error.timedout = true; + return this.callback .calledWith(error) - .should.equal true + .should.equal(true); + }); + }); + }); - describe "destroyOldContainers", -> - beforeEach (done) -> - oneHourInSeconds = 60 * 60 - oneHourInMilliseconds = oneHourInSeconds * 1000 - nowInSeconds = Date.now()/1000 - @containers = [{ - Name: "/project-old-container-name" - Id: "old-container-id" + describe("destroyOldContainers", function() { + beforeEach(function(done) { + const oneHourInSeconds = 60 * 60; + const oneHourInMilliseconds = oneHourInSeconds * 1000; + const nowInSeconds = Date.now()/1000; + this.containers = [{ + Name: "/project-old-container-name", + Id: "old-container-id", Created: nowInSeconds - oneHourInSeconds - 100 }, { - Name: "/project-new-container-name" - Id: "new-container-id" - Created: nowInSeconds - oneHourInSeconds + 100 + Name: "/project-new-container-name", + Id: "new-container-id", + Created: (nowInSeconds - oneHourInSeconds) + 100 }, { - Name: "/totally-not-a-project-container" - Id: "some-random-id" + Name: "/totally-not-a-project-container", + Id: "some-random-id", Created: nowInSeconds - (2 * oneHourInSeconds ) - }] - @DockerRunner.MAX_CONTAINER_AGE = oneHourInMilliseconds - @listContainers.callsArgWith(1, null, @containers) - @DockerRunner.destroyContainer = sinon.stub().callsArg(3) - @DockerRunner.destroyOldContainers (error) => - @callback(error) - done() + }]; + this.DockerRunner.MAX_CONTAINER_AGE = oneHourInMilliseconds; + this.listContainers.callsArgWith(1, null, this.containers); + this.DockerRunner.destroyContainer = sinon.stub().callsArg(3); + return this.DockerRunner.destroyOldContainers(error => { + this.callback(error); + return done(); + }); + }); - it "should list all containers", -> - @listContainers - .calledWith(all: true) - .should.equal true + it("should list all containers", function() { + return this.listContainers + .calledWith({all: true}) + .should.equal(true); + }); - it "should destroy old containers", -> - @DockerRunner.destroyContainer + it("should destroy old containers", function() { + this.DockerRunner.destroyContainer .callCount - .should.equal 1 - @DockerRunner.destroyContainer + .should.equal(1); + return this.DockerRunner.destroyContainer .calledWith("/project-old-container-name", "old-container-id") - .should.equal true + .should.equal(true); + }); - it "should not destroy new containers", -> - @DockerRunner.destroyContainer + it("should not destroy new containers", function() { + return this.DockerRunner.destroyContainer .calledWith("/project-new-container-name", "new-container-id") - .should.equal false + .should.equal(false); + }); - it "should not destroy non-project containers", -> - @DockerRunner.destroyContainer + it("should not destroy non-project containers", function() { + return this.DockerRunner.destroyContainer .calledWith("/totally-not-a-project-container", "some-random-id") - .should.equal false + .should.equal(false); + }); - it "should callback the callback", -> - @callback.called.should.equal true + return it("should callback the callback", function() { + return this.callback.called.should.equal(true); + }); + }); - describe '_destroyContainer', -> - beforeEach -> - @containerId = 'some_id' - @fakeContainer = - remove: sinon.stub().callsArgWith(1, null) - @Docker::getContainer = sinon.stub().returns(@fakeContainer) + describe('_destroyContainer', function() { + beforeEach(function() { + this.containerId = 'some_id'; + this.fakeContainer = + {remove: sinon.stub().callsArgWith(1, null)}; + return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer); + }); - it 'should get the container', (done) -> - @DockerRunner._destroyContainer @containerId, false, (err) => - @Docker::getContainer.callCount.should.equal 1 - @Docker::getContainer.calledWith(@containerId).should.equal true - done() + it('should get the container', function(done) { + return this.DockerRunner._destroyContainer(this.containerId, false, err => { + this.Docker.prototype.getContainer.callCount.should.equal(1); + this.Docker.prototype.getContainer.calledWith(this.containerId).should.equal(true); + return done(); + }); + }); - it 'should try to force-destroy the container when shouldForce=true', (done) -> - @DockerRunner._destroyContainer @containerId, true, (err) => - @fakeContainer.remove.callCount.should.equal 1 - @fakeContainer.remove.calledWith({force: true}).should.equal true - done() + it('should try to force-destroy the container when shouldForce=true', function(done) { + return this.DockerRunner._destroyContainer(this.containerId, true, err => { + this.fakeContainer.remove.callCount.should.equal(1); + this.fakeContainer.remove.calledWith({force: true}).should.equal(true); + return done(); + }); + }); - it 'should not try to force-destroy the container when shouldForce=false', (done) -> - @DockerRunner._destroyContainer @containerId, false, (err) => - @fakeContainer.remove.callCount.should.equal 1 - @fakeContainer.remove.calledWith({force: false}).should.equal true - done() + it('should not try to force-destroy the container when shouldForce=false', function(done) { + return this.DockerRunner._destroyContainer(this.containerId, false, err => { + this.fakeContainer.remove.callCount.should.equal(1); + this.fakeContainer.remove.calledWith({force: false}).should.equal(true); + return done(); + }); + }); - it 'should not produce an error', (done) -> - @DockerRunner._destroyContainer @containerId, false, (err) => - expect(err).to.equal null - done() + it('should not produce an error', function(done) { + return this.DockerRunner._destroyContainer(this.containerId, false, err => { + expect(err).to.equal(null); + return done(); + }); + }); - describe 'when the container is already gone', -> - beforeEach -> - @fakeError = new Error('woops') - @fakeError.statusCode = 404 - @fakeContainer = - remove: sinon.stub().callsArgWith(1, @fakeError) - @Docker::getContainer = sinon.stub().returns(@fakeContainer) + describe('when the container is already gone', function() { + beforeEach(function() { + this.fakeError = new Error('woops'); + this.fakeError.statusCode = 404; + this.fakeContainer = + {remove: sinon.stub().callsArgWith(1, this.fakeError)}; + return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer); + }); - it 'should not produce an error', (done) -> - @DockerRunner._destroyContainer @containerId, false, (err) => - expect(err).to.equal null - done() + return it('should not produce an error', function(done) { + return this.DockerRunner._destroyContainer(this.containerId, false, err => { + expect(err).to.equal(null); + return done(); + }); + }); + }); - describe 'when container.destroy produces an error', (done) -> - beforeEach -> - @fakeError = new Error('woops') - @fakeError.statusCode = 500 - @fakeContainer = - remove: sinon.stub().callsArgWith(1, @fakeError) - @Docker::getContainer = sinon.stub().returns(@fakeContainer) + return describe('when container.destroy produces an error', function(done) { + beforeEach(function() { + this.fakeError = new Error('woops'); + this.fakeError.statusCode = 500; + this.fakeContainer = + {remove: sinon.stub().callsArgWith(1, this.fakeError)}; + return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer); + }); - it 'should produce an error', (done) -> - @DockerRunner._destroyContainer @containerId, false, (err) => - expect(err).to.not.equal null - expect(err).to.equal @fakeError - done() + return it('should produce an error', function(done) { + return this.DockerRunner._destroyContainer(this.containerId, false, err => { + expect(err).to.not.equal(null); + expect(err).to.equal(this.fakeError); + return done(); + }); + }); + }); + }); - describe 'kill', -> - beforeEach -> - @containerId = 'some_id' - @fakeContainer = - kill: sinon.stub().callsArgWith(0, null) - @Docker::getContainer = sinon.stub().returns(@fakeContainer) + return describe('kill', function() { + beforeEach(function() { + this.containerId = 'some_id'; + this.fakeContainer = + {kill: sinon.stub().callsArgWith(0, null)}; + return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer); + }); - it 'should get the container', (done) -> - @DockerRunner.kill @containerId, (err) => - @Docker::getContainer.callCount.should.equal 1 - @Docker::getContainer.calledWith(@containerId).should.equal true - done() + it('should get the container', function(done) { + return this.DockerRunner.kill(this.containerId, err => { + this.Docker.prototype.getContainer.callCount.should.equal(1); + this.Docker.prototype.getContainer.calledWith(this.containerId).should.equal(true); + return done(); + }); + }); - it 'should try to force-destroy the container', (done) -> - @DockerRunner.kill @containerId, (err) => - @fakeContainer.kill.callCount.should.equal 1 - done() + it('should try to force-destroy the container', function(done) { + return this.DockerRunner.kill(this.containerId, err => { + this.fakeContainer.kill.callCount.should.equal(1); + return done(); + }); + }); - it 'should not produce an error', (done) -> - @DockerRunner.kill @containerId, (err) => - expect(err).to.equal undefined - done() + it('should not produce an error', function(done) { + return this.DockerRunner.kill(this.containerId, err => { + expect(err).to.equal(undefined); + return done(); + }); + }); - describe 'when the container is not actually running', -> - beforeEach -> - @fakeError = new Error('woops') - @fakeError.statusCode = 500 - @fakeError.message = 'Cannot kill container <whatever> is not running' - @fakeContainer = - kill: sinon.stub().callsArgWith(0, @fakeError) - @Docker::getContainer = sinon.stub().returns(@fakeContainer) + describe('when the container is not actually running', function() { + beforeEach(function() { + this.fakeError = new Error('woops'); + this.fakeError.statusCode = 500; + this.fakeError.message = 'Cannot kill container <whatever> is not running'; + this.fakeContainer = + {kill: sinon.stub().callsArgWith(0, this.fakeError)}; + return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer); + }); - it 'should not produce an error', (done) -> - @DockerRunner.kill @containerId, (err) => - expect(err).to.equal undefined - done() + return it('should not produce an error', function(done) { + return this.DockerRunner.kill(this.containerId, err => { + expect(err).to.equal(undefined); + return done(); + }); + }); + }); - describe 'when container.kill produces a legitimate error', (done) -> - beforeEach -> - @fakeError = new Error('woops') - @fakeError.statusCode = 500 - @fakeError.message = 'Totally legitimate reason to throw an error' - @fakeContainer = - kill: sinon.stub().callsArgWith(0, @fakeError) - @Docker::getContainer = sinon.stub().returns(@fakeContainer) + return describe('when container.kill produces a legitimate error', function(done) { + beforeEach(function() { + this.fakeError = new Error('woops'); + this.fakeError.statusCode = 500; + this.fakeError.message = 'Totally legitimate reason to throw an error'; + this.fakeContainer = + {kill: sinon.stub().callsArgWith(0, this.fakeError)}; + return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer); + }); - it 'should produce an error', (done) -> - @DockerRunner.kill @containerId, (err) => - expect(err).to.not.equal undefined - expect(err).to.equal @fakeError - done() + return it('should produce an error', function(done) { + return this.DockerRunner.kill(this.containerId, err => { + expect(err).to.not.equal(undefined); + expect(err).to.equal(this.fakeError); + return done(); + }); + }); + }); + }); +}); diff --git a/services/clsi/test/unit/coffee/DraftModeManagerTests.js b/services/clsi/test/unit/coffee/DraftModeManagerTests.js index 549be29310..ffea05087c 100644 --- a/services/clsi/test/unit/coffee/DraftModeManagerTests.js +++ b/services/clsi/test/unit/coffee/DraftModeManagerTests.js @@ -1,61 +1,77 @@ -SandboxedModule = require('sandboxed-module') -sinon = require('sinon') -require('chai').should() -modulePath = require('path').join __dirname, '../../../app/js/DraftModeManager' +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module'); +const sinon = require('sinon'); +require('chai').should(); +const modulePath = require('path').join(__dirname, '../../../app/js/DraftModeManager'); -describe 'DraftModeManager', -> - beforeEach -> - @DraftModeManager = SandboxedModule.require modulePath, requires: - "fs": @fs = {} - "logger-sharelatex": @logger = {log: () ->} +describe('DraftModeManager', function() { + beforeEach(function() { + return this.DraftModeManager = SandboxedModule.require(modulePath, { requires: { + "fs": (this.fs = {}), + "logger-sharelatex": (this.logger = {log() {}}) + } + });}); - describe "_injectDraftOption", -> - it "should add draft option into documentclass with existing options", -> - @DraftModeManager - ._injectDraftOption(''' - \\documentclass[a4paper,foo=bar]{article} - ''') - .should.equal(''' - \\documentclass[draft,a4paper,foo=bar]{article} - ''') + describe("_injectDraftOption", function() { + it("should add draft option into documentclass with existing options", function() { + return this.DraftModeManager + ._injectDraftOption(`\ +\\documentclass[a4paper,foo=bar]{article}\ +`) + .should.equal(`\ +\\documentclass[draft,a4paper,foo=bar]{article}\ +`); + }); - it "should add draft option into documentclass with no options", -> - @DraftModeManager - ._injectDraftOption(''' - \\documentclass{article} - ''') - .should.equal(''' - \\documentclass[draft]{article} - ''') + return it("should add draft option into documentclass with no options", function() { + return this.DraftModeManager + ._injectDraftOption(`\ +\\documentclass{article}\ +`) + .should.equal(`\ +\\documentclass[draft]{article}\ +`); + }); + }); - describe "injectDraftMode", -> - beforeEach -> - @filename = "/mock/filename.tex" - @callback = sinon.stub() - content = ''' - \\documentclass{article} - \\begin{document} - Hello world - \\end{document} - ''' - @fs.readFile = sinon.stub().callsArgWith(2, null, content) - @fs.writeFile = sinon.stub().callsArg(2) - @DraftModeManager.injectDraftMode @filename, @callback + return describe("injectDraftMode", function() { + beforeEach(function() { + this.filename = "/mock/filename.tex"; + this.callback = sinon.stub(); + const content = `\ +\\documentclass{article} +\\begin{document} +Hello world +\\end{document}\ +`; + this.fs.readFile = sinon.stub().callsArgWith(2, null, content); + this.fs.writeFile = sinon.stub().callsArg(2); + return this.DraftModeManager.injectDraftMode(this.filename, this.callback); + }); - it "should read the file", -> - @fs.readFile - .calledWith(@filename, "utf8") - .should.equal true + it("should read the file", function() { + return this.fs.readFile + .calledWith(this.filename, "utf8") + .should.equal(true); + }); - it "should write the modified file", -> - @fs.writeFile - .calledWith(@filename, """ - \\documentclass[draft]{article} - \\begin{document} - Hello world - \\end{document} - """) - .should.equal true + it("should write the modified file", function() { + return this.fs.writeFile + .calledWith(this.filename, `\ +\\documentclass[draft]{article} +\\begin{document} +Hello world +\\end{document}\ +`) + .should.equal(true); + }); - it "should call the callback", -> - @callback.called.should.equal true + return it("should call the callback", function() { + return this.callback.called.should.equal(true); + }); + }); +}); diff --git a/services/clsi/test/unit/coffee/LatexRunnerTests.js b/services/clsi/test/unit/coffee/LatexRunnerTests.js index 77c6edbcc0..5cb4d0666d 100644 --- a/services/clsi/test/unit/coffee/LatexRunnerTests.js +++ b/services/clsi/test/unit/coffee/LatexRunnerTests.js @@ -1,79 +1,105 @@ -SandboxedModule = require('sandboxed-module') -sinon = require('sinon') -require('chai').should() -modulePath = require('path').join __dirname, '../../../app/js/LatexRunner' -Path = require "path" +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module'); +const sinon = require('sinon'); +require('chai').should(); +const modulePath = require('path').join(__dirname, '../../../app/js/LatexRunner'); +const Path = require("path"); -describe "LatexRunner", -> - beforeEach -> - @LatexRunner = SandboxedModule.require modulePath, requires: - "settings-sharelatex": @Settings = - docker: +describe("LatexRunner", function() { + beforeEach(function() { + let Timer; + this.LatexRunner = SandboxedModule.require(modulePath, { requires: { + "settings-sharelatex": (this.Settings = { + docker: { socketPath: "/var/run/docker.sock" - "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } - "./Metrics": - Timer: class Timer - done: () -> - "./CommandRunner": @CommandRunner = {} + } + }), + "logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub() }), + "./Metrics": { + Timer: (Timer = class Timer { + done() {} + }) + }, + "./CommandRunner": (this.CommandRunner = {}) + } + }); - @directory = "/local/compile/directory" - @mainFile = "main-file.tex" - @compiler = "pdflatex" - @image = "example.com/image" - @callback = sinon.stub() - @project_id = "project-id-123" - @env = {'foo': '123'} + this.directory = "/local/compile/directory"; + this.mainFile = "main-file.tex"; + this.compiler = "pdflatex"; + this.image = "example.com/image"; + this.callback = sinon.stub(); + this.project_id = "project-id-123"; + return this.env = {'foo': '123'};}); - describe "runLatex", -> - beforeEach -> - @CommandRunner.run = sinon.stub().callsArg(6) + return describe("runLatex", function() { + beforeEach(function() { + return this.CommandRunner.run = sinon.stub().callsArg(6); + }); - describe "normally", -> - beforeEach -> - @LatexRunner.runLatex @project_id, - directory: @directory - mainFile: @mainFile - compiler: @compiler - timeout: @timeout = 42000 - image: @image - environment: @env - @callback + describe("normally", function() { + beforeEach(function() { + return this.LatexRunner.runLatex(this.project_id, { + directory: this.directory, + mainFile: this.mainFile, + compiler: this.compiler, + timeout: (this.timeout = 42000), + image: this.image, + environment: this.env + }, + this.callback); + }); - it "should run the latex command", -> - @CommandRunner.run - .calledWith(@project_id, sinon.match.any, @directory, @image, @timeout, @env) - .should.equal true + return it("should run the latex command", function() { + return this.CommandRunner.run + .calledWith(this.project_id, sinon.match.any, this.directory, this.image, this.timeout, this.env) + .should.equal(true); + }); + }); - describe "with an .Rtex main file", -> - beforeEach -> - @LatexRunner.runLatex @project_id, - directory: @directory - mainFile: "main-file.Rtex" - compiler: @compiler - image: @image - timeout: @timeout = 42000 - @callback + describe("with an .Rtex main file", function() { + beforeEach(function() { + return this.LatexRunner.runLatex(this.project_id, { + directory: this.directory, + mainFile: "main-file.Rtex", + compiler: this.compiler, + image: this.image, + timeout: (this.timeout = 42000) + }, + this.callback); + }); - it "should run the latex command on the equivalent .tex file", -> - command = @CommandRunner.run.args[0][1] - mainFile = command.slice(-1)[0] - mainFile.should.equal "$COMPILE_DIR/main-file.tex" + return it("should run the latex command on the equivalent .tex file", function() { + const command = this.CommandRunner.run.args[0][1]; + const mainFile = command.slice(-1)[0]; + return mainFile.should.equal("$COMPILE_DIR/main-file.tex"); + }); + }); - describe "with a flags option", -> - beforeEach -> - @LatexRunner.runLatex @project_id, - directory: @directory - mainFile: @mainFile - compiler: @compiler - image: @image - timeout: @timeout = 42000 + return describe("with a flags option", function() { + beforeEach(function() { + return this.LatexRunner.runLatex(this.project_id, { + directory: this.directory, + mainFile: this.mainFile, + compiler: this.compiler, + image: this.image, + timeout: (this.timeout = 42000), flags: ["-file-line-error", "-halt-on-error"] - @callback + }, + this.callback); + }); - it "should include the flags in the command", -> - command = @CommandRunner.run.args[0][1] - flags = command.filter (arg) -> - (arg == "-file-line-error") || (arg == "-halt-on-error") - flags.length.should.equal 2 - flags[0].should.equal "-file-line-error" - flags[1].should.equal "-halt-on-error" + return it("should include the flags in the command", function() { + const command = this.CommandRunner.run.args[0][1]; + const flags = command.filter(arg => (arg === "-file-line-error") || (arg === "-halt-on-error")); + flags.length.should.equal(2); + flags[0].should.equal("-file-line-error"); + return flags[1].should.equal("-halt-on-error"); + }); + }); + }); +}); diff --git a/services/clsi/test/unit/coffee/LockManagerTests.js b/services/clsi/test/unit/coffee/LockManagerTests.js index 9dd1d46cbb..d716a443c3 100644 --- a/services/clsi/test/unit/coffee/LockManagerTests.js +++ b/services/clsi/test/unit/coffee/LockManagerTests.js @@ -1,57 +1,77 @@ -SandboxedModule = require('sandboxed-module') -sinon = require('sinon') -require('chai').should() -modulePath = require('path').join __dirname, '../../../app/js/LockManager' -Path = require "path" -Errors = require "../../../app/js/Errors" +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module'); +const sinon = require('sinon'); +require('chai').should(); +const modulePath = require('path').join(__dirname, '../../../app/js/LockManager'); +const Path = require("path"); +const Errors = require("../../../app/js/Errors"); -describe "DockerLockManager", -> - beforeEach -> - @LockManager = SandboxedModule.require modulePath, requires: - "settings-sharelatex": {} - "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub(), err:-> } - "fs": - lstat:sinon.stub().callsArgWith(1) +describe("DockerLockManager", function() { + beforeEach(function() { + this.LockManager = SandboxedModule.require(modulePath, { requires: { + "settings-sharelatex": {}, + "logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub(), err() {} }), + "fs": { + lstat:sinon.stub().callsArgWith(1), readdir: sinon.stub().callsArgWith(1) - "lockfile": @Lockfile = {} - @lockFile = "/local/compile/directory/.project-lock" + }, + "lockfile": (this.Lockfile = {}) + } + }); + return this.lockFile = "/local/compile/directory/.project-lock"; + }); - describe "runWithLock", -> - beforeEach -> - @runner = sinon.stub().callsArgWith(0, null, "foo", "bar") - @callback = sinon.stub() + return describe("runWithLock", function() { + beforeEach(function() { + this.runner = sinon.stub().callsArgWith(0, null, "foo", "bar"); + return this.callback = sinon.stub(); + }); - describe "normally", -> - beforeEach -> - @Lockfile.lock = sinon.stub().callsArgWith(2, null) - @Lockfile.unlock = sinon.stub().callsArgWith(1, null) - @LockManager.runWithLock @lockFile, @runner, @callback + describe("normally", function() { + beforeEach(function() { + this.Lockfile.lock = sinon.stub().callsArgWith(2, null); + this.Lockfile.unlock = sinon.stub().callsArgWith(1, null); + return this.LockManager.runWithLock(this.lockFile, this.runner, this.callback); + }); - it "should run the compile", -> - @runner + it("should run the compile", function() { + return this.runner .calledWith() - .should.equal true + .should.equal(true); + }); - it "should call the callback with the response from the compile", -> - @callback + return it("should call the callback with the response from the compile", function() { + return this.callback .calledWithExactly(null, "foo", "bar") - .should.equal true + .should.equal(true); + }); + }); - describe "when the project is locked", -> - beforeEach -> - @error = new Error() - @error.code = "EEXIST" - @Lockfile.lock = sinon.stub().callsArgWith(2,@error) - @Lockfile.unlock = sinon.stub().callsArgWith(1, null) - @LockManager.runWithLock @lockFile, @runner, @callback + return describe("when the project is locked", function() { + beforeEach(function() { + this.error = new Error(); + this.error.code = "EEXIST"; + this.Lockfile.lock = sinon.stub().callsArgWith(2,this.error); + this.Lockfile.unlock = sinon.stub().callsArgWith(1, null); + return this.LockManager.runWithLock(this.lockFile, this.runner, this.callback); + }); - it "should not run the compile", -> - @runner + it("should not run the compile", function() { + return this.runner .called - .should.equal false + .should.equal(false); + }); - it "should return an error", -> - error = new Errors.AlreadyCompilingError() - @callback + return it("should return an error", function() { + const error = new Errors.AlreadyCompilingError(); + return this.callback .calledWithExactly(error) - .should.equal true + .should.equal(true); + }); + }); + }); +}); diff --git a/services/clsi/test/unit/coffee/OutputFileFinderTests.js b/services/clsi/test/unit/coffee/OutputFileFinderTests.js index 46d8c1fc42..3292d0a3bf 100644 --- a/services/clsi/test/unit/coffee/OutputFileFinderTests.js +++ b/services/clsi/test/unit/coffee/OutputFileFinderTests.js @@ -1,68 +1,92 @@ -SandboxedModule = require('sandboxed-module') -sinon = require('sinon') -require('chai').should() -modulePath = require('path').join __dirname, '../../../app/js/OutputFileFinder' -path = require "path" -expect = require("chai").expect -EventEmitter = require("events").EventEmitter +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module'); +const sinon = require('sinon'); +require('chai').should(); +const modulePath = require('path').join(__dirname, '../../../app/js/OutputFileFinder'); +const path = require("path"); +const { expect } = require("chai"); +const { EventEmitter } = require("events"); -describe "OutputFileFinder", -> - beforeEach -> - @OutputFileFinder = SandboxedModule.require modulePath, requires: - "fs": @fs = {} - "child_process": spawn: @spawn = sinon.stub() +describe("OutputFileFinder", function() { + beforeEach(function() { + this.OutputFileFinder = SandboxedModule.require(modulePath, { requires: { + "fs": (this.fs = {}), + "child_process": { spawn: (this.spawn = sinon.stub()) + }, "logger-sharelatex": { log: sinon.stub(), warn: sinon.stub() } - @directory = "/test/dir" - @callback = sinon.stub() + } + }); + this.directory = "/test/dir"; + return this.callback = sinon.stub(); + }); - describe "findOutputFiles", -> - beforeEach -> - @resource_path = "resource/path.tex" - @output_paths = ["output.pdf", "extra/file.tex"] - @all_paths = @output_paths.concat [@resource_path] - @resources = [ - path: @resource_path = "resource/path.tex" - ] - @OutputFileFinder._getAllFiles = sinon.stub().callsArgWith(1, null, @all_paths) - @OutputFileFinder.findOutputFiles @resources, @directory, (error, @outputFiles) => + describe("findOutputFiles", function() { + beforeEach(function() { + this.resource_path = "resource/path.tex"; + this.output_paths = ["output.pdf", "extra/file.tex"]; + this.all_paths = this.output_paths.concat([this.resource_path]); + this.resources = [ + {path: (this.resource_path = "resource/path.tex")} + ]; + this.OutputFileFinder._getAllFiles = sinon.stub().callsArgWith(1, null, this.all_paths); + return this.OutputFileFinder.findOutputFiles(this.resources, this.directory, (error, outputFiles) => { + this.outputFiles = outputFiles; + + }); + }); - it "should only return the output files, not directories or resource paths", -> - expect(@outputFiles).to.deep.equal [{ - path: "output.pdf" + return it("should only return the output files, not directories or resource paths", function() { + return expect(this.outputFiles).to.deep.equal([{ + path: "output.pdf", type: "pdf" }, { path: "extra/file.tex", type: "tex" - }] + }]); + }); +}); - describe "_getAllFiles", -> - beforeEach -> - @proc = new EventEmitter() - @proc.stdout = new EventEmitter() - @spawn.returns @proc - @directory = "/base/dir" - @OutputFileFinder._getAllFiles @directory, @callback + return describe("_getAllFiles", function() { + beforeEach(function() { + this.proc = new EventEmitter(); + this.proc.stdout = new EventEmitter(); + this.spawn.returns(this.proc); + this.directory = "/base/dir"; + return this.OutputFileFinder._getAllFiles(this.directory, this.callback); + }); - describe "successfully", -> - beforeEach -> - @proc.stdout.emit( + describe("successfully", function() { + beforeEach(function() { + this.proc.stdout.emit( "data", ["/base/dir/main.tex", "/base/dir/chapters/chapter1.tex"].join("\n") + "\n" - ) - @proc.emit "close", 0 + ); + return this.proc.emit("close", 0); + }); - it "should call the callback with the relative file paths", -> - @callback.calledWith( + return it("should call the callback with the relative file paths", function() { + return this.callback.calledWith( null, ["main.tex", "chapters/chapter1.tex"] - ).should.equal true + ).should.equal(true); + }); + }); - describe "when the directory doesn't exist", -> - beforeEach -> - @proc.emit "close", 1 + return describe("when the directory doesn't exist", function() { + beforeEach(function() { + return this.proc.emit("close", 1); + }); - it "should call the callback with a blank array", -> - @callback.calledWith( + return it("should call the callback with a blank array", function() { + return this.callback.calledWith( null, [] - ).should.equal true + ).should.equal(true); + }); + }); + }); +}); diff --git a/services/clsi/test/unit/coffee/OutputFileOptimiserTests.js b/services/clsi/test/unit/coffee/OutputFileOptimiserTests.js index 2988715569..8934c717e5 100644 --- a/services/clsi/test/unit/coffee/OutputFileOptimiserTests.js +++ b/services/clsi/test/unit/coffee/OutputFileOptimiserTests.js @@ -1,103 +1,141 @@ -SandboxedModule = require('sandboxed-module') -sinon = require('sinon') -require('chai').should() -modulePath = require('path').join __dirname, '../../../app/js/OutputFileOptimiser' -path = require "path" -expect = require("chai").expect -EventEmitter = require("events").EventEmitter +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module'); +const sinon = require('sinon'); +require('chai').should(); +const modulePath = require('path').join(__dirname, '../../../app/js/OutputFileOptimiser'); +const path = require("path"); +const { expect } = require("chai"); +const { EventEmitter } = require("events"); -describe "OutputFileOptimiser", -> - beforeEach -> - @OutputFileOptimiser = SandboxedModule.require modulePath, requires: - "fs": @fs = {} - "path": @Path = {} - "child_process": spawn: @spawn = sinon.stub() - "logger-sharelatex": { log: sinon.stub(), warn: sinon.stub() } +describe("OutputFileOptimiser", function() { + beforeEach(function() { + this.OutputFileOptimiser = SandboxedModule.require(modulePath, { requires: { + "fs": (this.fs = {}), + "path": (this.Path = {}), + "child_process": { spawn: (this.spawn = sinon.stub()) + }, + "logger-sharelatex": { log: sinon.stub(), warn: sinon.stub() }, "./Metrics" : {} - @directory = "/test/dir" - @callback = sinon.stub() + } + }); + this.directory = "/test/dir"; + return this.callback = sinon.stub(); + }); - describe "optimiseFile", -> - beforeEach -> - @src = "./output.pdf" - @dst = "./output.pdf" + describe("optimiseFile", function() { + beforeEach(function() { + this.src = "./output.pdf"; + return this.dst = "./output.pdf"; + }); - describe "when the file is not a pdf file", -> - beforeEach (done)-> - @src = "./output.log" - @OutputFileOptimiser.checkIfPDFIsOptimised = sinon.stub().callsArgWith(1, null, false) - @OutputFileOptimiser.optimisePDF = sinon.stub().callsArgWith(2, null) - @OutputFileOptimiser.optimiseFile @src, @dst, done + describe("when the file is not a pdf file", function() { + beforeEach(function(done){ + this.src = "./output.log"; + this.OutputFileOptimiser.checkIfPDFIsOptimised = sinon.stub().callsArgWith(1, null, false); + this.OutputFileOptimiser.optimisePDF = sinon.stub().callsArgWith(2, null); + return this.OutputFileOptimiser.optimiseFile(this.src, this.dst, done); + }); - it "should not check if the file is optimised", -> - @OutputFileOptimiser.checkIfPDFIsOptimised.calledWith(@src).should.equal false + it("should not check if the file is optimised", function() { + return this.OutputFileOptimiser.checkIfPDFIsOptimised.calledWith(this.src).should.equal(false); + }); - it "should not optimise the file", -> - @OutputFileOptimiser.optimisePDF.calledWith(@src, @dst).should.equal false + return it("should not optimise the file", function() { + return this.OutputFileOptimiser.optimisePDF.calledWith(this.src, this.dst).should.equal(false); + }); + }); - describe "when the pdf file is not optimised", -> - beforeEach (done) -> - @OutputFileOptimiser.checkIfPDFIsOptimised = sinon.stub().callsArgWith(1, null, false) - @OutputFileOptimiser.optimisePDF = sinon.stub().callsArgWith(2, null) - @OutputFileOptimiser.optimiseFile @src, @dst, done + describe("when the pdf file is not optimised", function() { + beforeEach(function(done) { + this.OutputFileOptimiser.checkIfPDFIsOptimised = sinon.stub().callsArgWith(1, null, false); + this.OutputFileOptimiser.optimisePDF = sinon.stub().callsArgWith(2, null); + return this.OutputFileOptimiser.optimiseFile(this.src, this.dst, done); + }); - it "should check if the pdf is optimised", -> - @OutputFileOptimiser.checkIfPDFIsOptimised.calledWith(@src).should.equal true + it("should check if the pdf is optimised", function() { + return this.OutputFileOptimiser.checkIfPDFIsOptimised.calledWith(this.src).should.equal(true); + }); - it "should optimise the pdf", -> - @OutputFileOptimiser.optimisePDF.calledWith(@src, @dst).should.equal true + return it("should optimise the pdf", function() { + return this.OutputFileOptimiser.optimisePDF.calledWith(this.src, this.dst).should.equal(true); + }); + }); - describe "when the pdf file is optimised", -> - beforeEach (done) -> - @OutputFileOptimiser.checkIfPDFIsOptimised = sinon.stub().callsArgWith(1, null, true) - @OutputFileOptimiser.optimisePDF = sinon.stub().callsArgWith(2, null) - @OutputFileOptimiser.optimiseFile @src, @dst, done + return describe("when the pdf file is optimised", function() { + beforeEach(function(done) { + this.OutputFileOptimiser.checkIfPDFIsOptimised = sinon.stub().callsArgWith(1, null, true); + this.OutputFileOptimiser.optimisePDF = sinon.stub().callsArgWith(2, null); + return this.OutputFileOptimiser.optimiseFile(this.src, this.dst, done); + }); - it "should check if the pdf is optimised", -> - @OutputFileOptimiser.checkIfPDFIsOptimised.calledWith(@src).should.equal true + it("should check if the pdf is optimised", function() { + return this.OutputFileOptimiser.checkIfPDFIsOptimised.calledWith(this.src).should.equal(true); + }); - it "should not optimise the pdf", -> - @OutputFileOptimiser.optimisePDF.calledWith(@src, @dst).should.equal false + return it("should not optimise the pdf", function() { + return this.OutputFileOptimiser.optimisePDF.calledWith(this.src, this.dst).should.equal(false); + }); + }); + }); - describe "checkIfPDFISOptimised", -> - beforeEach () -> - @callback = sinon.stub() - @fd = 1234 - @fs.open = sinon.stub().yields(null, @fd) - @fs.read = sinon.stub().withArgs(@fd).yields(null, 100, new Buffer("hello /Linearized 1")) - @fs.close = sinon.stub().withArgs(@fd).yields(null) - @OutputFileOptimiser.checkIfPDFIsOptimised @src, @callback + return describe("checkIfPDFISOptimised", function() { + beforeEach(function() { + this.callback = sinon.stub(); + this.fd = 1234; + this.fs.open = sinon.stub().yields(null, this.fd); + this.fs.read = sinon.stub().withArgs(this.fd).yields(null, 100, new Buffer("hello /Linearized 1")); + this.fs.close = sinon.stub().withArgs(this.fd).yields(null); + return this.OutputFileOptimiser.checkIfPDFIsOptimised(this.src, this.callback); + }); - describe "for a linearised file", -> - beforeEach () -> - @fs.read = sinon.stub().withArgs(@fd).yields(null, 100, new Buffer("hello /Linearized 1")) - @OutputFileOptimiser.checkIfPDFIsOptimised @src, @callback + describe("for a linearised file", function() { + beforeEach(function() { + this.fs.read = sinon.stub().withArgs(this.fd).yields(null, 100, new Buffer("hello /Linearized 1")); + return this.OutputFileOptimiser.checkIfPDFIsOptimised(this.src, this.callback); + }); - it "should open the file", -> - @fs.open.calledWith(@src, "r").should.equal true + it("should open the file", function() { + return this.fs.open.calledWith(this.src, "r").should.equal(true); + }); - it "should read the header", -> - @fs.read.calledWith(@fd).should.equal true + it("should read the header", function() { + return this.fs.read.calledWith(this.fd).should.equal(true); + }); - it "should close the file", -> - @fs.close.calledWith(@fd).should.equal true + it("should close the file", function() { + return this.fs.close.calledWith(this.fd).should.equal(true); + }); - it "should call the callback with a true result", -> - @callback.calledWith(null, true).should.equal true + return it("should call the callback with a true result", function() { + return this.callback.calledWith(null, true).should.equal(true); + }); + }); - describe "for an unlinearised file", -> - beforeEach () -> - @fs.read = sinon.stub().withArgs(@fd).yields(null, 100, new Buffer("hello not linearized 1")) - @OutputFileOptimiser.checkIfPDFIsOptimised @src, @callback + return describe("for an unlinearised file", function() { + beforeEach(function() { + this.fs.read = sinon.stub().withArgs(this.fd).yields(null, 100, new Buffer("hello not linearized 1")); + return this.OutputFileOptimiser.checkIfPDFIsOptimised(this.src, this.callback); + }); - it "should open the file", -> - @fs.open.calledWith(@src, "r").should.equal true + it("should open the file", function() { + return this.fs.open.calledWith(this.src, "r").should.equal(true); + }); - it "should read the header", -> - @fs.read.calledWith(@fd).should.equal true + it("should read the header", function() { + return this.fs.read.calledWith(this.fd).should.equal(true); + }); - it "should close the file", -> - @fs.close.calledWith(@fd).should.equal true + it("should close the file", function() { + return this.fs.close.calledWith(this.fd).should.equal(true); + }); - it "should call the callback with a false result", -> - @callback.calledWith(null, false).should.equal true + return it("should call the callback with a false result", function() { + return this.callback.calledWith(null, false).should.equal(true); + }); + }); + }); +}); diff --git a/services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.js b/services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.js index 69bfd4fa5e..c15cd80865 100644 --- a/services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.js +++ b/services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.js @@ -1,62 +1,82 @@ -SandboxedModule = require('sandboxed-module') -sinon = require('sinon') -require('chai').should() -modulePath = require('path').join __dirname, '../../../app/js/ProjectPersistenceManager' -tk = require("timekeeper") +/* + * 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 SandboxedModule = require('sandboxed-module'); +const sinon = require('sinon'); +require('chai').should(); +const modulePath = require('path').join(__dirname, '../../../app/js/ProjectPersistenceManager'); +const tk = require("timekeeper"); -describe "ProjectPersistenceManager", -> - beforeEach -> - @ProjectPersistenceManager = SandboxedModule.require modulePath, requires: - "./UrlCache": @UrlCache = {} - "./CompileManager": @CompileManager = {} - "logger-sharelatex": @logger = { log: sinon.stub() } - "./db": @db = {} - @callback = sinon.stub() - @project_id = "project-id-123" - @user_id = "1234" +describe("ProjectPersistenceManager", function() { + beforeEach(function() { + this.ProjectPersistenceManager = SandboxedModule.require(modulePath, { requires: { + "./UrlCache": (this.UrlCache = {}), + "./CompileManager": (this.CompileManager = {}), + "logger-sharelatex": (this.logger = { log: sinon.stub() }), + "./db": (this.db = {}) + } + }); + this.callback = sinon.stub(); + this.project_id = "project-id-123"; + return this.user_id = "1234"; + }); - describe "clearExpiredProjects", -> - beforeEach -> - @project_ids = [ - "project-id-1" + describe("clearExpiredProjects", function() { + beforeEach(function() { + this.project_ids = [ + "project-id-1", "project-id-2" - ] - @ProjectPersistenceManager._findExpiredProjectIds = sinon.stub().callsArgWith(0, null, @project_ids) - @ProjectPersistenceManager.clearProjectFromCache = sinon.stub().callsArg(1) - @CompileManager.clearExpiredProjects = sinon.stub().callsArg(1) - @ProjectPersistenceManager.clearExpiredProjects @callback + ]; + this.ProjectPersistenceManager._findExpiredProjectIds = sinon.stub().callsArgWith(0, null, this.project_ids); + this.ProjectPersistenceManager.clearProjectFromCache = sinon.stub().callsArg(1); + this.CompileManager.clearExpiredProjects = sinon.stub().callsArg(1); + return this.ProjectPersistenceManager.clearExpiredProjects(this.callback); + }); - it "should clear each expired project", -> - for project_id in @project_ids - @ProjectPersistenceManager.clearProjectFromCache + it("should clear each expired project", function() { + return Array.from(this.project_ids).map((project_id) => + this.ProjectPersistenceManager.clearProjectFromCache .calledWith(project_id) - .should.equal true + .should.equal(true)); + }); - it "should call the callback", -> - @callback.called.should.equal true + return it("should call the callback", function() { + return this.callback.called.should.equal(true); + }); + }); - describe "clearProject", -> - beforeEach -> - @ProjectPersistenceManager._clearProjectFromDatabase = sinon.stub().callsArg(1) - @UrlCache.clearProject = sinon.stub().callsArg(1) - @CompileManager.clearProject = sinon.stub().callsArg(2) - @ProjectPersistenceManager.clearProject @project_id, @user_id, @callback + return describe("clearProject", function() { + beforeEach(function() { + this.ProjectPersistenceManager._clearProjectFromDatabase = sinon.stub().callsArg(1); + this.UrlCache.clearProject = sinon.stub().callsArg(1); + this.CompileManager.clearProject = sinon.stub().callsArg(2); + return this.ProjectPersistenceManager.clearProject(this.project_id, this.user_id, this.callback); + }); - it "should clear the project from the database", -> - @ProjectPersistenceManager._clearProjectFromDatabase - .calledWith(@project_id) - .should.equal true + it("should clear the project from the database", function() { + return this.ProjectPersistenceManager._clearProjectFromDatabase + .calledWith(this.project_id) + .should.equal(true); + }); - it "should clear all the cached Urls for the project", -> - @UrlCache.clearProject - .calledWith(@project_id) - .should.equal true + it("should clear all the cached Urls for the project", function() { + return this.UrlCache.clearProject + .calledWith(this.project_id) + .should.equal(true); + }); - it "should clear the project compile folder", -> - @CompileManager.clearProject - .calledWith(@project_id, @user_id) - .should.equal true + it("should clear the project compile folder", function() { + return this.CompileManager.clearProject + .calledWith(this.project_id, this.user_id) + .should.equal(true); + }); - it "should call the callback", -> - @callback.called.should.equal true + return it("should call the callback", function() { + return this.callback.called.should.equal(true); + }); + }); +}); diff --git a/services/clsi/test/unit/coffee/RequestParserTests.js b/services/clsi/test/unit/coffee/RequestParserTests.js index e263e49207..5ca09411a7 100644 --- a/services/clsi/test/unit/coffee/RequestParserTests.js +++ b/services/clsi/test/unit/coffee/RequestParserTests.js @@ -1,279 +1,380 @@ -SandboxedModule = require('sandboxed-module') -sinon = require('sinon') -require('chai').should() -expect = require('chai').expect -modulePath = require('path').join __dirname, '../../../app/js/RequestParser' -tk = require("timekeeper") +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module'); +const sinon = require('sinon'); +require('chai').should(); +const { expect } = require('chai'); +const modulePath = require('path').join(__dirname, '../../../app/js/RequestParser'); +const tk = require("timekeeper"); -describe "RequestParser", -> - beforeEach -> - tk.freeze() - @callback = sinon.stub() - @validResource = - path: "main.tex" - date: "12:00 01/02/03" +describe("RequestParser", function() { + beforeEach(function() { + tk.freeze(); + this.callback = sinon.stub(); + this.validResource = { + path: "main.tex", + date: "12:00 01/02/03", content: "Hello world" - @validRequest = - compile: - token: "token-123" - options: - imageName: "basicImageName/here:2017-1" - compiler: "pdflatex" + }; + this.validRequest = { + compile: { + token: "token-123", + options: { + imageName: "basicImageName/here:2017-1", + compiler: "pdflatex", timeout: 42 + }, resources: [] - @RequestParser = SandboxedModule.require modulePath, requires: - "settings-sharelatex": @settings = {} + } + }; + return this.RequestParser = SandboxedModule.require(modulePath, { requires: { + "settings-sharelatex": (this.settings = {}) + } + });}); - afterEach -> - tk.reset() + afterEach(() => tk.reset()); - describe "without a top level object", -> - beforeEach -> - @RequestParser.parse [], @callback + describe("without a top level object", function() { + beforeEach(function() { + return this.RequestParser.parse([], this.callback); + }); - it "should return an error", -> - @callback.calledWith("top level object should have a compile attribute") - .should.equal true + return it("should return an error", function() { + return this.callback.calledWith("top level object should have a compile attribute") + .should.equal(true); + }); + }); - describe "without a compile attribute", -> - beforeEach -> - @RequestParser.parse {}, @callback + describe("without a compile attribute", function() { + beforeEach(function() { + return this.RequestParser.parse({}, this.callback); + }); - it "should return an error", -> - @callback.calledWith("top level object should have a compile attribute") - .should.equal true + return it("should return an error", function() { + return this.callback.calledWith("top level object should have a compile attribute") + .should.equal(true); + }); + }); - describe "without a valid compiler", -> - beforeEach -> - @validRequest.compile.options.compiler = "not-a-compiler" - @RequestParser.parse @validRequest, @callback + describe("without a valid compiler", function() { + beforeEach(function() { + this.validRequest.compile.options.compiler = "not-a-compiler"; + return this.RequestParser.parse(this.validRequest, this.callback); + }); - it "should return an error", -> - @callback.calledWith("compiler attribute should be one of: pdflatex, latex, xelatex, lualatex") - .should.equal true + return it("should return an error", function() { + return this.callback.calledWith("compiler attribute should be one of: pdflatex, latex, xelatex, lualatex") + .should.equal(true); + }); + }); - describe "without a compiler specified", -> - beforeEach -> - delete @validRequest.compile.options.compiler - @RequestParser.parse @validRequest, (error, @data) => + describe("without a compiler specified", function() { + beforeEach(function() { + delete this.validRequest.compile.options.compiler; + return this.RequestParser.parse(this.validRequest, (error, data) => { + this.data = data; + + }); + }); - it "should set the compiler to pdflatex by default", -> - @data.compiler.should.equal "pdflatex" + return it("should set the compiler to pdflatex by default", function() { + return this.data.compiler.should.equal("pdflatex"); + }); + }); - describe "with imageName set", -> - beforeEach -> - @RequestParser.parse @validRequest, (error, @data) => + describe("with imageName set", function() { + beforeEach(function() { + return this.RequestParser.parse(this.validRequest, (error, data) => { + this.data = data; + + }); + }); - it "should set the imageName", -> - @data.imageName.should.equal "basicImageName/here:2017-1" + return it("should set the imageName", function() { + return this.data.imageName.should.equal("basicImageName/here:2017-1"); + }); + }); - describe "with flags set", -> - beforeEach -> - @validRequest.compile.options.flags = ["-file-line-error"] - @RequestParser.parse @validRequest, (error, @data) => + describe("with flags set", function() { + beforeEach(function() { + this.validRequest.compile.options.flags = ["-file-line-error"]; + return this.RequestParser.parse(this.validRequest, (error, data) => { + this.data = data; + + }); + }); - it "should set the flags attribute", -> - expect(@data.flags).to.deep.equal ["-file-line-error"] + return it("should set the flags attribute", function() { + return expect(this.data.flags).to.deep.equal(["-file-line-error"]); + }); +}); - describe "with flags not specified", -> - beforeEach -> - @RequestParser.parse @validRequest, (error, @data) => + describe("with flags not specified", function() { + beforeEach(function() { + return this.RequestParser.parse(this.validRequest, (error, data) => { + this.data = data; + + }); + }); - it "it should have an empty flags list", -> - expect(@data.flags).to.deep.equal [] + return it("it should have an empty flags list", function() { + return expect(this.data.flags).to.deep.equal([]); + }); +}); - describe "without a timeout specified", -> - beforeEach -> - delete @validRequest.compile.options.timeout - @RequestParser.parse @validRequest, (error, @data) => + describe("without a timeout specified", function() { + beforeEach(function() { + delete this.validRequest.compile.options.timeout; + return this.RequestParser.parse(this.validRequest, (error, data) => { + this.data = data; + + }); + }); - it "should set the timeout to MAX_TIMEOUT", -> - @data.timeout.should.equal @RequestParser.MAX_TIMEOUT * 1000 + return it("should set the timeout to MAX_TIMEOUT", function() { + return this.data.timeout.should.equal(this.RequestParser.MAX_TIMEOUT * 1000); + }); + }); - describe "with a timeout larger than the maximum", -> - beforeEach -> - @validRequest.compile.options.timeout = @RequestParser.MAX_TIMEOUT + 1 - @RequestParser.parse @validRequest, (error, @data) => + describe("with a timeout larger than the maximum", function() { + beforeEach(function() { + this.validRequest.compile.options.timeout = this.RequestParser.MAX_TIMEOUT + 1; + return this.RequestParser.parse(this.validRequest, (error, data) => { + this.data = data; + + }); + }); - it "should set the timeout to MAX_TIMEOUT", -> - @data.timeout.should.equal @RequestParser.MAX_TIMEOUT * 1000 + return it("should set the timeout to MAX_TIMEOUT", function() { + return this.data.timeout.should.equal(this.RequestParser.MAX_TIMEOUT * 1000); + }); + }); - describe "with a timeout", -> - beforeEach -> - @RequestParser.parse @validRequest, (error, @data) => + describe("with a timeout", function() { + beforeEach(function() { + return this.RequestParser.parse(this.validRequest, (error, data) => { + this.data = data; + + }); + }); - it "should set the timeout (in milliseconds)", -> - @data.timeout.should.equal @validRequest.compile.options.timeout * 1000 + return it("should set the timeout (in milliseconds)", function() { + return this.data.timeout.should.equal(this.validRequest.compile.options.timeout * 1000); + }); + }); - describe "with a resource without a path", -> - beforeEach -> - delete @validResource.path - @validRequest.compile.resources.push @validResource - @RequestParser.parse @validRequest, @callback + describe("with a resource without a path", function() { + beforeEach(function() { + delete this.validResource.path; + this.validRequest.compile.resources.push(this.validResource); + return this.RequestParser.parse(this.validRequest, this.callback); + }); - it "should return an error", -> - @callback.calledWith("all resources should have a path attribute") - .should.equal true + return it("should return an error", function() { + return this.callback.calledWith("all resources should have a path attribute") + .should.equal(true); + }); + }); - describe "with a resource with a path", -> - beforeEach -> - @validResource.path = @path = "test.tex" - @validRequest.compile.resources.push @validResource - @RequestParser.parse @validRequest, @callback - @data = @callback.args[0][1] + describe("with a resource with a path", function() { + beforeEach(function() { + this.validResource.path = (this.path = "test.tex"); + this.validRequest.compile.resources.push(this.validResource); + this.RequestParser.parse(this.validRequest, this.callback); + return this.data = this.callback.args[0][1];}); - it "should return the path in the parsed response", -> - @data.resources[0].path.should.equal @path + return it("should return the path in the parsed response", function() { + return this.data.resources[0].path.should.equal(this.path); + }); + }); - describe "with a resource with a malformed modified date", -> - beforeEach -> - @validResource.modified = "not-a-date" - @validRequest.compile.resources.push @validResource - @RequestParser.parse @validRequest, @callback + describe("with a resource with a malformed modified date", function() { + beforeEach(function() { + this.validResource.modified = "not-a-date"; + this.validRequest.compile.resources.push(this.validResource); + return this.RequestParser.parse(this.validRequest, this.callback); + }); - it "should return an error", -> - @callback + return it("should return an error", function() { + return this.callback .calledWith( "resource modified date could not be understood: "+ - @validResource.modified + this.validResource.modified ) - .should.equal true + .should.equal(true); + }); + }); - describe "with a resource with a valid date", -> - beforeEach -> - @date = "12:00 01/02/03" - @validResource.modified = @date - @validRequest.compile.resources.push @validResource - @RequestParser.parse @validRequest, @callback - @data = @callback.args[0][1] + describe("with a resource with a valid date", function() { + beforeEach(function() { + this.date = "12:00 01/02/03"; + this.validResource.modified = this.date; + this.validRequest.compile.resources.push(this.validResource); + this.RequestParser.parse(this.validRequest, this.callback); + return this.data = this.callback.args[0][1];}); - it "should return the date as a Javascript Date object", -> - (@data.resources[0].modified instanceof Date).should.equal true - @data.resources[0].modified.getTime().should.equal Date.parse(@date) + return it("should return the date as a Javascript Date object", function() { + (this.data.resources[0].modified instanceof Date).should.equal(true); + return this.data.resources[0].modified.getTime().should.equal(Date.parse(this.date)); + }); + }); - describe "with a resource without either a content or URL attribute", -> - beforeEach -> - delete @validResource.url - delete @validResource.content - @validRequest.compile.resources.push @validResource - @RequestParser.parse @validRequest, @callback + describe("with a resource without either a content or URL attribute", function() { + beforeEach(function() { + delete this.validResource.url; + delete this.validResource.content; + this.validRequest.compile.resources.push(this.validResource); + return this.RequestParser.parse(this.validRequest, this.callback); + }); - it "should return an error", -> - @callback.calledWith("all resources should have either a url or content attribute") - .should.equal true + return it("should return an error", function() { + return this.callback.calledWith("all resources should have either a url or content attribute") + .should.equal(true); + }); + }); - describe "with a resource where the content is not a string", -> - beforeEach -> - @validResource.content = [] - @validRequest.compile.resources.push @validResource - @RequestParser.parse (@validRequest), @callback + describe("with a resource where the content is not a string", function() { + beforeEach(function() { + this.validResource.content = []; + this.validRequest.compile.resources.push(this.validResource); + return this.RequestParser.parse((this.validRequest), this.callback); + }); - it "should return an error", -> - @callback.calledWith("content attribute should be a string") - .should.equal true + return it("should return an error", function() { + return this.callback.calledWith("content attribute should be a string") + .should.equal(true); + }); + }); - describe "with a resource where the url is not a string", -> - beforeEach -> - @validResource.url = [] - @validRequest.compile.resources.push @validResource - @RequestParser.parse (@validRequest), @callback + describe("with a resource where the url is not a string", function() { + beforeEach(function() { + this.validResource.url = []; + this.validRequest.compile.resources.push(this.validResource); + return this.RequestParser.parse((this.validRequest), this.callback); + }); - it "should return an error", -> - @callback.calledWith("url attribute should be a string") - .should.equal true + return it("should return an error", function() { + return this.callback.calledWith("url attribute should be a string") + .should.equal(true); + }); + }); - describe "with a resource with a url", -> - beforeEach -> - @validResource.url = @url = "www.example.com" - @validRequest.compile.resources.push @validResource - @RequestParser.parse (@validRequest), @callback - @data = @callback.args[0][1] + describe("with a resource with a url", function() { + beforeEach(function() { + this.validResource.url = (this.url = "www.example.com"); + this.validRequest.compile.resources.push(this.validResource); + this.RequestParser.parse((this.validRequest), this.callback); + return this.data = this.callback.args[0][1];}); - it "should return the url in the parsed response", -> - @data.resources[0].url.should.equal @url + return it("should return the url in the parsed response", function() { + return this.data.resources[0].url.should.equal(this.url); + }); + }); - describe "with a resource with a content attribute", -> - beforeEach -> - @validResource.content = @content = "Hello world" - @validRequest.compile.resources.push @validResource - @RequestParser.parse (@validRequest), @callback - @data = @callback.args[0][1] + describe("with a resource with a content attribute", function() { + beforeEach(function() { + this.validResource.content = (this.content = "Hello world"); + this.validRequest.compile.resources.push(this.validResource); + this.RequestParser.parse((this.validRequest), this.callback); + return this.data = this.callback.args[0][1];}); - it "should return the content in the parsed response", -> - @data.resources[0].content.should.equal @content + return it("should return the content in the parsed response", function() { + return this.data.resources[0].content.should.equal(this.content); + }); + }); - describe "without a root resource path", -> - beforeEach -> - delete @validRequest.compile.rootResourcePath - @RequestParser.parse (@validRequest), @callback - @data = @callback.args[0][1] + describe("without a root resource path", function() { + beforeEach(function() { + delete this.validRequest.compile.rootResourcePath; + this.RequestParser.parse((this.validRequest), this.callback); + return this.data = this.callback.args[0][1];}); - it "should set the root resource path to 'main.tex' by default", -> - @data.rootResourcePath.should.equal "main.tex" + return it("should set the root resource path to 'main.tex' by default", function() { + return this.data.rootResourcePath.should.equal("main.tex"); + }); + }); - describe "with a root resource path", -> - beforeEach -> - @validRequest.compile.rootResourcePath = @path = "test.tex" - @RequestParser.parse (@validRequest), @callback - @data = @callback.args[0][1] + describe("with a root resource path", function() { + beforeEach(function() { + this.validRequest.compile.rootResourcePath = (this.path = "test.tex"); + this.RequestParser.parse((this.validRequest), this.callback); + return this.data = this.callback.args[0][1];}); - it "should return the root resource path in the parsed response", -> - @data.rootResourcePath.should.equal @path + return it("should return the root resource path in the parsed response", function() { + return this.data.rootResourcePath.should.equal(this.path); + }); + }); - describe "with a root resource path that is not a string", -> - beforeEach -> - @validRequest.compile.rootResourcePath = [] - @RequestParser.parse (@validRequest), @callback + describe("with a root resource path that is not a string", function() { + beforeEach(function() { + this.validRequest.compile.rootResourcePath = []; + return this.RequestParser.parse((this.validRequest), this.callback); + }); - it "should return an error", -> - @callback.calledWith("rootResourcePath attribute should be a string") - .should.equal true + return it("should return an error", function() { + return this.callback.calledWith("rootResourcePath attribute should be a string") + .should.equal(true); + }); + }); - describe "with a root resource path that needs escaping", -> - beforeEach -> - @badPath = "`rm -rf foo`.tex" - @goodPath = "rm -rf foo.tex" - @validRequest.compile.rootResourcePath = @badPath - @validRequest.compile.resources.push { - path: @badPath - date: "12:00 01/02/03" + describe("with a root resource path that needs escaping", function() { + beforeEach(function() { + this.badPath = "`rm -rf foo`.tex"; + this.goodPath = "rm -rf foo.tex"; + this.validRequest.compile.rootResourcePath = this.badPath; + this.validRequest.compile.resources.push({ + path: this.badPath, + date: "12:00 01/02/03", content: "Hello world" - } - @RequestParser.parse @validRequest, @callback - @data = @callback.args[0][1] + }); + this.RequestParser.parse(this.validRequest, this.callback); + return this.data = this.callback.args[0][1];}); - it "should return the escaped resource", -> - @data.rootResourcePath.should.equal @goodPath + it("should return the escaped resource", function() { + return this.data.rootResourcePath.should.equal(this.goodPath); + }); - it "should also escape the resource path", -> - @data.resources[0].path.should.equal @goodPath + return it("should also escape the resource path", function() { + return this.data.resources[0].path.should.equal(this.goodPath); + }); + }); - describe "with a root resource path that has a relative path", -> - beforeEach -> - @validRequest.compile.rootResourcePath = "foo/../../bar.tex" - @RequestParser.parse @validRequest, @callback - @data = @callback.args[0][1] + describe("with a root resource path that has a relative path", function() { + beforeEach(function() { + this.validRequest.compile.rootResourcePath = "foo/../../bar.tex"; + this.RequestParser.parse(this.validRequest, this.callback); + return this.data = this.callback.args[0][1];}); - it "should return an error", -> - @callback.calledWith("relative path in root resource") - .should.equal true + return it("should return an error", function() { + return this.callback.calledWith("relative path in root resource") + .should.equal(true); + }); + }); - describe "with a root resource path that has unescaped + relative path", -> - beforeEach -> - @validRequest.compile.rootResourcePath = "foo/#../bar.tex" - @RequestParser.parse @validRequest, @callback - @data = @callback.args[0][1] + describe("with a root resource path that has unescaped + relative path", function() { + beforeEach(function() { + this.validRequest.compile.rootResourcePath = "foo/#../bar.tex"; + this.RequestParser.parse(this.validRequest, this.callback); + return this.data = this.callback.args[0][1];}); - it "should return an error", -> - @callback.calledWith("relative path in root resource") - .should.equal true + return it("should return an error", function() { + return this.callback.calledWith("relative path in root resource") + .should.equal(true); + }); + }); - describe "with an unknown syncType", -> - beforeEach -> - @validRequest.compile.options.syncType = "unexpected" - @RequestParser.parse @validRequest, @callback - @data = @callback.args[0][1] + return describe("with an unknown syncType", function() { + beforeEach(function() { + this.validRequest.compile.options.syncType = "unexpected"; + this.RequestParser.parse(this.validRequest, this.callback); + return this.data = this.callback.args[0][1];}); - it "should return an error", -> - @callback.calledWith("syncType attribute should be one of: full, incremental") - .should.equal true + return it("should return an error", function() { + return this.callback.calledWith("syncType attribute should be one of: full, incremental") + .should.equal(true); + }); + }); +}); diff --git a/services/clsi/test/unit/coffee/ResourceStateManagerTests.js b/services/clsi/test/unit/coffee/ResourceStateManagerTests.js index e5e1c13011..4b09135efc 100644 --- a/services/clsi/test/unit/coffee/ResourceStateManagerTests.js +++ b/services/clsi/test/unit/coffee/ResourceStateManagerTests.js @@ -1,109 +1,147 @@ -SandboxedModule = require('sandboxed-module') -sinon = require('sinon') -should = require('chai').should() -modulePath = require('path').join __dirname, '../../../app/js/ResourceStateManager' -Path = require "path" -Errors = require "../../../app/js/Errors" +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module'); +const sinon = require('sinon'); +const should = require('chai').should(); +const modulePath = require('path').join(__dirname, '../../../app/js/ResourceStateManager'); +const Path = require("path"); +const Errors = require("../../../app/js/Errors"); -describe "ResourceStateManager", -> - beforeEach -> - @ResourceStateManager = SandboxedModule.require modulePath, requires: - "fs": @fs = {} - "logger-sharelatex": {log: sinon.stub(), err: sinon.stub()} - "./SafeReader": @SafeReader = {} - @basePath = "/path/to/write/files/to" - @resources = [ - {path: "resource-1-mock"} - {path: "resource-2-mock"} +describe("ResourceStateManager", function() { + beforeEach(function() { + this.ResourceStateManager = SandboxedModule.require(modulePath, { requires: { + "fs": (this.fs = {}), + "logger-sharelatex": {log: sinon.stub(), err: sinon.stub()}, + "./SafeReader": (this.SafeReader = {}) + } + }); + this.basePath = "/path/to/write/files/to"; + this.resources = [ + {path: "resource-1-mock"}, + {path: "resource-2-mock"}, {path: "resource-3-mock"} - ] - @state = "1234567890" - @resourceFileName = "#{@basePath}/.project-sync-state" - @resourceFileContents = "#{@resources[0].path}\n#{@resources[1].path}\n#{@resources[2].path}\nstateHash:#{@state}" - @callback = sinon.stub() + ]; + this.state = "1234567890"; + this.resourceFileName = `${this.basePath}/.project-sync-state`; + this.resourceFileContents = `${this.resources[0].path}\n${this.resources[1].path}\n${this.resources[2].path}\nstateHash:${this.state}`; + return this.callback = sinon.stub(); + }); - describe "saveProjectState", -> - beforeEach -> - @fs.writeFile = sinon.stub().callsArg(2) + describe("saveProjectState", function() { + beforeEach(function() { + return this.fs.writeFile = sinon.stub().callsArg(2); + }); - describe "when the state is specified", -> - beforeEach -> - @ResourceStateManager.saveProjectState(@state, @resources, @basePath, @callback) + describe("when the state is specified", function() { + beforeEach(function() { + return this.ResourceStateManager.saveProjectState(this.state, this.resources, this.basePath, this.callback); + }); - it "should write the resource list to disk", -> - @fs.writeFile - .calledWith(@resourceFileName, @resourceFileContents) - .should.equal true + it("should write the resource list to disk", function() { + return this.fs.writeFile + .calledWith(this.resourceFileName, this.resourceFileContents) + .should.equal(true); + }); - it "should call the callback", -> - @callback.called.should.equal true + return it("should call the callback", function() { + return this.callback.called.should.equal(true); + }); + }); - describe "when the state is undefined", -> - beforeEach -> - @state = undefined - @fs.unlink = sinon.stub().callsArg(1) - @ResourceStateManager.saveProjectState(@state, @resources, @basePath, @callback) + return describe("when the state is undefined", function() { + beforeEach(function() { + this.state = undefined; + this.fs.unlink = sinon.stub().callsArg(1); + return this.ResourceStateManager.saveProjectState(this.state, this.resources, this.basePath, this.callback); + }); - it "should unlink the resource file", -> - @fs.unlink - .calledWith(@resourceFileName) - .should.equal true + it("should unlink the resource file", function() { + return this.fs.unlink + .calledWith(this.resourceFileName) + .should.equal(true); + }); - it "should not write the resource list to disk", -> - @fs.writeFile.called.should.equal false + it("should not write the resource list to disk", function() { + return this.fs.writeFile.called.should.equal(false); + }); - it "should call the callback", -> - @callback.called.should.equal true + return it("should call the callback", function() { + return this.callback.called.should.equal(true); + }); + }); + }); - describe "checkProjectStateMatches", -> + describe("checkProjectStateMatches", function() { - describe "when the state matches", -> - beforeEach -> - @SafeReader.readFile = sinon.stub().callsArgWith(3, null, @resourceFileContents) - @ResourceStateManager.checkProjectStateMatches(@state, @basePath, @callback) + describe("when the state matches", function() { + beforeEach(function() { + this.SafeReader.readFile = sinon.stub().callsArgWith(3, null, this.resourceFileContents); + return this.ResourceStateManager.checkProjectStateMatches(this.state, this.basePath, this.callback); + }); - it "should read the resource file", -> - @SafeReader.readFile - .calledWith(@resourceFileName) - .should.equal true + it("should read the resource file", function() { + return this.SafeReader.readFile + .calledWith(this.resourceFileName) + .should.equal(true); + }); - it "should call the callback with the results", -> - @callback.calledWithMatch(null, @resources).should.equal true + return it("should call the callback with the results", function() { + return this.callback.calledWithMatch(null, this.resources).should.equal(true); + }); + }); - describe "when the state does not match", -> - beforeEach -> - @SafeReader.readFile = sinon.stub().callsArgWith(3, null, @resourceFileContents) - @ResourceStateManager.checkProjectStateMatches("not-the-original-state", @basePath, @callback) + return describe("when the state does not match", function() { + beforeEach(function() { + this.SafeReader.readFile = sinon.stub().callsArgWith(3, null, this.resourceFileContents); + return this.ResourceStateManager.checkProjectStateMatches("not-the-original-state", this.basePath, this.callback); + }); - it "should call the callback with an error", -> - error = new Errors.FilesOutOfSyncError("invalid state for incremental update") - @callback.calledWith(error).should.equal true + return it("should call the callback with an error", function() { + const error = new Errors.FilesOutOfSyncError("invalid state for incremental update"); + return this.callback.calledWith(error).should.equal(true); + }); + }); + }); - describe "checkResourceFiles", -> - describe "when all the files are present", -> - beforeEach -> - @allFiles = [ @resources[0].path, @resources[1].path, @resources[2].path] - @ResourceStateManager.checkResourceFiles(@resources, @allFiles, @basePath, @callback) + return describe("checkResourceFiles", function() { + describe("when all the files are present", function() { + beforeEach(function() { + this.allFiles = [ this.resources[0].path, this.resources[1].path, this.resources[2].path]; + return this.ResourceStateManager.checkResourceFiles(this.resources, this.allFiles, this.basePath, this.callback); + }); - it "should call the callback", -> - @callback.calledWithExactly().should.equal true + return it("should call the callback", function() { + return this.callback.calledWithExactly().should.equal(true); + }); + }); - describe "when there is a missing file", -> - beforeEach -> - @allFiles = [ @resources[0].path, @resources[1].path] - @fs.stat = sinon.stub().callsArgWith(1, new Error()) - @ResourceStateManager.checkResourceFiles(@resources, @allFiles, @basePath, @callback) + describe("when there is a missing file", function() { + beforeEach(function() { + this.allFiles = [ this.resources[0].path, this.resources[1].path]; + this.fs.stat = sinon.stub().callsArgWith(1, new Error()); + return this.ResourceStateManager.checkResourceFiles(this.resources, this.allFiles, this.basePath, this.callback); + }); - it "should call the callback with an error", -> - error = new Errors.FilesOutOfSyncError("resource files missing in incremental update") - @callback.calledWith(error).should.equal true + return it("should call the callback with an error", function() { + const error = new Errors.FilesOutOfSyncError("resource files missing in incremental update"); + return this.callback.calledWith(error).should.equal(true); + }); + }); - describe "when a resource contains a relative path", -> - beforeEach -> - @resources[0].path = "../foo/bar.tex" - @allFiles = [ @resources[0].path, @resources[1].path, @resources[2].path] - @ResourceStateManager.checkResourceFiles(@resources, @allFiles, @basePath, @callback) + return describe("when a resource contains a relative path", function() { + beforeEach(function() { + this.resources[0].path = "../foo/bar.tex"; + this.allFiles = [ this.resources[0].path, this.resources[1].path, this.resources[2].path]; + return this.ResourceStateManager.checkResourceFiles(this.resources, this.allFiles, this.basePath, this.callback); + }); - it "should call the callback with an error", -> - @callback.calledWith(new Error("relative path in resource file list")).should.equal true + return it("should call the callback with an error", function() { + return this.callback.calledWith(new Error("relative path in resource file list")).should.equal(true); + }); + }); + }); +}); diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.js b/services/clsi/test/unit/coffee/ResourceWriterTests.js index 4a88226f20..89433c8206 100644 --- a/services/clsi/test/unit/coffee/ResourceWriterTests.js +++ b/services/clsi/test/unit/coffee/ResourceWriterTests.js @@ -1,324 +1,409 @@ -SandboxedModule = require('sandboxed-module') -sinon = require('sinon') -should = require('chai').should() -modulePath = require('path').join __dirname, '../../../app/js/ResourceWriter' -path = require "path" +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS206: Consider reworking classes to avoid initClass + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module'); +const sinon = require('sinon'); +const should = require('chai').should(); +const modulePath = require('path').join(__dirname, '../../../app/js/ResourceWriter'); +const path = require("path"); -describe "ResourceWriter", -> - beforeEach -> - @ResourceWriter = SandboxedModule.require modulePath, requires: - "fs": @fs = - mkdir: sinon.stub().callsArg(1) +describe("ResourceWriter", function() { + beforeEach(function() { + let Timer; + this.ResourceWriter = SandboxedModule.require(modulePath, { requires: { + "fs": (this.fs = { + mkdir: sinon.stub().callsArg(1), unlink: sinon.stub().callsArg(1) - "./ResourceStateManager": @ResourceStateManager = {} - "wrench": @wrench = {} - "./UrlCache" : @UrlCache = {} - "mkdirp" : @mkdirp = sinon.stub().callsArg(1) - "./OutputFileFinder": @OutputFileFinder = {} - "logger-sharelatex": {log: sinon.stub(), err: sinon.stub()} - "./Metrics": @Metrics = - Timer: class Timer - done: sinon.stub() - @project_id = "project-id-123" - @basePath = "/path/to/write/files/to" - @callback = sinon.stub() + }), + "./ResourceStateManager": (this.ResourceStateManager = {}), + "wrench": (this.wrench = {}), + "./UrlCache" : (this.UrlCache = {}), + "mkdirp" : (this.mkdirp = sinon.stub().callsArg(1)), + "./OutputFileFinder": (this.OutputFileFinder = {}), + "logger-sharelatex": {log: sinon.stub(), err: sinon.stub()}, + "./Metrics": (this.Metrics = { + Timer: (Timer = (function() { + Timer = class Timer { + static initClass() { + this.prototype.done = sinon.stub(); + } + }; + Timer.initClass(); + return Timer; + })()) + }) + } + } + ); + this.project_id = "project-id-123"; + this.basePath = "/path/to/write/files/to"; + return this.callback = sinon.stub(); + }); - describe "syncResourcesToDisk on a full request", -> - beforeEach -> - @resources = [ - "resource-1-mock" - "resource-2-mock" + describe("syncResourcesToDisk on a full request", function() { + beforeEach(function() { + this.resources = [ + "resource-1-mock", + "resource-2-mock", "resource-3-mock" - ] - @ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3) - @ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2) - @ResourceStateManager.saveProjectState = sinon.stub().callsArg(3) - @ResourceWriter.syncResourcesToDisk({ - project_id: @project_id - syncState: @syncState = "0123456789abcdef" - resources: @resources - }, @basePath, @callback) + ]; + this.ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3); + this.ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2); + this.ResourceStateManager.saveProjectState = sinon.stub().callsArg(3); + return this.ResourceWriter.syncResourcesToDisk({ + project_id: this.project_id, + syncState: (this.syncState = "0123456789abcdef"), + resources: this.resources + }, this.basePath, this.callback); + }); - it "should remove old files", -> - @ResourceWriter._removeExtraneousFiles - .calledWith(@resources, @basePath) - .should.equal true + it("should remove old files", function() { + return this.ResourceWriter._removeExtraneousFiles + .calledWith(this.resources, this.basePath) + .should.equal(true); + }); - it "should write each resource to disk", -> - for resource in @resources - @ResourceWriter._writeResourceToDisk - .calledWith(@project_id, resource, @basePath) - .should.equal true + it("should write each resource to disk", function() { + return Array.from(this.resources).map((resource) => + this.ResourceWriter._writeResourceToDisk + .calledWith(this.project_id, resource, this.basePath) + .should.equal(true)); + }); - it "should store the sync state and resource list", -> - @ResourceStateManager.saveProjectState - .calledWith(@syncState, @resources, @basePath) - .should.equal true + it("should store the sync state and resource list", function() { + return this.ResourceStateManager.saveProjectState + .calledWith(this.syncState, this.resources, this.basePath) + .should.equal(true); + }); - it "should call the callback", -> - @callback.called.should.equal true + return it("should call the callback", function() { + return this.callback.called.should.equal(true); + }); + }); - describe "syncResourcesToDisk on an incremental update", -> - beforeEach -> - @resources = [ + describe("syncResourcesToDisk on an incremental update", function() { + beforeEach(function() { + this.resources = [ "resource-1-mock" - ] - @ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3) - @ResourceWriter._removeExtraneousFiles = sinon.stub().callsArgWith(2, null, @outputFiles = [], @allFiles = []) - @ResourceStateManager.checkProjectStateMatches = sinon.stub().callsArgWith(2, null, @resources) - @ResourceStateManager.saveProjectState = sinon.stub().callsArg(3) - @ResourceStateManager.checkResourceFiles = sinon.stub().callsArg(3) - @ResourceWriter.syncResourcesToDisk({ - project_id: @project_id, + ]; + this.ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3); + this.ResourceWriter._removeExtraneousFiles = sinon.stub().callsArgWith(2, null, (this.outputFiles = []), (this.allFiles = [])); + this.ResourceStateManager.checkProjectStateMatches = sinon.stub().callsArgWith(2, null, this.resources); + this.ResourceStateManager.saveProjectState = sinon.stub().callsArg(3); + this.ResourceStateManager.checkResourceFiles = sinon.stub().callsArg(3); + return this.ResourceWriter.syncResourcesToDisk({ + project_id: this.project_id, syncType: "incremental", - syncState: @syncState = "1234567890abcdef", - resources: @resources - }, @basePath, @callback) + syncState: (this.syncState = "1234567890abcdef"), + resources: this.resources + }, this.basePath, this.callback); + }); - it "should check the sync state matches", -> - @ResourceStateManager.checkProjectStateMatches - .calledWith(@syncState, @basePath) - .should.equal true + it("should check the sync state matches", function() { + return this.ResourceStateManager.checkProjectStateMatches + .calledWith(this.syncState, this.basePath) + .should.equal(true); + }); - it "should remove old files", -> - @ResourceWriter._removeExtraneousFiles - .calledWith(@resources, @basePath) - .should.equal true + it("should remove old files", function() { + return this.ResourceWriter._removeExtraneousFiles + .calledWith(this.resources, this.basePath) + .should.equal(true); + }); - it "should check each resource exists", -> - @ResourceStateManager.checkResourceFiles - .calledWith(@resources, @allFiles, @basePath) - .should.equal true + it("should check each resource exists", function() { + return this.ResourceStateManager.checkResourceFiles + .calledWith(this.resources, this.allFiles, this.basePath) + .should.equal(true); + }); - it "should write each resource to disk", -> - for resource in @resources - @ResourceWriter._writeResourceToDisk - .calledWith(@project_id, resource, @basePath) - .should.equal true + it("should write each resource to disk", function() { + return Array.from(this.resources).map((resource) => + this.ResourceWriter._writeResourceToDisk + .calledWith(this.project_id, resource, this.basePath) + .should.equal(true)); + }); - it "should call the callback", -> - @callback.called.should.equal true + return it("should call the callback", function() { + return this.callback.called.should.equal(true); + }); + }); - describe "syncResourcesToDisk on an incremental update when the state does not match", -> - beforeEach -> - @resources = [ + describe("syncResourcesToDisk on an incremental update when the state does not match", function() { + beforeEach(function() { + this.resources = [ "resource-1-mock" - ] - @ResourceStateManager.checkProjectStateMatches = sinon.stub().callsArgWith(2, @error = new Error()) - @ResourceWriter.syncResourcesToDisk({ - project_id: @project_id, + ]; + this.ResourceStateManager.checkProjectStateMatches = sinon.stub().callsArgWith(2, (this.error = new Error())); + return this.ResourceWriter.syncResourcesToDisk({ + project_id: this.project_id, syncType: "incremental", - syncState: @syncState = "1234567890abcdef", - resources: @resources - }, @basePath, @callback) + syncState: (this.syncState = "1234567890abcdef"), + resources: this.resources + }, this.basePath, this.callback); + }); - it "should check whether the sync state matches", -> - @ResourceStateManager.checkProjectStateMatches - .calledWith(@syncState, @basePath) - .should.equal true + it("should check whether the sync state matches", function() { + return this.ResourceStateManager.checkProjectStateMatches + .calledWith(this.syncState, this.basePath) + .should.equal(true); + }); - it "should call the callback with an error", -> - @callback.calledWith(@error).should.equal true + return it("should call the callback with an error", function() { + return this.callback.calledWith(this.error).should.equal(true); + }); + }); - describe "_removeExtraneousFiles", -> - beforeEach -> - @output_files = [{ - path: "output.pdf" + describe("_removeExtraneousFiles", function() { + beforeEach(function() { + this.output_files = [{ + path: "output.pdf", type: "pdf" }, { - path: "extra/file.tex" + path: "extra/file.tex", type: "tex" }, { - path: "extra.aux" + path: "extra.aux", type: "aux" }, { path: "cache/_chunk1" },{ - path: "figures/image-eps-converted-to.pdf" + path: "figures/image-eps-converted-to.pdf", type: "pdf" },{ - path: "foo/main-figure0.md5" + path: "foo/main-figure0.md5", type: "md5" }, { - path: "foo/main-figure0.dpth" + path: "foo/main-figure0.dpth", type: "dpth" }, { - path: "foo/main-figure0.pdf" + path: "foo/main-figure0.pdf", type: "pdf" }, { - path: "_minted-main/default-pyg-prefix.pygstyle" + path: "_minted-main/default-pyg-prefix.pygstyle", type: "pygstyle" }, { - path: "_minted-main/default.pygstyle" + path: "_minted-main/default.pygstyle", type: "pygstyle" }, { - path: "_minted-main/35E248B60965545BD232AE9F0FE9750D504A7AF0CD3BAA7542030FC560DFCC45.pygtex" + path: "_minted-main/35E248B60965545BD232AE9F0FE9750D504A7AF0CD3BAA7542030FC560DFCC45.pygtex", type: "pygtex" }, { - path: "_markdown_main/30893013dec5d869a415610079774c2f.md.tex" + path: "_markdown_main/30893013dec5d869a415610079774c2f.md.tex", type: "tex" - }] - @resources = "mock-resources" - @OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, @output_files) - @ResourceWriter._deleteFileIfNotDirectory = sinon.stub().callsArg(1) - @ResourceWriter._removeExtraneousFiles(@resources, @basePath, @callback) + }]; + this.resources = "mock-resources"; + this.OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, this.output_files); + this.ResourceWriter._deleteFileIfNotDirectory = sinon.stub().callsArg(1); + return this.ResourceWriter._removeExtraneousFiles(this.resources, this.basePath, this.callback); + }); - it "should find the existing output files", -> - @OutputFileFinder.findOutputFiles - .calledWith(@resources, @basePath) - .should.equal true + it("should find the existing output files", function() { + return this.OutputFileFinder.findOutputFiles + .calledWith(this.resources, this.basePath) + .should.equal(true); + }); - it "should delete the output files", -> - @ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(@basePath, "output.pdf")) - .should.equal true + it("should delete the output files", function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, "output.pdf")) + .should.equal(true); + }); - it "should delete the extra files", -> - @ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(@basePath, "extra/file.tex")) - .should.equal true + it("should delete the extra files", function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, "extra/file.tex")) + .should.equal(true); + }); - it "should not delete the extra aux files", -> - @ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(@basePath, "extra.aux")) - .should.equal false + it("should not delete the extra aux files", function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, "extra.aux")) + .should.equal(false); + }); - it "should not delete the knitr cache file", -> - @ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(@basePath, "cache/_chunk1")) - .should.equal false + it("should not delete the knitr cache file", function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, "cache/_chunk1")) + .should.equal(false); + }); - it "should not delete the epstopdf converted files", -> - @ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(@basePath, "figures/image-eps-converted-to.pdf")) - .should.equal false + it("should not delete the epstopdf converted files", function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, "figures/image-eps-converted-to.pdf")) + .should.equal(false); + }); - it "should not delete the tikz md5 files", -> - @ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(@basePath, "foo/main-figure0.md5")) - .should.equal false + it("should not delete the tikz md5 files", function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, "foo/main-figure0.md5")) + .should.equal(false); + }); - it "should not delete the tikz dpth files", -> - @ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(@basePath, "foo/main-figure0.dpth")) - .should.equal false + it("should not delete the tikz dpth files", function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, "foo/main-figure0.dpth")) + .should.equal(false); + }); - it "should not delete the tikz pdf files", -> - @ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(@basePath, "foo/main-figure0.pdf")) - .should.equal false + it("should not delete the tikz pdf files", function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, "foo/main-figure0.pdf")) + .should.equal(false); + }); - it "should not delete the minted pygstyle files", -> - @ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(@basePath, "_minted-main/default-pyg-prefix.pygstyle")) - .should.equal false + it("should not delete the minted pygstyle files", function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, "_minted-main/default-pyg-prefix.pygstyle")) + .should.equal(false); + }); - it "should not delete the minted default pygstyle files", -> - @ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(@basePath, "_minted-main/default.pygstyle")) - .should.equal false + it("should not delete the minted default pygstyle files", function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, "_minted-main/default.pygstyle")) + .should.equal(false); + }); - it "should not delete the minted default pygtex files", -> - @ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(@basePath, "_minted-main/35E248B60965545BD232AE9F0FE9750D504A7AF0CD3BAA7542030FC560DFCC45.pygtex")) - .should.equal false + it("should not delete the minted default pygtex files", function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, "_minted-main/35E248B60965545BD232AE9F0FE9750D504A7AF0CD3BAA7542030FC560DFCC45.pygtex")) + .should.equal(false); + }); - it "should not delete the markdown md.tex files", -> - @ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(@basePath, "_markdown_main/30893013dec5d869a415610079774c2f.md.tex")) - .should.equal false + it("should not delete the markdown md.tex files", function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, "_markdown_main/30893013dec5d869a415610079774c2f.md.tex")) + .should.equal(false); + }); - it "should call the callback", -> - @callback.called.should.equal true + it("should call the callback", function() { + return this.callback.called.should.equal(true); + }); - it "should time the request", -> - @Metrics.Timer::done.called.should.equal true + return it("should time the request", function() { + return this.Metrics.Timer.prototype.done.called.should.equal(true); + }); + }); - describe "_writeResourceToDisk", -> - describe "with a url based resource", -> - beforeEach -> - @resource = - path: "main.tex" - url: "http://www.example.com/main.tex" + describe("_writeResourceToDisk", function() { + describe("with a url based resource", function() { + beforeEach(function() { + this.resource = { + path: "main.tex", + url: "http://www.example.com/main.tex", modified: Date.now() - @UrlCache.downloadUrlToFile = sinon.stub().callsArgWith(4, "fake error downloading file") - @ResourceWriter._writeResourceToDisk(@project_id, @resource, @basePath, @callback) + }; + this.UrlCache.downloadUrlToFile = sinon.stub().callsArgWith(4, "fake error downloading file"); + return this.ResourceWriter._writeResourceToDisk(this.project_id, this.resource, this.basePath, this.callback); + }); - it "should ensure the directory exists", -> - @mkdirp - .calledWith(path.dirname(path.join(@basePath, @resource.path))) - .should.equal true + it("should ensure the directory exists", function() { + return this.mkdirp + .calledWith(path.dirname(path.join(this.basePath, this.resource.path))) + .should.equal(true); + }); - it "should write the URL from the cache", -> - @UrlCache.downloadUrlToFile - .calledWith(@project_id, @resource.url, path.join(@basePath, @resource.path), @resource.modified) - .should.equal true + it("should write the URL from the cache", function() { + return this.UrlCache.downloadUrlToFile + .calledWith(this.project_id, this.resource.url, path.join(this.basePath, this.resource.path), this.resource.modified) + .should.equal(true); + }); - it "should call the callback", -> - @callback.called.should.equal true + it("should call the callback", function() { + return this.callback.called.should.equal(true); + }); - it "should not return an error if the resource writer errored", -> - should.not.exist @callback.args[0][0] + return it("should not return an error if the resource writer errored", function() { + return should.not.exist(this.callback.args[0][0]); + }); + }); - describe "with a content based resource", -> - beforeEach -> - @resource = - path: "main.tex" + describe("with a content based resource", function() { + beforeEach(function() { + this.resource = { + path: "main.tex", content: "Hello world" - @fs.writeFile = sinon.stub().callsArg(2) - @ResourceWriter._writeResourceToDisk(@project_id, @resource, @basePath, @callback) + }; + this.fs.writeFile = sinon.stub().callsArg(2); + return this.ResourceWriter._writeResourceToDisk(this.project_id, this.resource, this.basePath, this.callback); + }); - it "should ensure the directory exists", -> - @mkdirp - .calledWith(path.dirname(path.join(@basePath, @resource.path))) - .should.equal true + it("should ensure the directory exists", function() { + return this.mkdirp + .calledWith(path.dirname(path.join(this.basePath, this.resource.path))) + .should.equal(true); + }); - it "should write the contents to disk", -> - @fs.writeFile - .calledWith(path.join(@basePath, @resource.path), @resource.content) - .should.equal true + it("should write the contents to disk", function() { + return this.fs.writeFile + .calledWith(path.join(this.basePath, this.resource.path), this.resource.content) + .should.equal(true); + }); - it "should call the callback", -> - @callback.called.should.equal true + return it("should call the callback", function() { + return this.callback.called.should.equal(true); + }); + }); - describe "with a file path that breaks out of the root folder", -> - beforeEach -> - @resource = - path: "../../main.tex" + return describe("with a file path that breaks out of the root folder", function() { + beforeEach(function() { + this.resource = { + path: "../../main.tex", content: "Hello world" - @fs.writeFile = sinon.stub().callsArg(2) - @ResourceWriter._writeResourceToDisk(@project_id, @resource, @basePath, @callback) + }; + this.fs.writeFile = sinon.stub().callsArg(2); + return this.ResourceWriter._writeResourceToDisk(this.project_id, this.resource, this.basePath, this.callback); + }); - it "should not write to disk", -> - @fs.writeFile.called.should.equal false + it("should not write to disk", function() { + return this.fs.writeFile.called.should.equal(false); + }); - it "should return an error", -> - @callback + return it("should return an error", function() { + return this.callback .calledWith(new Error("resource path is outside root directory")) - .should.equal true + .should.equal(true); + }); + }); + }); - describe "checkPath", -> - describe "with a valid path", -> - beforeEach -> - @ResourceWriter.checkPath("foo", "bar", @callback) + return describe("checkPath", function() { + describe("with a valid path", function() { + beforeEach(function() { + return this.ResourceWriter.checkPath("foo", "bar", this.callback); + }); - it "should return the joined path", -> - @callback.calledWith(null, "foo/bar") - .should.equal true + return it("should return the joined path", function() { + return this.callback.calledWith(null, "foo/bar") + .should.equal(true); + }); + }); - describe "with an invalid path", -> - beforeEach -> - @ResourceWriter.checkPath("foo", "baz/../../bar", @callback) + describe("with an invalid path", function() { + beforeEach(function() { + return this.ResourceWriter.checkPath("foo", "baz/../../bar", this.callback); + }); - it "should return an error", -> - @callback.calledWith(new Error("resource path is outside root directory")) - .should.equal true + return it("should return an error", function() { + return this.callback.calledWith(new Error("resource path is outside root directory")) + .should.equal(true); + }); + }); - describe "with another invalid path matching on a prefix", -> - beforeEach -> - @ResourceWriter.checkPath("foo", "../foobar/baz", @callback) + return describe("with another invalid path matching on a prefix", function() { + beforeEach(function() { + return this.ResourceWriter.checkPath("foo", "../foobar/baz", this.callback); + }); - it "should return an error", -> - @callback.calledWith(new Error("resource path is outside root directory")) - .should.equal true + return it("should return an error", function() { + return this.callback.calledWith(new Error("resource path is outside root directory")) + .should.equal(true); + }); + }); + }); +}); diff --git a/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.js b/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.js index 4a87d64207..9063c1fdcb 100644 --- a/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.js +++ b/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.js @@ -1,158 +1,219 @@ -should = require('chai').should() -SandboxedModule = require('sandboxed-module') -assert = require('assert') -path = require('path') -sinon = require('sinon') -modulePath = path.join __dirname, "../../../app/js/StaticServerForbidSymlinks" -expect = require("chai").expect +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const should = require('chai').should(); +const SandboxedModule = require('sandboxed-module'); +const assert = require('assert'); +const path = require('path'); +const sinon = require('sinon'); +const modulePath = path.join(__dirname, "../../../app/js/StaticServerForbidSymlinks"); +const { expect } = require("chai"); -describe "StaticServerForbidSymlinks", -> +describe("StaticServerForbidSymlinks", function() { - beforeEach -> + beforeEach(function() { - @settings = - path: + this.settings = { + path: { compilesDir: "/compiles/here" + } + }; - @fs = {} - @ForbidSymlinks = SandboxedModule.require modulePath, requires: - "settings-sharelatex":@settings - "logger-sharelatex": - log:-> - warn:-> - error:-> - "fs":@fs + this.fs = {}; + this.ForbidSymlinks = SandboxedModule.require(modulePath, { requires: { + "settings-sharelatex":this.settings, + "logger-sharelatex": { + log() {}, + warn() {}, + error() {} + }, + "fs":this.fs + } + } + ); - @dummyStatic = (rootDir, options) -> - return (req, res, next) -> - # console.log "dummyStatic serving file", rootDir, "called with", req.url - # serve it + this.dummyStatic = (rootDir, options) => + (req, res, next) => + // console.log "dummyStatic serving file", rootDir, "called with", req.url + // serve it next() + + ; - @StaticServerForbidSymlinks = @ForbidSymlinks @dummyStatic, @settings.path.compilesDir - @req = - params: + this.StaticServerForbidSymlinks = this.ForbidSymlinks(this.dummyStatic, this.settings.path.compilesDir); + this.req = { + params: { project_id:"12345" + } + }; - @res = {} - @req.url = "/12345/output.pdf" + this.res = {}; + return this.req.url = "/12345/output.pdf"; + }); - describe "sending a normal file through", -> - beforeEach -> - @fs.realpath = sinon.stub().callsArgWith(1, null, "#{@settings.path.compilesDir}/#{@req.params.project_id}/output.pdf") + describe("sending a normal file through", function() { + beforeEach(function() { + return this.fs.realpath = sinon.stub().callsArgWith(1, null, `${this.settings.path.compilesDir}/${this.req.params.project_id}/output.pdf`); + }); - it "should call next", (done)-> - @res.sendStatus = (resCode)-> - resCode.should.equal 200 - done() - @StaticServerForbidSymlinks @req, @res, done + return it("should call next", function(done){ + this.res.sendStatus = function(resCode){ + resCode.should.equal(200); + return done(); + }; + return this.StaticServerForbidSymlinks(this.req, this.res, done); + }); + }); - describe "with a missing file", -> - beforeEach -> - @fs.realpath = sinon.stub().callsArgWith(1, {code: 'ENOENT'}, "#{@settings.path.compilesDir}/#{@req.params.project_id}/unknown.pdf") + describe("with a missing file", function() { + beforeEach(function() { + return this.fs.realpath = sinon.stub().callsArgWith(1, {code: 'ENOENT'}, `${this.settings.path.compilesDir}/${this.req.params.project_id}/unknown.pdf`); + }); - it "should send a 404", (done)-> - @res.sendStatus = (resCode)-> - resCode.should.equal 404 - done() - @StaticServerForbidSymlinks @req, @res + return it("should send a 404", function(done){ + this.res.sendStatus = function(resCode){ + resCode.should.equal(404); + return done(); + }; + return this.StaticServerForbidSymlinks(this.req, this.res); + }); + }); - describe "with a symlink file", -> - beforeEach -> - @fs.realpath = sinon.stub().callsArgWith(1, null, "/etc/#{@req.params.project_id}/output.pdf") + describe("with a symlink file", function() { + beforeEach(function() { + return this.fs.realpath = sinon.stub().callsArgWith(1, null, `/etc/${this.req.params.project_id}/output.pdf`); + }); - it "should send a 404", (done)-> - @res.sendStatus = (resCode)-> - resCode.should.equal 404 - done() - @StaticServerForbidSymlinks @req, @res + return it("should send a 404", function(done){ + this.res.sendStatus = function(resCode){ + resCode.should.equal(404); + return done(); + }; + return this.StaticServerForbidSymlinks(this.req, this.res); + }); + }); - describe "with a relative file", -> - beforeEach -> - @req.url = "/12345/../67890/output.pdf" + describe("with a relative file", function() { + beforeEach(function() { + return this.req.url = "/12345/../67890/output.pdf"; + }); - it "should send a 404", (done)-> - @res.sendStatus = (resCode)-> - resCode.should.equal 404 - done() - @StaticServerForbidSymlinks @req, @res + return it("should send a 404", function(done){ + this.res.sendStatus = function(resCode){ + resCode.should.equal(404); + return done(); + }; + return this.StaticServerForbidSymlinks(this.req, this.res); + }); + }); - describe "with a unnormalized file containing .", -> - beforeEach -> - @req.url = "/12345/foo/./output.pdf" + describe("with a unnormalized file containing .", function() { + beforeEach(function() { + return this.req.url = "/12345/foo/./output.pdf"; + }); - it "should send a 404", (done)-> - @res.sendStatus = (resCode)-> - resCode.should.equal 404 - done() - @StaticServerForbidSymlinks @req, @res + return it("should send a 404", function(done){ + this.res.sendStatus = function(resCode){ + resCode.should.equal(404); + return done(); + }; + return this.StaticServerForbidSymlinks(this.req, this.res); + }); + }); - describe "with a file containing an empty path", -> - beforeEach -> - @req.url = "/12345/foo//output.pdf" + describe("with a file containing an empty path", function() { + beforeEach(function() { + return this.req.url = "/12345/foo//output.pdf"; + }); - it "should send a 404", (done)-> - @res.sendStatus = (resCode)-> - resCode.should.equal 404 - done() - @StaticServerForbidSymlinks @req, @res + return it("should send a 404", function(done){ + this.res.sendStatus = function(resCode){ + resCode.should.equal(404); + return done(); + }; + return this.StaticServerForbidSymlinks(this.req, this.res); + }); + }); - describe "with a non-project file", -> - beforeEach -> - @req.url = "/.foo/output.pdf" + describe("with a non-project file", function() { + beforeEach(function() { + return this.req.url = "/.foo/output.pdf"; + }); - it "should send a 404", (done)-> - @res.sendStatus = (resCode)-> - resCode.should.equal 404 - done() - @StaticServerForbidSymlinks @req, @res + return it("should send a 404", function(done){ + this.res.sendStatus = function(resCode){ + resCode.should.equal(404); + return done(); + }; + return this.StaticServerForbidSymlinks(this.req, this.res); + }); + }); - describe "with a file outside the compiledir", -> - beforeEach -> - @req.url = "/../bar/output.pdf" + describe("with a file outside the compiledir", function() { + beforeEach(function() { + return this.req.url = "/../bar/output.pdf"; + }); - it "should send a 404", (done)-> - @res.sendStatus = (resCode)-> - resCode.should.equal 404 - done() - @StaticServerForbidSymlinks @req, @res + return it("should send a 404", function(done){ + this.res.sendStatus = function(resCode){ + resCode.should.equal(404); + return done(); + }; + return this.StaticServerForbidSymlinks(this.req, this.res); + }); + }); - describe "with a file with no leading /", -> - beforeEach -> - @req.url = "./../bar/output.pdf" + describe("with a file with no leading /", function() { + beforeEach(function() { + return this.req.url = "./../bar/output.pdf"; + }); - it "should send a 404", (done)-> - @res.sendStatus = (resCode)-> - resCode.should.equal 404 - done() - @StaticServerForbidSymlinks @req, @res + return it("should send a 404", function(done){ + this.res.sendStatus = function(resCode){ + resCode.should.equal(404); + return done(); + }; + return this.StaticServerForbidSymlinks(this.req, this.res); + }); + }); - describe "with a github style path", -> - beforeEach -> - @req.url = "/henryoswald-latex_example/output/output.log" - @fs.realpath = sinon.stub().callsArgWith(1, null, "#{@settings.path.compilesDir}/henryoswald-latex_example/output/output.log") + describe("with a github style path", function() { + beforeEach(function() { + this.req.url = "/henryoswald-latex_example/output/output.log"; + return this.fs.realpath = sinon.stub().callsArgWith(1, null, `${this.settings.path.compilesDir}/henryoswald-latex_example/output/output.log`); + }); - it "should call next", (done)-> - @res.sendStatus = (resCode)-> - resCode.should.equal 200 - done() - @StaticServerForbidSymlinks @req, @res, done + return it("should call next", function(done){ + this.res.sendStatus = function(resCode){ + resCode.should.equal(200); + return done(); + }; + return this.StaticServerForbidSymlinks(this.req, this.res, done); + }); + }); - describe "with an error from fs.realpath", -> + return describe("with an error from fs.realpath", function() { - beforeEach -> - @fs.realpath = sinon.stub().callsArgWith(1, "error") + beforeEach(function() { + return this.fs.realpath = sinon.stub().callsArgWith(1, "error"); + }); - it "should send a 500", (done)-> - @res.sendStatus = (resCode)-> - resCode.should.equal 500 - done() - @StaticServerForbidSymlinks @req, @res + return it("should send a 500", function(done){ + this.res.sendStatus = function(resCode){ + resCode.should.equal(500); + return done(); + }; + return this.StaticServerForbidSymlinks(this.req, this.res); + }); + }); +}); diff --git a/services/clsi/test/unit/coffee/TikzManager.js b/services/clsi/test/unit/coffee/TikzManager.js index 69968aa5ea..c792fabfb7 100644 --- a/services/clsi/test/unit/coffee/TikzManager.js +++ b/services/clsi/test/unit/coffee/TikzManager.js @@ -1,117 +1,150 @@ -SandboxedModule = require('sandboxed-module') -sinon = require('sinon') -require('chai').should() -modulePath = require('path').join __dirname, '../../../app/js/TikzManager' +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module'); +const sinon = require('sinon'); +require('chai').should(); +const modulePath = require('path').join(__dirname, '../../../app/js/TikzManager'); -describe 'TikzManager', -> - beforeEach -> - @TikzManager = SandboxedModule.require modulePath, requires: - "./ResourceWriter": @ResourceWriter = {} - "./SafeReader": @SafeReader = {} - "fs": @fs = {} - "logger-sharelatex": @logger = {log: () ->} +describe('TikzManager', function() { + beforeEach(function() { + return this.TikzManager = SandboxedModule.require(modulePath, { requires: { + "./ResourceWriter": (this.ResourceWriter = {}), + "./SafeReader": (this.SafeReader = {}), + "fs": (this.fs = {}), + "logger-sharelatex": (this.logger = {log() {}}) + } + });}); - describe "checkMainFile", -> - beforeEach -> - @compileDir = "compile-dir" - @mainFile = "main.tex" - @callback = sinon.stub() + describe("checkMainFile", function() { + beforeEach(function() { + this.compileDir = "compile-dir"; + this.mainFile = "main.tex"; + return this.callback = sinon.stub(); + }); - describe "if there is already an output.tex file in the resources", -> - beforeEach -> - @resources = [{path:"main.tex"},{path:"output.tex"}] - @TikzManager.checkMainFile @compileDir, @mainFile, @resources, @callback + describe("if there is already an output.tex file in the resources", function() { + beforeEach(function() { + this.resources = [{path:"main.tex"},{path:"output.tex"}]; + return this.TikzManager.checkMainFile(this.compileDir, this.mainFile, this.resources, this.callback); + }); - it "should call the callback with false ", -> - @callback.calledWithExactly(null, false) - .should.equal true + return it("should call the callback with false ", function() { + return this.callback.calledWithExactly(null, false) + .should.equal(true); + }); + }); - describe "if there is no output.tex file in the resources", -> - beforeEach -> - @resources = [{path:"main.tex"}] - @ResourceWriter.checkPath = sinon.stub() - .withArgs(@compileDir, @mainFile) - .callsArgWith(2, null, "#{@compileDir}/#{@mainFile}") + return describe("if there is no output.tex file in the resources", function() { + beforeEach(function() { + this.resources = [{path:"main.tex"}]; + return this.ResourceWriter.checkPath = sinon.stub() + .withArgs(this.compileDir, this.mainFile) + .callsArgWith(2, null, `${this.compileDir}/${this.mainFile}`); + }); - describe "and the main file contains tikzexternalize", -> - beforeEach -> - @SafeReader.readFile = sinon.stub() - .withArgs("#{@compileDir}/#{@mainFile}") - .callsArgWith(3, null, "hello \\tikzexternalize") - @TikzManager.checkMainFile @compileDir, @mainFile, @resources, @callback + describe("and the main file contains tikzexternalize", function() { + beforeEach(function() { + this.SafeReader.readFile = sinon.stub() + .withArgs(`${this.compileDir}/${this.mainFile}`) + .callsArgWith(3, null, "hello \\tikzexternalize"); + return this.TikzManager.checkMainFile(this.compileDir, this.mainFile, this.resources, this.callback); + }); - it "should look at the file on disk", -> - @SafeReader.readFile - .calledWith("#{@compileDir}/#{@mainFile}") - .should.equal true + it("should look at the file on disk", function() { + return this.SafeReader.readFile + .calledWith(`${this.compileDir}/${this.mainFile}`) + .should.equal(true); + }); - it "should call the callback with true ", -> - @callback.calledWithExactly(null, true) - .should.equal true + return it("should call the callback with true ", function() { + return this.callback.calledWithExactly(null, true) + .should.equal(true); + }); + }); - describe "and the main file does not contain tikzexternalize", -> - beforeEach -> - @SafeReader.readFile = sinon.stub() - .withArgs("#{@compileDir}/#{@mainFile}") - .callsArgWith(3, null, "hello") - @TikzManager.checkMainFile @compileDir, @mainFile, @resources, @callback + describe("and the main file does not contain tikzexternalize", function() { + beforeEach(function() { + this.SafeReader.readFile = sinon.stub() + .withArgs(`${this.compileDir}/${this.mainFile}`) + .callsArgWith(3, null, "hello"); + return this.TikzManager.checkMainFile(this.compileDir, this.mainFile, this.resources, this.callback); + }); - it "should look at the file on disk", -> - @SafeReader.readFile - .calledWith("#{@compileDir}/#{@mainFile}") - .should.equal true + it("should look at the file on disk", function() { + return this.SafeReader.readFile + .calledWith(`${this.compileDir}/${this.mainFile}`) + .should.equal(true); + }); - it "should call the callback with false", -> - @callback.calledWithExactly(null, false) - .should.equal true + return it("should call the callback with false", function() { + return this.callback.calledWithExactly(null, false) + .should.equal(true); + }); + }); - describe "and the main file contains \\usepackage{pstool}", -> - beforeEach -> - @SafeReader.readFile = sinon.stub() - .withArgs("#{@compileDir}/#{@mainFile}") - .callsArgWith(3, null, "hello \\usepackage[random-options]{pstool}") - @TikzManager.checkMainFile @compileDir, @mainFile, @resources, @callback + return describe("and the main file contains \\usepackage{pstool}", function() { + beforeEach(function() { + this.SafeReader.readFile = sinon.stub() + .withArgs(`${this.compileDir}/${this.mainFile}`) + .callsArgWith(3, null, "hello \\usepackage[random-options]{pstool}"); + return this.TikzManager.checkMainFile(this.compileDir, this.mainFile, this.resources, this.callback); + }); - it "should look at the file on disk", -> - @SafeReader.readFile - .calledWith("#{@compileDir}/#{@mainFile}") - .should.equal true + it("should look at the file on disk", function() { + return this.SafeReader.readFile + .calledWith(`${this.compileDir}/${this.mainFile}`) + .should.equal(true); + }); - it "should call the callback with true ", -> - @callback.calledWithExactly(null, true) - .should.equal true + return it("should call the callback with true ", function() { + return this.callback.calledWithExactly(null, true) + .should.equal(true); + }); + }); + }); + }); - describe "injectOutputFile", -> - beforeEach -> - @rootDir = "/mock" - @filename = "filename.tex" - @callback = sinon.stub() - @content = ''' - \\documentclass{article} - \\usepackage{tikz} - \\tikzexternalize - \\begin{document} - Hello world - \\end{document} - ''' - @fs.readFile = sinon.stub().callsArgWith(2, null, @content) - @fs.writeFile = sinon.stub().callsArg(3) - @ResourceWriter.checkPath = sinon.stub().callsArgWith(2, null, "#{@rootDir}/#{@filename}") - @TikzManager.injectOutputFile @rootDir, @filename, @callback + return describe("injectOutputFile", function() { + beforeEach(function() { + this.rootDir = "/mock"; + this.filename = "filename.tex"; + this.callback = sinon.stub(); + this.content = `\ +\\documentclass{article} +\\usepackage{tikz} +\\tikzexternalize +\\begin{document} +Hello world +\\end{document}\ +`; + this.fs.readFile = sinon.stub().callsArgWith(2, null, this.content); + this.fs.writeFile = sinon.stub().callsArg(3); + this.ResourceWriter.checkPath = sinon.stub().callsArgWith(2, null, `${this.rootDir}/${this.filename}`); + return this.TikzManager.injectOutputFile(this.rootDir, this.filename, this.callback); + }); - it "sould check the path", -> - @ResourceWriter.checkPath.calledWith(@rootDir, @filename) - .should.equal true + it("sould check the path", function() { + return this.ResourceWriter.checkPath.calledWith(this.rootDir, this.filename) + .should.equal(true); + }); - it "should read the file", -> - @fs.readFile - .calledWith("#{@rootDir}/#{@filename}", "utf8") - .should.equal true + it("should read the file", function() { + return this.fs.readFile + .calledWith(`${this.rootDir}/${this.filename}`, "utf8") + .should.equal(true); + }); - it "should write out the same file as output.tex", -> - @fs.writeFile - .calledWith("#{@rootDir}/output.tex", @content, {flag: 'wx'}) - .should.equal true + it("should write out the same file as output.tex", function() { + return this.fs.writeFile + .calledWith(`${this.rootDir}/output.tex`, this.content, {flag: 'wx'}) + .should.equal(true); + }); - it "should call the callback", -> - @callback.called.should.equal true + return it("should call the callback", function() { + return this.callback.called.should.equal(true); + }); + }); +}); diff --git a/services/clsi/test/unit/coffee/UrlCacheTests.js b/services/clsi/test/unit/coffee/UrlCacheTests.js index 36a11cbbd2..a3af0081a6 100644 --- a/services/clsi/test/unit/coffee/UrlCacheTests.js +++ b/services/clsi/test/unit/coffee/UrlCacheTests.js @@ -1,200 +1,262 @@ -SandboxedModule = require('sandboxed-module') -sinon = require('sinon') -require('chai').should() -modulePath = require('path').join __dirname, '../../../app/js/UrlCache' -EventEmitter = require("events").EventEmitter +/* + * 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 SandboxedModule = require('sandboxed-module'); +const sinon = require('sinon'); +require('chai').should(); +const modulePath = require('path').join(__dirname, '../../../app/js/UrlCache'); +const { EventEmitter } = require("events"); -describe "UrlCache", -> - beforeEach -> - @callback = sinon.stub() - @url = "www.example.com/file" - @project_id = "project-id-123" - @UrlCache = SandboxedModule.require modulePath, requires: - "./db" : {} - "./UrlFetcher" : @UrlFetcher = {} - "logger-sharelatex": @logger = {log: sinon.stub()} - "settings-sharelatex": @Settings = { path: clsiCacheDir: "/cache/dir" } - "fs": @fs = {} +describe("UrlCache", function() { + beforeEach(function() { + this.callback = sinon.stub(); + this.url = "www.example.com/file"; + this.project_id = "project-id-123"; + return this.UrlCache = SandboxedModule.require(modulePath, { requires: { + "./db" : {}, + "./UrlFetcher" : (this.UrlFetcher = {}), + "logger-sharelatex": (this.logger = {log: sinon.stub()}), + "settings-sharelatex": (this.Settings = { path: {clsiCacheDir: "/cache/dir"} }), + "fs": (this.fs = {}) + } + });}); - describe "_doesUrlNeedDownloading", -> - beforeEach -> - @lastModified = new Date() - @lastModifiedRoundedToSeconds = new Date(Math.floor(@lastModified.getTime() / 1000) * 1000) + describe("_doesUrlNeedDownloading", function() { + beforeEach(function() { + this.lastModified = new Date(); + return this.lastModifiedRoundedToSeconds = new Date(Math.floor(this.lastModified.getTime() / 1000) * 1000); + }); - describe "when URL does not exist in cache", -> - beforeEach -> - @UrlCache._findUrlDetails = sinon.stub().callsArgWith(2, null, null) - @UrlCache._doesUrlNeedDownloading(@project_id, @url, @lastModified, @callback) + describe("when URL does not exist in cache", function() { + beforeEach(function() { + this.UrlCache._findUrlDetails = sinon.stub().callsArgWith(2, null, null); + return this.UrlCache._doesUrlNeedDownloading(this.project_id, this.url, this.lastModified, this.callback); + }); - it "should return the callback with true", -> - @callback.calledWith(null, true).should.equal true + return it("should return the callback with true", function() { + return this.callback.calledWith(null, true).should.equal(true); + }); + }); - describe "when URL does exist in cache", -> - beforeEach -> - @urlDetails = {} - @UrlCache._findUrlDetails = sinon.stub().callsArgWith(2, null, @urlDetails) + return describe("when URL does exist in cache", function() { + beforeEach(function() { + this.urlDetails = {}; + return this.UrlCache._findUrlDetails = sinon.stub().callsArgWith(2, null, this.urlDetails); + }); - describe "when the modified date is more recent than the cached modified date", -> - beforeEach -> - @urlDetails.lastModified = new Date(@lastModified.getTime() - 1000) - @UrlCache._doesUrlNeedDownloading(@project_id, @url, @lastModified, @callback) + describe("when the modified date is more recent than the cached modified date", function() { + beforeEach(function() { + this.urlDetails.lastModified = new Date(this.lastModified.getTime() - 1000); + return this.UrlCache._doesUrlNeedDownloading(this.project_id, this.url, this.lastModified, this.callback); + }); - it "should get the url details", -> - @UrlCache._findUrlDetails - .calledWith(@project_id, @url) - .should.equal true + it("should get the url details", function() { + return this.UrlCache._findUrlDetails + .calledWith(this.project_id, this.url) + .should.equal(true); + }); - it "should return the callback with true", -> - @callback.calledWith(null, true).should.equal true + return it("should return the callback with true", function() { + return this.callback.calledWith(null, true).should.equal(true); + }); + }); - describe "when the cached modified date is more recent than the modified date", -> - beforeEach -> - @urlDetails.lastModified = new Date(@lastModified.getTime() + 1000) - @UrlCache._doesUrlNeedDownloading(@project_id, @url, @lastModified, @callback) + describe("when the cached modified date is more recent than the modified date", function() { + beforeEach(function() { + this.urlDetails.lastModified = new Date(this.lastModified.getTime() + 1000); + return this.UrlCache._doesUrlNeedDownloading(this.project_id, this.url, this.lastModified, this.callback); + }); - it "should return the callback with false", -> - @callback.calledWith(null, false).should.equal true + return it("should return the callback with false", function() { + return this.callback.calledWith(null, false).should.equal(true); + }); + }); - describe "when the cached modified date is equal to the modified date", -> - beforeEach -> - @urlDetails.lastModified = @lastModified - @UrlCache._doesUrlNeedDownloading(@project_id, @url, @lastModified, @callback) + describe("when the cached modified date is equal to the modified date", function() { + beforeEach(function() { + this.urlDetails.lastModified = this.lastModified; + return this.UrlCache._doesUrlNeedDownloading(this.project_id, this.url, this.lastModified, this.callback); + }); - it "should return the callback with false", -> - @callback.calledWith(null, false).should.equal true + return it("should return the callback with false", function() { + return this.callback.calledWith(null, false).should.equal(true); + }); + }); - describe "when the provided modified date does not exist", -> - beforeEach -> - @lastModified = null - @UrlCache._doesUrlNeedDownloading(@project_id, @url, @lastModified, @callback) + describe("when the provided modified date does not exist", function() { + beforeEach(function() { + this.lastModified = null; + return this.UrlCache._doesUrlNeedDownloading(this.project_id, this.url, this.lastModified, this.callback); + }); - it "should return the callback with true", -> - @callback.calledWith(null, true).should.equal true + return it("should return the callback with true", function() { + return this.callback.calledWith(null, true).should.equal(true); + }); + }); - describe "when the URL does not have a modified date", -> - beforeEach -> - @urlDetails.lastModified = null - @UrlCache._doesUrlNeedDownloading(@project_id, @url, @lastModified, @callback) + return describe("when the URL does not have a modified date", function() { + beforeEach(function() { + this.urlDetails.lastModified = null; + return this.UrlCache._doesUrlNeedDownloading(this.project_id, this.url, this.lastModified, this.callback); + }); - it "should return the callback with true", -> - @callback.calledWith(null, true).should.equal true + return it("should return the callback with true", function() { + return this.callback.calledWith(null, true).should.equal(true); + }); + }); + }); + }); - describe "_ensureUrlIsInCache", -> - beforeEach -> - @UrlFetcher.pipeUrlToFile = sinon.stub().callsArg(2) - @UrlCache._updateOrCreateUrlDetails = sinon.stub().callsArg(3) + describe("_ensureUrlIsInCache", function() { + beforeEach(function() { + this.UrlFetcher.pipeUrlToFile = sinon.stub().callsArg(2); + return this.UrlCache._updateOrCreateUrlDetails = sinon.stub().callsArg(3); + }); - describe "when the URL needs updating", -> - beforeEach -> - @UrlCache._doesUrlNeedDownloading = sinon.stub().callsArgWith(3, null, true) - @UrlCache._ensureUrlIsInCache(@project_id, @url, @lastModified, @callback) + describe("when the URL needs updating", function() { + beforeEach(function() { + this.UrlCache._doesUrlNeedDownloading = sinon.stub().callsArgWith(3, null, true); + return this.UrlCache._ensureUrlIsInCache(this.project_id, this.url, this.lastModified, this.callback); + }); - it "should check that the url needs downloading", -> - @UrlCache._doesUrlNeedDownloading - .calledWith(@project_id, @url, @lastModifiedRoundedToSeconds) - .should.equal true + it("should check that the url needs downloading", function() { + return this.UrlCache._doesUrlNeedDownloading + .calledWith(this.project_id, this.url, this.lastModifiedRoundedToSeconds) + .should.equal(true); + }); - it "should download the URL to the cache file", -> - @UrlFetcher.pipeUrlToFile - .calledWith(@url, @UrlCache._cacheFilePathForUrl(@project_id, @url)) - .should.equal true + it("should download the URL to the cache file", function() { + return this.UrlFetcher.pipeUrlToFile + .calledWith(this.url, this.UrlCache._cacheFilePathForUrl(this.project_id, this.url)) + .should.equal(true); + }); - it "should update the database entry", -> - @UrlCache._updateOrCreateUrlDetails - .calledWith(@project_id, @url, @lastModifiedRoundedToSeconds) - .should.equal true + it("should update the database entry", function() { + return this.UrlCache._updateOrCreateUrlDetails + .calledWith(this.project_id, this.url, this.lastModifiedRoundedToSeconds) + .should.equal(true); + }); - it "should return the callback with the cache file path", -> - @callback - .calledWith(null, @UrlCache._cacheFilePathForUrl(@project_id, @url)) - .should.equal true + return it("should return the callback with the cache file path", function() { + return this.callback + .calledWith(null, this.UrlCache._cacheFilePathForUrl(this.project_id, this.url)) + .should.equal(true); + }); + }); - describe "when the URL does not need updating", -> - beforeEach -> - @UrlCache._doesUrlNeedDownloading = sinon.stub().callsArgWith(3, null, false) - @UrlCache._ensureUrlIsInCache(@project_id, @url, @lastModified, @callback) + return describe("when the URL does not need updating", function() { + beforeEach(function() { + this.UrlCache._doesUrlNeedDownloading = sinon.stub().callsArgWith(3, null, false); + return this.UrlCache._ensureUrlIsInCache(this.project_id, this.url, this.lastModified, this.callback); + }); - it "should not download the URL to the cache file", -> - @UrlFetcher.pipeUrlToFile - .called.should.equal false + it("should not download the URL to the cache file", function() { + return this.UrlFetcher.pipeUrlToFile + .called.should.equal(false); + }); - it "should return the callback with the cache file path", -> - @callback - .calledWith(null, @UrlCache._cacheFilePathForUrl(@project_id, @url)) - .should.equal true + return it("should return the callback with the cache file path", function() { + return this.callback + .calledWith(null, this.UrlCache._cacheFilePathForUrl(this.project_id, this.url)) + .should.equal(true); + }); + }); + }); - describe "downloadUrlToFile", -> - beforeEach -> - @cachePath = "path/to/cached/url" - @destPath = "path/to/destination" - @UrlCache._copyFile = sinon.stub().callsArg(2) - @UrlCache._ensureUrlIsInCache = sinon.stub().callsArgWith(3, null, @cachePath) - @UrlCache.downloadUrlToFile(@project_id, @url, @destPath, @lastModified, @callback) + describe("downloadUrlToFile", function() { + beforeEach(function() { + this.cachePath = "path/to/cached/url"; + this.destPath = "path/to/destination"; + this.UrlCache._copyFile = sinon.stub().callsArg(2); + this.UrlCache._ensureUrlIsInCache = sinon.stub().callsArgWith(3, null, this.cachePath); + return this.UrlCache.downloadUrlToFile(this.project_id, this.url, this.destPath, this.lastModified, this.callback); + }); - it "should ensure the URL is downloaded and updated in the cache", -> - @UrlCache._ensureUrlIsInCache - .calledWith(@project_id, @url, @lastModified) - .should.equal true + it("should ensure the URL is downloaded and updated in the cache", function() { + return this.UrlCache._ensureUrlIsInCache + .calledWith(this.project_id, this.url, this.lastModified) + .should.equal(true); + }); - it "should copy the file to the new location", -> - @UrlCache._copyFile - .calledWith(@cachePath, @destPath) - .should.equal true + it("should copy the file to the new location", function() { + return this.UrlCache._copyFile + .calledWith(this.cachePath, this.destPath) + .should.equal(true); + }); - it "should call the callback", -> - @callback.called.should.equal true + return it("should call the callback", function() { + return this.callback.called.should.equal(true); + }); + }); - describe "_deleteUrlCacheFromDisk", -> - beforeEach -> - @fs.unlink = sinon.stub().callsArg(1) - @UrlCache._deleteUrlCacheFromDisk(@project_id, @url, @callback) + describe("_deleteUrlCacheFromDisk", function() { + beforeEach(function() { + this.fs.unlink = sinon.stub().callsArg(1); + return this.UrlCache._deleteUrlCacheFromDisk(this.project_id, this.url, this.callback); + }); - it "should delete the cache file", -> - @fs.unlink - .calledWith(@UrlCache._cacheFilePathForUrl(@project_id, @url)) - .should.equal true + it("should delete the cache file", function() { + return this.fs.unlink + .calledWith(this.UrlCache._cacheFilePathForUrl(this.project_id, this.url)) + .should.equal(true); + }); - it "should call the callback", -> - @callback.called.should.equal true + return it("should call the callback", function() { + return this.callback.called.should.equal(true); + }); + }); - describe "_clearUrlFromCache", -> - beforeEach -> - @UrlCache._deleteUrlCacheFromDisk = sinon.stub().callsArg(2) - @UrlCache._clearUrlDetails = sinon.stub().callsArg(2) - @UrlCache._clearUrlFromCache @project_id, @url, @callback + describe("_clearUrlFromCache", function() { + beforeEach(function() { + this.UrlCache._deleteUrlCacheFromDisk = sinon.stub().callsArg(2); + this.UrlCache._clearUrlDetails = sinon.stub().callsArg(2); + return this.UrlCache._clearUrlFromCache(this.project_id, this.url, this.callback); + }); - it "should delete the file on disk", -> - @UrlCache._deleteUrlCacheFromDisk - .calledWith(@project_id, @url) - .should.equal true + it("should delete the file on disk", function() { + return this.UrlCache._deleteUrlCacheFromDisk + .calledWith(this.project_id, this.url) + .should.equal(true); + }); - it "should clear the entry in the database", -> - @UrlCache._clearUrlDetails - .calledWith(@project_id, @url) - .should.equal true + it("should clear the entry in the database", function() { + return this.UrlCache._clearUrlDetails + .calledWith(this.project_id, this.url) + .should.equal(true); + }); - it "should call the callback", -> - @callback.called.should.equal true + return it("should call the callback", function() { + return this.callback.called.should.equal(true); + }); + }); - describe "clearProject", -> - beforeEach -> - @urls = [ - "www.example.com/file1" + return describe("clearProject", function() { + beforeEach(function() { + this.urls = [ + "www.example.com/file1", "www.example.com/file2" - ] - @UrlCache._findAllUrlsInProject = sinon.stub().callsArgWith(1, null, @urls) - @UrlCache._clearUrlFromCache = sinon.stub().callsArg(2) - @UrlCache.clearProject @project_id, @callback + ]; + this.UrlCache._findAllUrlsInProject = sinon.stub().callsArgWith(1, null, this.urls); + this.UrlCache._clearUrlFromCache = sinon.stub().callsArg(2); + return this.UrlCache.clearProject(this.project_id, this.callback); + }); - it "should clear the cache for each url in the project", -> - for url in @urls - @UrlCache._clearUrlFromCache - .calledWith(@project_id, url) - .should.equal true + it("should clear the cache for each url in the project", function() { + return Array.from(this.urls).map((url) => + this.UrlCache._clearUrlFromCache + .calledWith(this.project_id, url) + .should.equal(true)); + }); - it "should call the callback", -> - @callback.called.should.equal true + return it("should call the callback", function() { + return this.callback.called.should.equal(true); + }); + }); +}); diff --git a/services/clsi/test/unit/coffee/UrlFetcherTests.js b/services/clsi/test/unit/coffee/UrlFetcherTests.js index e91720e52b..21258ab83d 100644 --- a/services/clsi/test/unit/coffee/UrlFetcherTests.js +++ b/services/clsi/test/unit/coffee/UrlFetcherTests.js @@ -1,120 +1,154 @@ -SandboxedModule = require('sandboxed-module') -sinon = require('sinon') -require('chai').should() -modulePath = require('path').join __dirname, '../../../app/js/UrlFetcher' -EventEmitter = require("events").EventEmitter +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module'); +const sinon = require('sinon'); +require('chai').should(); +const modulePath = require('path').join(__dirname, '../../../app/js/UrlFetcher'); +const { EventEmitter } = require("events"); -describe "UrlFetcher", -> - beforeEach -> - @callback = sinon.stub() - @url = "https://www.example.com/file/here?query=string" - @UrlFetcher = SandboxedModule.require modulePath, requires: - request: defaults: @defaults = sinon.stub().returns(@request = {}) - fs: @fs = {} - "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } - "settings-sharelatex": @settings = {} +describe("UrlFetcher", function() { + beforeEach(function() { + this.callback = sinon.stub(); + this.url = "https://www.example.com/file/here?query=string"; + return this.UrlFetcher = SandboxedModule.require(modulePath, { requires: { + request: { defaults: (this.defaults = sinon.stub().returns(this.request = {})) + }, + fs: (this.fs = {}), + "logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub() }), + "settings-sharelatex": (this.settings = {}) + } + });}); - it "should turn off the cookie jar in request", -> - @defaults.calledWith(jar: false) - .should.equal true + it("should turn off the cookie jar in request", function() { + return this.defaults.calledWith({jar: false}) + .should.equal(true); + }); - describe "rewrite url domain if filestoreDomainOveride is set", -> - beforeEach -> - @path = "/path/to/file/on/disk" - @request.get = sinon.stub().returns(@urlStream = new EventEmitter) - @urlStream.pipe = sinon.stub() - @urlStream.pause = sinon.stub() - @urlStream.resume = sinon.stub() - @fs.createWriteStream = sinon.stub().returns(@fileStream = new EventEmitter) - @fs.unlink = (file, callback) -> callback() + describe("rewrite url domain if filestoreDomainOveride is set", function() { + beforeEach(function() { + this.path = "/path/to/file/on/disk"; + this.request.get = sinon.stub().returns(this.urlStream = new EventEmitter); + this.urlStream.pipe = sinon.stub(); + this.urlStream.pause = sinon.stub(); + this.urlStream.resume = sinon.stub(); + this.fs.createWriteStream = sinon.stub().returns(this.fileStream = new EventEmitter); + return this.fs.unlink = (file, callback) => callback(); + }); - it "should use the normal domain when override not set", (done)-> - @UrlFetcher.pipeUrlToFile @url, @path, => - @request.get.args[0][0].url.should.equal @url - done() - @res = statusCode: 200 - @urlStream.emit "response", @res - @urlStream.emit "end" - @fileStream.emit "finish" + it("should use the normal domain when override not set", function(done){ + this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { + this.request.get.args[0][0].url.should.equal(this.url); + return done(); + }); + this.res = {statusCode: 200}; + this.urlStream.emit("response", this.res); + this.urlStream.emit("end"); + return this.fileStream.emit("finish"); + }); - it "should use override domain when filestoreDomainOveride is set", (done)-> - @settings.filestoreDomainOveride = "192.11.11.11" - @UrlFetcher.pipeUrlToFile @url, @path, => - @request.get.args[0][0].url.should.equal "192.11.11.11/file/here?query=string" - done() - @res = statusCode: 200 - @urlStream.emit "response", @res - @urlStream.emit "end" - @fileStream.emit "finish" + return it("should use override domain when filestoreDomainOveride is set", function(done){ + this.settings.filestoreDomainOveride = "192.11.11.11"; + this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { + this.request.get.args[0][0].url.should.equal("192.11.11.11/file/here?query=string"); + return done(); + }); + this.res = {statusCode: 200}; + this.urlStream.emit("response", this.res); + this.urlStream.emit("end"); + return this.fileStream.emit("finish"); + }); + }); - describe "pipeUrlToFile", -> - beforeEach (done)-> - @path = "/path/to/file/on/disk" - @request.get = sinon.stub().returns(@urlStream = new EventEmitter) - @urlStream.pipe = sinon.stub() - @urlStream.pause = sinon.stub() - @urlStream.resume = sinon.stub() - @fs.createWriteStream = sinon.stub().returns(@fileStream = new EventEmitter) - @fs.unlink = (file, callback) -> callback() - done() + return describe("pipeUrlToFile", function() { + beforeEach(function(done){ + this.path = "/path/to/file/on/disk"; + this.request.get = sinon.stub().returns(this.urlStream = new EventEmitter); + this.urlStream.pipe = sinon.stub(); + this.urlStream.pause = sinon.stub(); + this.urlStream.resume = sinon.stub(); + this.fs.createWriteStream = sinon.stub().returns(this.fileStream = new EventEmitter); + this.fs.unlink = (file, callback) => callback(); + return done(); + }); - describe "successfully", -> - beforeEach (done)-> - @UrlFetcher.pipeUrlToFile @url, @path, => - @callback() - done() - @res = statusCode: 200 - @urlStream.emit "response", @res - @urlStream.emit "end" - @fileStream.emit "finish" + describe("successfully", function() { + beforeEach(function(done){ + this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { + this.callback(); + return done(); + }); + this.res = {statusCode: 200}; + this.urlStream.emit("response", this.res); + this.urlStream.emit("end"); + return this.fileStream.emit("finish"); + }); - it "should request the URL", -> - @request.get - .calledWith(sinon.match {"url": @url}) - .should.equal true + it("should request the URL", function() { + return this.request.get + .calledWith(sinon.match({"url": this.url})) + .should.equal(true); + }); - it "should open the file for writing", -> - @fs.createWriteStream - .calledWith(@path) - .should.equal true + it("should open the file for writing", function() { + return this.fs.createWriteStream + .calledWith(this.path) + .should.equal(true); + }); - it "should pipe the URL to the file", -> - @urlStream.pipe - .calledWith(@fileStream) - .should.equal true + it("should pipe the URL to the file", function() { + return this.urlStream.pipe + .calledWith(this.fileStream) + .should.equal(true); + }); - it "should call the callback", -> - @callback.called.should.equal true + return it("should call the callback", function() { + return this.callback.called.should.equal(true); + }); + }); - describe "with non success status code", -> - beforeEach (done)-> - @UrlFetcher.pipeUrlToFile @url, @path, (err)=> - @callback(err) - done() - @res = statusCode: 404 - @urlStream.emit "response", @res - @urlStream.emit "end" + describe("with non success status code", function() { + beforeEach(function(done){ + this.UrlFetcher.pipeUrlToFile(this.url, this.path, err=> { + this.callback(err); + return done(); + }); + this.res = {statusCode: 404}; + this.urlStream.emit("response", this.res); + return this.urlStream.emit("end"); + }); - it "should call the callback with an error", -> - @callback + return it("should call the callback with an error", function() { + return this.callback .calledWith(new Error("URL returned non-success status code: 404")) - .should.equal true + .should.equal(true); + }); + }); - describe "with error", -> - beforeEach (done)-> - @UrlFetcher.pipeUrlToFile @url, @path, (err)=> - @callback(err) - done() - @urlStream.emit "error", @error = new Error("something went wrong") + return describe("with error", function() { + beforeEach(function(done){ + this.UrlFetcher.pipeUrlToFile(this.url, this.path, err=> { + this.callback(err); + return done(); + }); + return this.urlStream.emit("error", (this.error = new Error("something went wrong"))); + }); - it "should call the callback with the error", -> - @callback - .calledWith(@error) - .should.equal true + it("should call the callback with the error", function() { + return this.callback + .calledWith(this.error) + .should.equal(true); + }); - it "should only call the callback once, even if end is called", -> - @urlStream.emit "end" - @callback.calledOnce.should.equal true + return it("should only call the callback once, even if end is called", function() { + this.urlStream.emit("end"); + return this.callback.calledOnce.should.equal(true); + }); + }); + }); +}); From 4c79f223898fb2288527ab07f7ef17fe131aeacc Mon Sep 17 00:00:00 2001 From: decaffeinate <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:15:25 +0100 Subject: [PATCH 523/754] decaffeinate: Run post-processing cleanups on CompileControllerTests.coffee and 17 other files --- services/clsi/test/unit/coffee/CompileControllerTests.js | 6 ++++++ services/clsi/test/unit/coffee/CompileManagerTests.js | 9 +++++++++ services/clsi/test/unit/coffee/ContentTypeMapperTests.js | 7 +++++++ services/clsi/test/unit/coffee/DockerLockManagerTests.js | 5 +++++ services/clsi/test/unit/coffee/DockerRunnerTests.js | 9 ++++++++- services/clsi/test/unit/coffee/DraftModeManagerTests.js | 5 +++++ services/clsi/test/unit/coffee/LatexRunnerTests.js | 6 ++++++ services/clsi/test/unit/coffee/LockManagerTests.js | 6 ++++++ services/clsi/test/unit/coffee/OutputFileFinderTests.js | 7 +++++++ .../clsi/test/unit/coffee/OutputFileOptimiserTests.js | 7 +++++++ .../test/unit/coffee/ProjectPersistenceManagerTests.js | 7 +++++++ services/clsi/test/unit/coffee/RequestParserTests.js | 8 +++++++- .../clsi/test/unit/coffee/ResourceStateManagerTests.js | 6 ++++++ services/clsi/test/unit/coffee/ResourceWriterTests.js | 5 +++++ .../test/unit/coffee/StaticServerForbidSymlinksTests.js | 6 ++++++ services/clsi/test/unit/coffee/TikzManager.js | 5 +++++ services/clsi/test/unit/coffee/UrlCacheTests.js | 6 ++++++ services/clsi/test/unit/coffee/UrlFetcherTests.js | 5 +++++ 18 files changed, 113 insertions(+), 2 deletions(-) diff --git a/services/clsi/test/unit/coffee/CompileControllerTests.js b/services/clsi/test/unit/coffee/CompileControllerTests.js index 1defed70d1..2a06fbc30e 100644 --- a/services/clsi/test/unit/coffee/CompileControllerTests.js +++ b/services/clsi/test/unit/coffee/CompileControllerTests.js @@ -1,3 +1,9 @@ +/* eslint-disable + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.js b/services/clsi/test/unit/coffee/CompileManagerTests.js index 5675ac155b..e798aec390 100644 --- a/services/clsi/test/unit/coffee/CompileManagerTests.js +++ b/services/clsi/test/unit/coffee/CompileManagerTests.js @@ -1,3 +1,12 @@ +/* eslint-disable + camelcase, + chai-friendly/no-unused-expressions, + no-path-concat, + 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 diff --git a/services/clsi/test/unit/coffee/ContentTypeMapperTests.js b/services/clsi/test/unit/coffee/ContentTypeMapperTests.js index 64a6091453..bbde292318 100644 --- a/services/clsi/test/unit/coffee/ContentTypeMapperTests.js +++ b/services/clsi/test/unit/coffee/ContentTypeMapperTests.js @@ -1,3 +1,10 @@ +/* eslint-disable + camelcase, + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns diff --git a/services/clsi/test/unit/coffee/DockerLockManagerTests.js b/services/clsi/test/unit/coffee/DockerLockManagerTests.js index 5ef3ca2c8e..155a2464fd 100644 --- a/services/clsi/test/unit/coffee/DockerLockManagerTests.js +++ b/services/clsi/test/unit/coffee/DockerLockManagerTests.js @@ -1,3 +1,8 @@ +/* eslint-disable + no-return-assign, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from diff --git a/services/clsi/test/unit/coffee/DockerRunnerTests.js b/services/clsi/test/unit/coffee/DockerRunnerTests.js index 79ac5df1e5..152b8b9637 100644 --- a/services/clsi/test/unit/coffee/DockerRunnerTests.js +++ b/services/clsi/test/unit/coffee/DockerRunnerTests.js @@ -1,3 +1,10 @@ +/* eslint-disable + 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 @@ -247,7 +254,7 @@ describe("DockerRunner", function() { this.container.inspect = sinon.stub().callsArgWith(0); this.container.start = sinon.stub().yields(); - return this.DockerRunner.startContainer(this.options, this.volumes, this.callback, function() {}); + return this.DockerRunner.startContainer(this.options, this.volumes, this.callback, () => {}); }); it("should start the container with the given name", function() { diff --git a/services/clsi/test/unit/coffee/DraftModeManagerTests.js b/services/clsi/test/unit/coffee/DraftModeManagerTests.js index ffea05087c..f270873a74 100644 --- a/services/clsi/test/unit/coffee/DraftModeManagerTests.js +++ b/services/clsi/test/unit/coffee/DraftModeManagerTests.js @@ -1,3 +1,8 @@ +/* eslint-disable + no-return-assign, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns diff --git a/services/clsi/test/unit/coffee/LatexRunnerTests.js b/services/clsi/test/unit/coffee/LatexRunnerTests.js index 5cb4d0666d..7fe8bc8164 100644 --- a/services/clsi/test/unit/coffee/LatexRunnerTests.js +++ b/services/clsi/test/unit/coffee/LatexRunnerTests.js @@ -1,3 +1,9 @@ +/* eslint-disable + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns diff --git a/services/clsi/test/unit/coffee/LockManagerTests.js b/services/clsi/test/unit/coffee/LockManagerTests.js index d716a443c3..6d1b15629a 100644 --- a/services/clsi/test/unit/coffee/LockManagerTests.js +++ b/services/clsi/test/unit/coffee/LockManagerTests.js @@ -1,3 +1,9 @@ +/* eslint-disable + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns diff --git a/services/clsi/test/unit/coffee/OutputFileFinderTests.js b/services/clsi/test/unit/coffee/OutputFileFinderTests.js index 3292d0a3bf..5c956adb1d 100644 --- a/services/clsi/test/unit/coffee/OutputFileFinderTests.js +++ b/services/clsi/test/unit/coffee/OutputFileFinderTests.js @@ -1,3 +1,10 @@ +/* eslint-disable + 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: * DS102: Remove unnecessary code created because of implicit returns diff --git a/services/clsi/test/unit/coffee/OutputFileOptimiserTests.js b/services/clsi/test/unit/coffee/OutputFileOptimiserTests.js index 8934c717e5..13b8d60c0a 100644 --- a/services/clsi/test/unit/coffee/OutputFileOptimiserTests.js +++ b/services/clsi/test/unit/coffee/OutputFileOptimiserTests.js @@ -1,3 +1,10 @@ +/* eslint-disable + no-return-assign, + no-unused-vars, + node/no-deprecated-api, +*/ +// 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 diff --git a/services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.js b/services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.js index c15cd80865..5f77a80785 100644 --- a/services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.js +++ b/services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.js @@ -1,3 +1,10 @@ +/* eslint-disable + camelcase, + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from diff --git a/services/clsi/test/unit/coffee/RequestParserTests.js b/services/clsi/test/unit/coffee/RequestParserTests.js index 5ca09411a7..725988f623 100644 --- a/services/clsi/test/unit/coffee/RequestParserTests.js +++ b/services/clsi/test/unit/coffee/RequestParserTests.js @@ -1,3 +1,9 @@ +/* eslint-disable + handle-callback-err, + no-return-assign, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns @@ -35,7 +41,7 @@ describe("RequestParser", function() { } });}); - afterEach(() => tk.reset()); + afterEach(function() { return tk.reset(); }); describe("without a top level object", function() { beforeEach(function() { diff --git a/services/clsi/test/unit/coffee/ResourceStateManagerTests.js b/services/clsi/test/unit/coffee/ResourceStateManagerTests.js index 4b09135efc..fe52cc584d 100644 --- a/services/clsi/test/unit/coffee/ResourceStateManagerTests.js +++ b/services/clsi/test/unit/coffee/ResourceStateManagerTests.js @@ -1,3 +1,9 @@ +/* eslint-disable + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.js b/services/clsi/test/unit/coffee/ResourceWriterTests.js index 89433c8206..830954739f 100644 --- a/services/clsi/test/unit/coffee/ResourceWriterTests.js +++ b/services/clsi/test/unit/coffee/ResourceWriterTests.js @@ -1,3 +1,8 @@ +/* eslint-disable + no-return-assign, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from diff --git a/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.js b/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.js index 9063c1fdcb..e754ea7547 100644 --- a/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.js +++ b/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.js @@ -1,3 +1,9 @@ +/* eslint-disable + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns diff --git a/services/clsi/test/unit/coffee/TikzManager.js b/services/clsi/test/unit/coffee/TikzManager.js index c792fabfb7..f35d261988 100644 --- a/services/clsi/test/unit/coffee/TikzManager.js +++ b/services/clsi/test/unit/coffee/TikzManager.js @@ -1,3 +1,8 @@ +/* eslint-disable + no-return-assign, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns diff --git a/services/clsi/test/unit/coffee/UrlCacheTests.js b/services/clsi/test/unit/coffee/UrlCacheTests.js index a3af0081a6..7f024507f2 100644 --- a/services/clsi/test/unit/coffee/UrlCacheTests.js +++ b/services/clsi/test/unit/coffee/UrlCacheTests.js @@ -1,3 +1,9 @@ +/* eslint-disable + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from diff --git a/services/clsi/test/unit/coffee/UrlFetcherTests.js b/services/clsi/test/unit/coffee/UrlFetcherTests.js index 21258ab83d..453a386755 100644 --- a/services/clsi/test/unit/coffee/UrlFetcherTests.js +++ b/services/clsi/test/unit/coffee/UrlFetcherTests.js @@ -1,3 +1,8 @@ +/* eslint-disable + no-return-assign, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns From 89360bfe77d5a16dfdfece672aa2eb5952508093 Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:15:30 +0100 Subject: [PATCH 524/754] decaffeinate: rename test/unit/coffee to test/unit/js --- services/clsi/test/unit/{coffee => js}/CompileControllerTests.js | 0 services/clsi/test/unit/{coffee => js}/CompileManagerTests.js | 0 services/clsi/test/unit/{coffee => js}/ContentTypeMapperTests.js | 0 services/clsi/test/unit/{coffee => js}/DockerLockManagerTests.js | 0 services/clsi/test/unit/{coffee => js}/DockerRunnerTests.js | 0 services/clsi/test/unit/{coffee => js}/DraftModeManagerTests.js | 0 services/clsi/test/unit/{coffee => js}/LatexRunnerTests.js | 0 services/clsi/test/unit/{coffee => js}/LockManagerTests.js | 0 services/clsi/test/unit/{coffee => js}/OutputFileFinderTests.js | 0 .../clsi/test/unit/{coffee => js}/OutputFileOptimiserTests.js | 0 .../test/unit/{coffee => js}/ProjectPersistenceManagerTests.js | 0 services/clsi/test/unit/{coffee => js}/RequestParserTests.js | 0 .../clsi/test/unit/{coffee => js}/ResourceStateManagerTests.js | 0 services/clsi/test/unit/{coffee => js}/ResourceWriterTests.js | 0 .../test/unit/{coffee => js}/StaticServerForbidSymlinksTests.js | 0 services/clsi/test/unit/{coffee => js}/TikzManager.js | 0 services/clsi/test/unit/{coffee => js}/UrlCacheTests.js | 0 services/clsi/test/unit/{coffee => js}/UrlFetcherTests.js | 0 18 files changed, 0 insertions(+), 0 deletions(-) rename services/clsi/test/unit/{coffee => js}/CompileControllerTests.js (100%) rename services/clsi/test/unit/{coffee => js}/CompileManagerTests.js (100%) rename services/clsi/test/unit/{coffee => js}/ContentTypeMapperTests.js (100%) rename services/clsi/test/unit/{coffee => js}/DockerLockManagerTests.js (100%) rename services/clsi/test/unit/{coffee => js}/DockerRunnerTests.js (100%) rename services/clsi/test/unit/{coffee => js}/DraftModeManagerTests.js (100%) rename services/clsi/test/unit/{coffee => js}/LatexRunnerTests.js (100%) rename services/clsi/test/unit/{coffee => js}/LockManagerTests.js (100%) rename services/clsi/test/unit/{coffee => js}/OutputFileFinderTests.js (100%) rename services/clsi/test/unit/{coffee => js}/OutputFileOptimiserTests.js (100%) rename services/clsi/test/unit/{coffee => js}/ProjectPersistenceManagerTests.js (100%) rename services/clsi/test/unit/{coffee => js}/RequestParserTests.js (100%) rename services/clsi/test/unit/{coffee => js}/ResourceStateManagerTests.js (100%) rename services/clsi/test/unit/{coffee => js}/ResourceWriterTests.js (100%) rename services/clsi/test/unit/{coffee => js}/StaticServerForbidSymlinksTests.js (100%) rename services/clsi/test/unit/{coffee => js}/TikzManager.js (100%) rename services/clsi/test/unit/{coffee => js}/UrlCacheTests.js (100%) rename services/clsi/test/unit/{coffee => js}/UrlFetcherTests.js (100%) diff --git a/services/clsi/test/unit/coffee/CompileControllerTests.js b/services/clsi/test/unit/js/CompileControllerTests.js similarity index 100% rename from services/clsi/test/unit/coffee/CompileControllerTests.js rename to services/clsi/test/unit/js/CompileControllerTests.js diff --git a/services/clsi/test/unit/coffee/CompileManagerTests.js b/services/clsi/test/unit/js/CompileManagerTests.js similarity index 100% rename from services/clsi/test/unit/coffee/CompileManagerTests.js rename to services/clsi/test/unit/js/CompileManagerTests.js diff --git a/services/clsi/test/unit/coffee/ContentTypeMapperTests.js b/services/clsi/test/unit/js/ContentTypeMapperTests.js similarity index 100% rename from services/clsi/test/unit/coffee/ContentTypeMapperTests.js rename to services/clsi/test/unit/js/ContentTypeMapperTests.js diff --git a/services/clsi/test/unit/coffee/DockerLockManagerTests.js b/services/clsi/test/unit/js/DockerLockManagerTests.js similarity index 100% rename from services/clsi/test/unit/coffee/DockerLockManagerTests.js rename to services/clsi/test/unit/js/DockerLockManagerTests.js diff --git a/services/clsi/test/unit/coffee/DockerRunnerTests.js b/services/clsi/test/unit/js/DockerRunnerTests.js similarity index 100% rename from services/clsi/test/unit/coffee/DockerRunnerTests.js rename to services/clsi/test/unit/js/DockerRunnerTests.js diff --git a/services/clsi/test/unit/coffee/DraftModeManagerTests.js b/services/clsi/test/unit/js/DraftModeManagerTests.js similarity index 100% rename from services/clsi/test/unit/coffee/DraftModeManagerTests.js rename to services/clsi/test/unit/js/DraftModeManagerTests.js diff --git a/services/clsi/test/unit/coffee/LatexRunnerTests.js b/services/clsi/test/unit/js/LatexRunnerTests.js similarity index 100% rename from services/clsi/test/unit/coffee/LatexRunnerTests.js rename to services/clsi/test/unit/js/LatexRunnerTests.js diff --git a/services/clsi/test/unit/coffee/LockManagerTests.js b/services/clsi/test/unit/js/LockManagerTests.js similarity index 100% rename from services/clsi/test/unit/coffee/LockManagerTests.js rename to services/clsi/test/unit/js/LockManagerTests.js diff --git a/services/clsi/test/unit/coffee/OutputFileFinderTests.js b/services/clsi/test/unit/js/OutputFileFinderTests.js similarity index 100% rename from services/clsi/test/unit/coffee/OutputFileFinderTests.js rename to services/clsi/test/unit/js/OutputFileFinderTests.js diff --git a/services/clsi/test/unit/coffee/OutputFileOptimiserTests.js b/services/clsi/test/unit/js/OutputFileOptimiserTests.js similarity index 100% rename from services/clsi/test/unit/coffee/OutputFileOptimiserTests.js rename to services/clsi/test/unit/js/OutputFileOptimiserTests.js diff --git a/services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.js b/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js similarity index 100% rename from services/clsi/test/unit/coffee/ProjectPersistenceManagerTests.js rename to services/clsi/test/unit/js/ProjectPersistenceManagerTests.js diff --git a/services/clsi/test/unit/coffee/RequestParserTests.js b/services/clsi/test/unit/js/RequestParserTests.js similarity index 100% rename from services/clsi/test/unit/coffee/RequestParserTests.js rename to services/clsi/test/unit/js/RequestParserTests.js diff --git a/services/clsi/test/unit/coffee/ResourceStateManagerTests.js b/services/clsi/test/unit/js/ResourceStateManagerTests.js similarity index 100% rename from services/clsi/test/unit/coffee/ResourceStateManagerTests.js rename to services/clsi/test/unit/js/ResourceStateManagerTests.js diff --git a/services/clsi/test/unit/coffee/ResourceWriterTests.js b/services/clsi/test/unit/js/ResourceWriterTests.js similarity index 100% rename from services/clsi/test/unit/coffee/ResourceWriterTests.js rename to services/clsi/test/unit/js/ResourceWriterTests.js diff --git a/services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.js b/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js similarity index 100% rename from services/clsi/test/unit/coffee/StaticServerForbidSymlinksTests.js rename to services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js diff --git a/services/clsi/test/unit/coffee/TikzManager.js b/services/clsi/test/unit/js/TikzManager.js similarity index 100% rename from services/clsi/test/unit/coffee/TikzManager.js rename to services/clsi/test/unit/js/TikzManager.js diff --git a/services/clsi/test/unit/coffee/UrlCacheTests.js b/services/clsi/test/unit/js/UrlCacheTests.js similarity index 100% rename from services/clsi/test/unit/coffee/UrlCacheTests.js rename to services/clsi/test/unit/js/UrlCacheTests.js diff --git a/services/clsi/test/unit/coffee/UrlFetcherTests.js b/services/clsi/test/unit/js/UrlFetcherTests.js similarity index 100% rename from services/clsi/test/unit/coffee/UrlFetcherTests.js rename to services/clsi/test/unit/js/UrlFetcherTests.js From a62d8186e997be4d07fb288d8148cdc779133897 Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:15:37 +0100 Subject: [PATCH 525/754] prettier: convert test/unit decaffeinated files to Prettier format --- .../test/unit/js/CompileControllerTests.js | 518 +++--- .../clsi/test/unit/js/CompileManagerTests.js | 870 +++++----- .../test/unit/js/ContentTypeMapperTests.js | 115 +- .../test/unit/js/DockerLockManagerTests.js | 393 +++-- .../clsi/test/unit/js/DockerRunnerTests.js | 1471 +++++++++-------- .../test/unit/js/DraftModeManagerTests.js | 122 +- .../clsi/test/unit/js/LatexRunnerTests.js | 208 ++- .../clsi/test/unit/js/LockManagerTests.js | 138 +- .../test/unit/js/OutputFileFinderTests.js | 172 +- .../test/unit/js/OutputFileOptimiserTests.js | 276 ++-- .../unit/js/ProjectPersistenceManagerTests.js | 145 +- .../clsi/test/unit/js/RequestParserTests.js | 674 ++++---- .../test/unit/js/ResourceStateManagerTests.js | 299 ++-- .../clsi/test/unit/js/ResourceWriterTests.js | 798 +++++---- .../js/StaticServerForbidSymlinksTests.js | 386 ++--- services/clsi/test/unit/js/TikzManager.js | 270 +-- services/clsi/test/unit/js/UrlCacheTests.js | 526 +++--- services/clsi/test/unit/js/UrlFetcherTests.js | 279 ++-- 18 files changed, 4233 insertions(+), 3427 deletions(-) diff --git a/services/clsi/test/unit/js/CompileControllerTests.js b/services/clsi/test/unit/js/CompileControllerTests.js index 2a06fbc30e..4480c8804e 100644 --- a/services/clsi/test/unit/js/CompileControllerTests.js +++ b/services/clsi/test/unit/js/CompileControllerTests.js @@ -9,267 +9,299 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const SandboxedModule = require('sandboxed-module'); -const sinon = require('sinon'); -require('chai').should(); -const modulePath = require('path').join(__dirname, '../../../app/js/CompileController'); -const tk = require("timekeeper"); +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +require('chai').should() +const modulePath = require('path').join( + __dirname, + '../../../app/js/CompileController' +) +const tk = require('timekeeper') -describe("CompileController", function() { - beforeEach(function() { - this.CompileController = SandboxedModule.require(modulePath, { requires: { - "./CompileManager": (this.CompileManager = {}), - "./RequestParser": (this.RequestParser = {}), - "settings-sharelatex": (this.Settings = { - apis: { - clsi: { - url: "http://clsi.example.com" - } - } - }), - "./ProjectPersistenceManager": (this.ProjectPersistenceManager = {}), - "logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub(), err:sinon.stub(), warn: sinon.stub()}) - } - }); - this.Settings.externalUrl = "http://www.example.com"; - this.req = {}; - this.res = {}; - return this.next = sinon.stub(); - }); +describe('CompileController', function() { + beforeEach(function() { + this.CompileController = SandboxedModule.require(modulePath, { + requires: { + './CompileManager': (this.CompileManager = {}), + './RequestParser': (this.RequestParser = {}), + 'settings-sharelatex': (this.Settings = { + apis: { + clsi: { + url: 'http://clsi.example.com' + } + } + }), + './ProjectPersistenceManager': (this.ProjectPersistenceManager = {}), + 'logger-sharelatex': (this.logger = { + log: sinon.stub(), + error: sinon.stub(), + err: sinon.stub(), + warn: sinon.stub() + }) + } + }) + this.Settings.externalUrl = 'http://www.example.com' + this.req = {} + this.res = {} + return (this.next = sinon.stub()) + }) - describe("compile", function() { - beforeEach(function() { - this.req.body = { - compile: "mock-body" - }; - this.req.params = - {project_id: (this.project_id = "project-id-123")}; - this.request = { - compile: "mock-parsed-request" - }; - this.request_with_project_id = { - compile: this.request.compile, - project_id: this.project_id - }; - this.output_files = [{ - path: "output.pdf", - type: "pdf", - build: 1234 - }, { - path: "output.log", - type: "log", - build: 1234 - }]; - this.RequestParser.parse = sinon.stub().callsArgWith(1, null, this.request); - this.ProjectPersistenceManager.markProjectAsJustAccessed = sinon.stub().callsArg(1); - this.res.status = sinon.stub().returnsThis(); - return this.res.send = sinon.stub(); - }); + describe('compile', function() { + beforeEach(function() { + this.req.body = { + compile: 'mock-body' + } + this.req.params = { project_id: (this.project_id = 'project-id-123') } + this.request = { + compile: 'mock-parsed-request' + } + this.request_with_project_id = { + compile: this.request.compile, + project_id: this.project_id + } + this.output_files = [ + { + path: 'output.pdf', + type: 'pdf', + build: 1234 + }, + { + path: 'output.log', + type: 'log', + build: 1234 + } + ] + this.RequestParser.parse = sinon + .stub() + .callsArgWith(1, null, this.request) + this.ProjectPersistenceManager.markProjectAsJustAccessed = sinon + .stub() + .callsArg(1) + this.res.status = sinon.stub().returnsThis() + return (this.res.send = sinon.stub()) + }) - describe("successfully", function() { - beforeEach(function() { - this.CompileManager.doCompileWithLock = sinon.stub().callsArgWith(1, null, this.output_files); - return this.CompileController.compile(this.req, this.res); - }); + describe('successfully', function() { + beforeEach(function() { + this.CompileManager.doCompileWithLock = sinon + .stub() + .callsArgWith(1, null, this.output_files) + return this.CompileController.compile(this.req, this.res) + }) - it("should parse the request", function() { - return this.RequestParser.parse - .calledWith(this.req.body) - .should.equal(true); - }); + it('should parse the request', function() { + return this.RequestParser.parse + .calledWith(this.req.body) + .should.equal(true) + }) - it("should run the compile for the specified project", function() { - return this.CompileManager.doCompileWithLock - .calledWith(this.request_with_project_id) - .should.equal(true); - }); + it('should run the compile for the specified project', function() { + return this.CompileManager.doCompileWithLock + .calledWith(this.request_with_project_id) + .should.equal(true) + }) - it("should mark the project as accessed", function() { - return this.ProjectPersistenceManager.markProjectAsJustAccessed - .calledWith(this.project_id) - .should.equal(true); - }); + it('should mark the project as accessed', function() { + return this.ProjectPersistenceManager.markProjectAsJustAccessed + .calledWith(this.project_id) + .should.equal(true) + }) - return it("should return the JSON response", function() { - this.res.status.calledWith(200).should.equal(true); - return this.res.send - .calledWith({ - compile: { - status: "success", - error: null, - outputFiles: this.output_files.map(file => { - return { - url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`, - path: file.path, - type: file.type, - build: file.build - }; - }) - } - }) - .should.equal(true); - }); - }); - - describe("with an error", function() { - beforeEach(function() { - this.CompileManager.doCompileWithLock = sinon.stub().callsArgWith(1, new Error(this.message = "error message"), null); - return this.CompileController.compile(this.req, this.res); - }); - - return it("should return the JSON response with the error", function() { - this.res.status.calledWith(500).should.equal(true); - return this.res.send - .calledWith({ - compile: { - status: "error", - error: this.message, - outputFiles: [] - } - }) - .should.equal(true); - }); - }); + return it('should return the JSON response', function() { + this.res.status.calledWith(200).should.equal(true) + return this.res.send + .calledWith({ + compile: { + status: 'success', + error: null, + outputFiles: this.output_files.map(file => { + return { + url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`, + path: file.path, + type: file.type, + build: file.build + } + }) + } + }) + .should.equal(true) + }) + }) - describe("when the request times out", function() { - beforeEach(function() { - this.error = new Error(this.message = "container timed out"); - this.error.timedout = true; - this.CompileManager.doCompileWithLock = sinon.stub().callsArgWith(1, this.error, null); - return this.CompileController.compile(this.req, this.res); - }); - - return it("should return the JSON response with the timeout status", function() { - this.res.status.calledWith(200).should.equal(true); - return this.res.send - .calledWith({ - compile: { - status: "timedout", - error: this.message, - outputFiles: [] - } - }) - .should.equal(true); - }); - }); + describe('with an error', function() { + beforeEach(function() { + this.CompileManager.doCompileWithLock = sinon + .stub() + .callsArgWith(1, new Error((this.message = 'error message')), null) + return this.CompileController.compile(this.req, this.res) + }) - return describe("when the request returns no output files", function() { - beforeEach(function() { - this.CompileManager.doCompileWithLock = sinon.stub().callsArgWith(1, null, []); - return this.CompileController.compile(this.req, this.res); - }); - - return it("should return the JSON response with the failure status", function() { - this.res.status.calledWith(200).should.equal(true); - return this.res.send - .calledWith({ - compile: { - error: null, - status: "failure", - outputFiles: [] - } - }) - .should.equal(true); - }); - }); - }); + return it('should return the JSON response with the error', function() { + this.res.status.calledWith(500).should.equal(true) + return this.res.send + .calledWith({ + compile: { + status: 'error', + error: this.message, + outputFiles: [] + } + }) + .should.equal(true) + }) + }) - describe("syncFromCode", function() { - beforeEach(function() { - this.file = "main.tex"; - this.line = 42; - this.column = 5; - this.project_id = "mock-project-id"; - this.req.params = - {project_id: this.project_id}; - this.req.query = { - file: this.file, - line: this.line.toString(), - column: this.column.toString() - }; - this.res.json = sinon.stub(); + describe('when the request times out', function() { + beforeEach(function() { + this.error = new Error((this.message = 'container timed out')) + this.error.timedout = true + this.CompileManager.doCompileWithLock = sinon + .stub() + .callsArgWith(1, this.error, null) + return this.CompileController.compile(this.req, this.res) + }) - this.CompileManager.syncFromCode = sinon.stub().callsArgWith(5, null, (this.pdfPositions = ["mock-positions"])); - return this.CompileController.syncFromCode(this.req, this.res, this.next); - }); + return it('should return the JSON response with the timeout status', function() { + this.res.status.calledWith(200).should.equal(true) + return this.res.send + .calledWith({ + compile: { + status: 'timedout', + error: this.message, + outputFiles: [] + } + }) + .should.equal(true) + }) + }) - it("should find the corresponding location in the PDF", function() { - return this.CompileManager.syncFromCode - .calledWith(this.project_id, undefined, this.file, this.line, this.column) - .should.equal(true); - }); + return describe('when the request returns no output files', function() { + beforeEach(function() { + this.CompileManager.doCompileWithLock = sinon + .stub() + .callsArgWith(1, null, []) + return this.CompileController.compile(this.req, this.res) + }) - return it("should return the positions", function() { - return this.res.json - .calledWith({ - pdf: this.pdfPositions - }) - .should.equal(true); - }); - }); + return it('should return the JSON response with the failure status', function() { + this.res.status.calledWith(200).should.equal(true) + return this.res.send + .calledWith({ + compile: { + error: null, + status: 'failure', + outputFiles: [] + } + }) + .should.equal(true) + }) + }) + }) - describe("syncFromPdf", function() { - beforeEach(function() { - this.page = 5; - this.h = 100.23; - this.v = 45.67; - this.project_id = "mock-project-id"; - this.req.params = - {project_id: this.project_id}; - this.req.query = { - page: this.page.toString(), - h: this.h.toString(), - v: this.v.toString() - }; - this.res.json = sinon.stub(); + describe('syncFromCode', function() { + beforeEach(function() { + this.file = 'main.tex' + this.line = 42 + this.column = 5 + this.project_id = 'mock-project-id' + this.req.params = { project_id: this.project_id } + this.req.query = { + file: this.file, + line: this.line.toString(), + column: this.column.toString() + } + this.res.json = sinon.stub() - this.CompileManager.syncFromPdf = sinon.stub().callsArgWith(5, null, (this.codePositions = ["mock-positions"])); - return this.CompileController.syncFromPdf(this.req, this.res, this.next); - }); + this.CompileManager.syncFromCode = sinon + .stub() + .callsArgWith(5, null, (this.pdfPositions = ['mock-positions'])) + return this.CompileController.syncFromCode(this.req, this.res, this.next) + }) - it("should find the corresponding location in the code", function() { - return this.CompileManager.syncFromPdf - .calledWith(this.project_id, undefined, this.page, this.h, this.v) - .should.equal(true); - }); + it('should find the corresponding location in the PDF', function() { + return this.CompileManager.syncFromCode + .calledWith( + this.project_id, + undefined, + this.file, + this.line, + this.column + ) + .should.equal(true) + }) - return it("should return the positions", function() { - return this.res.json - .calledWith({ - code: this.codePositions - }) - .should.equal(true); - }); - }); + return it('should return the positions', function() { + return this.res.json + .calledWith({ + pdf: this.pdfPositions + }) + .should.equal(true) + }) + }) - return describe("wordcount", function() { - beforeEach(function() { - this.file = "main.tex"; - this.project_id = "mock-project-id"; - this.req.params = - {project_id: this.project_id}; - this.req.query = { - file: this.file, - image: (this.image = "example.com/image") - }; - this.res.json = sinon.stub(); + describe('syncFromPdf', function() { + beforeEach(function() { + this.page = 5 + this.h = 100.23 + this.v = 45.67 + this.project_id = 'mock-project-id' + this.req.params = { project_id: this.project_id } + this.req.query = { + page: this.page.toString(), + h: this.h.toString(), + v: this.v.toString() + } + this.res.json = sinon.stub() - this.CompileManager.wordcount = sinon.stub().callsArgWith(4, null, (this.texcount = ["mock-texcount"])); - return this.CompileController.wordcount(this.req, this.res, this.next); - }); + this.CompileManager.syncFromPdf = sinon + .stub() + .callsArgWith(5, null, (this.codePositions = ['mock-positions'])) + return this.CompileController.syncFromPdf(this.req, this.res, this.next) + }) - it("should return the word count of a file", function() { - return this.CompileManager.wordcount - .calledWith(this.project_id, undefined, this.file, this.image) - .should.equal(true); - }); + it('should find the corresponding location in the code', function() { + return this.CompileManager.syncFromPdf + .calledWith(this.project_id, undefined, this.page, this.h, this.v) + .should.equal(true) + }) - return it("should return the texcount info", function() { - return this.res.json - .calledWith({ - texcount: this.texcount - }) - .should.equal(true); - }); - }); -}); + return it('should return the positions', function() { + return this.res.json + .calledWith({ + code: this.codePositions + }) + .should.equal(true) + }) + }) + + return describe('wordcount', function() { + beforeEach(function() { + this.file = 'main.tex' + this.project_id = 'mock-project-id' + this.req.params = { project_id: this.project_id } + this.req.query = { + file: this.file, + image: (this.image = 'example.com/image') + } + this.res.json = sinon.stub() + + this.CompileManager.wordcount = sinon + .stub() + .callsArgWith(4, null, (this.texcount = ['mock-texcount'])) + return this.CompileController.wordcount(this.req, this.res, this.next) + }) + + it('should return the word count of a file', function() { + return this.CompileManager.wordcount + .calledWith(this.project_id, undefined, this.file, this.image) + .should.equal(true) + }) + + return it('should return the texcount info', function() { + return this.res.json + .calledWith({ + texcount: this.texcount + }) + .should.equal(true) + }) + }) +}) diff --git a/services/clsi/test/unit/js/CompileManagerTests.js b/services/clsi/test/unit/js/CompileManagerTests.js index e798aec390..ae50bcc618 100644 --- a/services/clsi/test/unit/js/CompileManagerTests.js +++ b/services/clsi/test/unit/js/CompileManagerTests.js @@ -13,423 +13,539 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const SandboxedModule = require('sandboxed-module'); -const sinon = require('sinon'); -require('chai').should(); -const modulePath = require('path').join(__dirname, '../../../app/js/CompileManager'); -const tk = require("timekeeper"); -const { EventEmitter } = require("events"); -const Path = require("path"); +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +require('chai').should() +const modulePath = require('path').join( + __dirname, + '../../../app/js/CompileManager' +) +const tk = require('timekeeper') +const { EventEmitter } = require('events') +const Path = require('path') -describe("CompileManager", function() { - beforeEach(function() { - this.CompileManager = SandboxedModule.require(modulePath, { requires: { - "./LatexRunner": (this.LatexRunner = {}), - "./ResourceWriter": (this.ResourceWriter = {}), - "./OutputFileFinder": (this.OutputFileFinder = {}), - "./OutputCacheManager": (this.OutputCacheManager = {}), - "settings-sharelatex": (this.Settings = { - path: { - compilesDir: "/compiles/dir" - }, - synctexBaseDir() { return "/compile"; }, - clsi: { - docker: { - image: "SOMEIMAGE" - } - } - }), +describe('CompileManager', function() { + beforeEach(function() { + this.CompileManager = SandboxedModule.require(modulePath, { + requires: { + './LatexRunner': (this.LatexRunner = {}), + './ResourceWriter': (this.ResourceWriter = {}), + './OutputFileFinder': (this.OutputFileFinder = {}), + './OutputCacheManager': (this.OutputCacheManager = {}), + 'settings-sharelatex': (this.Settings = { + path: { + compilesDir: '/compiles/dir' + }, + synctexBaseDir() { + return '/compile' + }, + clsi: { + docker: { + image: 'SOMEIMAGE' + } + } + }), - "logger-sharelatex": (this.logger = { log: sinon.stub() , info() {}}), - "child_process": (this.child_process = {}), - "./CommandRunner": (this.CommandRunner = {}), - "./DraftModeManager": (this.DraftModeManager = {}), - "./TikzManager": (this.TikzManager = {}), - "./LockManager": (this.LockManager = {}), - "fs": (this.fs = {}), - "fs-extra": (this.fse = { ensureDir: sinon.stub().callsArg(1) }) - } - }); - this.callback = sinon.stub(); - this.project_id = "project-id-123"; - return this.user_id = "1234"; - }); - describe("doCompileWithLock", function() { - beforeEach(function() { - this.request = { - resources: (this.resources = "mock-resources"), - project_id: this.project_id, - user_id: this.user_id - }; - this.output_files = ["foo", "bar"]; - this.Settings.compileDir = "compiles"; - this.compileDir = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`; - this.CompileManager.doCompile = sinon.stub().callsArgWith(1, null, this.output_files); - return this.LockManager.runWithLock = (lockFile, runner, callback) => - runner((err, ...result) => callback(err, ...Array.from(result))) - ; - }); + 'logger-sharelatex': (this.logger = { log: sinon.stub(), info() {} }), + child_process: (this.child_process = {}), + './CommandRunner': (this.CommandRunner = {}), + './DraftModeManager': (this.DraftModeManager = {}), + './TikzManager': (this.TikzManager = {}), + './LockManager': (this.LockManager = {}), + fs: (this.fs = {}), + 'fs-extra': (this.fse = { ensureDir: sinon.stub().callsArg(1) }) + } + }) + this.callback = sinon.stub() + this.project_id = 'project-id-123' + return (this.user_id = '1234') + }) + describe('doCompileWithLock', function() { + beforeEach(function() { + this.request = { + resources: (this.resources = 'mock-resources'), + project_id: this.project_id, + user_id: this.user_id + } + this.output_files = ['foo', 'bar'] + this.Settings.compileDir = 'compiles' + this.compileDir = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}` + this.CompileManager.doCompile = sinon + .stub() + .callsArgWith(1, null, this.output_files) + return (this.LockManager.runWithLock = (lockFile, runner, callback) => + runner((err, ...result) => callback(err, ...Array.from(result)))) + }) - describe("when the project is not locked", function() { - beforeEach(function() { - return this.CompileManager.doCompileWithLock(this.request, this.callback); - }); + describe('when the project is not locked', function() { + beforeEach(function() { + return this.CompileManager.doCompileWithLock( + this.request, + this.callback + ) + }) - it("should ensure that the compile directory exists", function() { - return this.fse.ensureDir.calledWith(this.compileDir) - .should.equal(true); - }); + it('should ensure that the compile directory exists', function() { + return this.fse.ensureDir.calledWith(this.compileDir).should.equal(true) + }) - it("should call doCompile with the request", function() { - return this.CompileManager.doCompile - .calledWith(this.request) - .should.equal(true); - }); + it('should call doCompile with the request', function() { + return this.CompileManager.doCompile + .calledWith(this.request) + .should.equal(true) + }) - return it("should call the callback with the output files", function() { - return this.callback.calledWithExactly(null, this.output_files) - .should.equal(true); - }); - }); + return it('should call the callback with the output files', function() { + return this.callback + .calledWithExactly(null, this.output_files) + .should.equal(true) + }) + }) - return describe("when the project is locked", function() { - beforeEach(function() { - this.error = new Error("locked"); - this.LockManager.runWithLock = (lockFile, runner, callback) => { - return callback(this.error); - }; - return this.CompileManager.doCompileWithLock(this.request, this.callback); - }); + return describe('when the project is locked', function() { + beforeEach(function() { + this.error = new Error('locked') + this.LockManager.runWithLock = (lockFile, runner, callback) => { + return callback(this.error) + } + return this.CompileManager.doCompileWithLock( + this.request, + this.callback + ) + }) - it("should ensure that the compile directory exists", function() { - return this.fse.ensureDir.calledWith(this.compileDir) - .should.equal(true); - }); + it('should ensure that the compile directory exists', function() { + return this.fse.ensureDir.calledWith(this.compileDir).should.equal(true) + }) - it("should not call doCompile with the request", function() { - return this.CompileManager.doCompile - .called.should.equal(false); - }); + it('should not call doCompile with the request', function() { + return this.CompileManager.doCompile.called.should.equal(false) + }) - return it("should call the callback with the error", function() { - return this.callback.calledWithExactly(this.error) - .should.equal(true); - }); - }); - }); + return it('should call the callback with the error', function() { + return this.callback.calledWithExactly(this.error).should.equal(true) + }) + }) + }) - describe("doCompile", function() { - beforeEach(function() { - this.output_files = [{ - path: "output.log", - type: "log" - }, { - path: "output.pdf", - type: "pdf" - }]; - this.build_files = [{ - path: "output.log", - type: "log", - build: 1234 - }, { - path: "output.pdf", - type: "pdf", - build: 1234 - }]; - this.request = { - resources: (this.resources = "mock-resources"), - rootResourcePath: (this.rootResourcePath = "main.tex"), - project_id: this.project_id, - user_id: this.user_id, - compiler: (this.compiler = "pdflatex"), - timeout: (this.timeout = 42000), - imageName: (this.image = "example.com/image"), - flags: (this.flags = ["-file-line-error"]) - }; - this.env = {}; - this.Settings.compileDir = "compiles"; - this.compileDir = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`; - this.ResourceWriter.syncResourcesToDisk = sinon.stub().callsArgWith(2, null, this.resources); - this.LatexRunner.runLatex = sinon.stub().callsArg(2); - this.OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, this.output_files); - this.OutputCacheManager.saveOutputFiles = sinon.stub().callsArgWith(2, null, this.build_files); - this.DraftModeManager.injectDraftMode = sinon.stub().callsArg(1); - return this.TikzManager.checkMainFile = sinon.stub().callsArg(3, false); - }); + describe('doCompile', function() { + beforeEach(function() { + this.output_files = [ + { + path: 'output.log', + type: 'log' + }, + { + path: 'output.pdf', + type: 'pdf' + } + ] + this.build_files = [ + { + path: 'output.log', + type: 'log', + build: 1234 + }, + { + path: 'output.pdf', + type: 'pdf', + build: 1234 + } + ] + this.request = { + resources: (this.resources = 'mock-resources'), + rootResourcePath: (this.rootResourcePath = 'main.tex'), + project_id: this.project_id, + user_id: this.user_id, + compiler: (this.compiler = 'pdflatex'), + timeout: (this.timeout = 42000), + imageName: (this.image = 'example.com/image'), + flags: (this.flags = ['-file-line-error']) + } + this.env = {} + this.Settings.compileDir = 'compiles' + this.compileDir = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}` + this.ResourceWriter.syncResourcesToDisk = sinon + .stub() + .callsArgWith(2, null, this.resources) + this.LatexRunner.runLatex = sinon.stub().callsArg(2) + this.OutputFileFinder.findOutputFiles = sinon + .stub() + .callsArgWith(2, null, this.output_files) + this.OutputCacheManager.saveOutputFiles = sinon + .stub() + .callsArgWith(2, null, this.build_files) + this.DraftModeManager.injectDraftMode = sinon.stub().callsArg(1) + return (this.TikzManager.checkMainFile = sinon.stub().callsArg(3, false)) + }) - describe("normally", function() { - beforeEach(function() { - return this.CompileManager.doCompile(this.request, this.callback); - }); + describe('normally', function() { + beforeEach(function() { + return this.CompileManager.doCompile(this.request, this.callback) + }) - it("should write the resources to disk", function() { - return this.ResourceWriter.syncResourcesToDisk - .calledWith(this.request, this.compileDir) - .should.equal(true); - }); + it('should write the resources to disk', function() { + return this.ResourceWriter.syncResourcesToDisk + .calledWith(this.request, this.compileDir) + .should.equal(true) + }) - it("should run LaTeX", function() { - return this.LatexRunner.runLatex - .calledWith(`${this.project_id}-${this.user_id}`, { - directory: this.compileDir, - mainFile: this.rootResourcePath, - compiler: this.compiler, - timeout: this.timeout, - image: this.image, - flags: this.flags, - environment: this.env - }) - .should.equal(true); - }); + it('should run LaTeX', function() { + return this.LatexRunner.runLatex + .calledWith(`${this.project_id}-${this.user_id}`, { + directory: this.compileDir, + mainFile: this.rootResourcePath, + compiler: this.compiler, + timeout: this.timeout, + image: this.image, + flags: this.flags, + environment: this.env + }) + .should.equal(true) + }) - it("should find the output files", function() { - return this.OutputFileFinder.findOutputFiles - .calledWith(this.resources, this.compileDir) - .should.equal(true); - }); + it('should find the output files', function() { + return this.OutputFileFinder.findOutputFiles + .calledWith(this.resources, this.compileDir) + .should.equal(true) + }) - it("should return the output files", function() { - return this.callback.calledWith(null, this.build_files).should.equal(true); - }); + it('should return the output files', function() { + return this.callback + .calledWith(null, this.build_files) + .should.equal(true) + }) - return it("should not inject draft mode by default", function() { - return this.DraftModeManager.injectDraftMode.called.should.equal(false); - }); - }); + return it('should not inject draft mode by default', function() { + return this.DraftModeManager.injectDraftMode.called.should.equal(false) + }) + }) - describe("with draft mode", function() { - beforeEach(function() { - this.request.draft = true; - return this.CompileManager.doCompile(this.request, this.callback); - }); + describe('with draft mode', function() { + beforeEach(function() { + this.request.draft = true + return this.CompileManager.doCompile(this.request, this.callback) + }) - return it("should inject the draft mode header", function() { - return this.DraftModeManager.injectDraftMode - .calledWith(this.compileDir + "/" + this.rootResourcePath) - .should.equal(true); - }); - }); + return it('should inject the draft mode header', function() { + return this.DraftModeManager.injectDraftMode + .calledWith(this.compileDir + '/' + this.rootResourcePath) + .should.equal(true) + }) + }) - describe("with a check option", function() { - beforeEach(function() { - this.request.check = "error"; - return this.CompileManager.doCompile(this.request, this.callback); - }); + describe('with a check option', function() { + beforeEach(function() { + this.request.check = 'error' + return this.CompileManager.doCompile(this.request, this.callback) + }) - return it("should run chktex", function() { - return this.LatexRunner.runLatex - .calledWith(`${this.project_id}-${this.user_id}`, { - directory: this.compileDir, - mainFile: this.rootResourcePath, - compiler: this.compiler, - timeout: this.timeout, - image: this.image, - flags: this.flags, - environment: {'CHKTEX_OPTIONS': '-nall -e9 -e10 -w15 -w16', 'CHKTEX_EXIT_ON_ERROR':1, 'CHKTEX_ULIMIT_OPTIONS': '-t 5 -v 64000'} - }) - .should.equal(true); - }); - }); + return it('should run chktex', function() { + return this.LatexRunner.runLatex + .calledWith(`${this.project_id}-${this.user_id}`, { + directory: this.compileDir, + mainFile: this.rootResourcePath, + compiler: this.compiler, + timeout: this.timeout, + image: this.image, + flags: this.flags, + environment: { + CHKTEX_OPTIONS: '-nall -e9 -e10 -w15 -w16', + CHKTEX_EXIT_ON_ERROR: 1, + CHKTEX_ULIMIT_OPTIONS: '-t 5 -v 64000' + } + }) + .should.equal(true) + }) + }) - return describe("with a knitr file and check options", function() { - beforeEach(function() { - this.request.rootResourcePath = "main.Rtex"; - this.request.check = "error"; - return this.CompileManager.doCompile(this.request, this.callback); - }); + return describe('with a knitr file and check options', function() { + beforeEach(function() { + this.request.rootResourcePath = 'main.Rtex' + this.request.check = 'error' + return this.CompileManager.doCompile(this.request, this.callback) + }) - return it("should not run chktex", function() { - return this.LatexRunner.runLatex - .calledWith(`${this.project_id}-${this.user_id}`, { - directory: this.compileDir, - mainFile: "main.Rtex", - compiler: this.compiler, - timeout: this.timeout, - image: this.image, - flags: this.flags, - environment: this.env - }) - .should.equal(true); - }); - }); - }); + return it('should not run chktex', function() { + return this.LatexRunner.runLatex + .calledWith(`${this.project_id}-${this.user_id}`, { + directory: this.compileDir, + mainFile: 'main.Rtex', + compiler: this.compiler, + timeout: this.timeout, + image: this.image, + flags: this.flags, + environment: this.env + }) + .should.equal(true) + }) + }) + }) - describe("clearProject", function() { - describe("succesfully", function() { - beforeEach(function() { - this.Settings.compileDir = "compiles"; - this.fs.lstat = sinon.stub().callsArgWith(1, null,{isDirectory(){ return true; }}); - this.proc = new EventEmitter(); - this.proc.stdout = new EventEmitter(); - this.proc.stderr = new EventEmitter(); - this.child_process.spawn = sinon.stub().returns(this.proc); - this.CompileManager.clearProject(this.project_id, this.user_id, this.callback); - return this.proc.emit("close", 0); - }); + describe('clearProject', function() { + describe('succesfully', function() { + beforeEach(function() { + this.Settings.compileDir = 'compiles' + this.fs.lstat = sinon.stub().callsArgWith(1, null, { + isDirectory() { + return true + } + }) + this.proc = new EventEmitter() + this.proc.stdout = new EventEmitter() + this.proc.stderr = new EventEmitter() + this.child_process.spawn = sinon.stub().returns(this.proc) + this.CompileManager.clearProject( + this.project_id, + this.user_id, + this.callback + ) + return this.proc.emit('close', 0) + }) - it("should remove the project directory", function() { - return this.child_process.spawn - .calledWith("rm", ["-r", `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`]) - .should.equal(true); - }); + it('should remove the project directory', function() { + return this.child_process.spawn + .calledWith('rm', [ + '-r', + `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}` + ]) + .should.equal(true) + }) - return it("should call the callback", function() { - return this.callback.called.should.equal(true); - }); - }); + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) - return describe("with a non-success status code", function() { - beforeEach(function() { - this.Settings.compileDir = "compiles"; - this.fs.lstat = sinon.stub().callsArgWith(1, null,{isDirectory(){ return true; }}); - this.proc = new EventEmitter(); - this.proc.stdout = new EventEmitter(); - this.proc.stderr = new EventEmitter(); - this.child_process.spawn = sinon.stub().returns(this.proc); - this.CompileManager.clearProject(this.project_id, this.user_id, this.callback); - this.proc.stderr.emit("data", (this.error = "oops")); - return this.proc.emit("close", 1); - }); + return describe('with a non-success status code', function() { + beforeEach(function() { + this.Settings.compileDir = 'compiles' + this.fs.lstat = sinon.stub().callsArgWith(1, null, { + isDirectory() { + return true + } + }) + this.proc = new EventEmitter() + this.proc.stdout = new EventEmitter() + this.proc.stderr = new EventEmitter() + this.child_process.spawn = sinon.stub().returns(this.proc) + this.CompileManager.clearProject( + this.project_id, + this.user_id, + this.callback + ) + this.proc.stderr.emit('data', (this.error = 'oops')) + return this.proc.emit('close', 1) + }) - it("should remove the project directory", function() { - return this.child_process.spawn - .calledWith("rm", ["-r", `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`]) - .should.equal(true); - }); + it('should remove the project directory', function() { + return this.child_process.spawn + .calledWith('rm', [ + '-r', + `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}` + ]) + .should.equal(true) + }) - return it("should call the callback with an error from the stderr", function() { - this.callback - .calledWith(new Error()) - .should.equal(true); + return it('should call the callback with an error from the stderr', function() { + this.callback.calledWith(new Error()).should.equal(true) - return this.callback.args[0][0].message.should.equal(`rm -r ${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id} failed: ${this.error}`); - }); - }); - }); + return this.callback.args[0][0].message.should.equal( + `rm -r ${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id} failed: ${this.error}` + ) + }) + }) + }) - describe("syncing", function() { - beforeEach(function() { - this.page = 1; - this.h = 42.23; - this.v = 87.56; - this.width = 100.01; - this.height = 234.56; - this.line = 5; - this.column = 3; - this.file_name = "main.tex"; - this.child_process.execFile = sinon.stub(); - return this.Settings.path.synctexBaseDir = project_id => `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`; - }); + describe('syncing', function() { + beforeEach(function() { + this.page = 1 + this.h = 42.23 + this.v = 87.56 + this.width = 100.01 + this.height = 234.56 + this.line = 5 + this.column = 3 + this.file_name = 'main.tex' + this.child_process.execFile = sinon.stub() + return (this.Settings.path.synctexBaseDir = project_id => + `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`) + }) - describe("syncFromCode", function() { - beforeEach(function() { - this.fs.stat = sinon.stub().callsArgWith(1, null,{isFile(){ return true; }}); - this.stdout = `NODE\t${this.page}\t${this.h}\t${this.v}\t${this.width}\t${this.height}\n`; - this.CommandRunner.run = sinon.stub().callsArgWith(6, null, {stdout:this.stdout}); - return this.CompileManager.syncFromCode(this.project_id, this.user_id, this.file_name, this.line, this.column, this.callback); - }); + describe('syncFromCode', function() { + beforeEach(function() { + this.fs.stat = sinon.stub().callsArgWith(1, null, { + isFile() { + return true + } + }) + this.stdout = `NODE\t${this.page}\t${this.h}\t${this.v}\t${this.width}\t${this.height}\n` + this.CommandRunner.run = sinon + .stub() + .callsArgWith(6, null, { stdout: this.stdout }) + return this.CompileManager.syncFromCode( + this.project_id, + this.user_id, + this.file_name, + this.line, + this.column, + this.callback + ) + }) - it("should execute the synctex binary", function() { - const bin_path = Path.resolve(__dirname + "/../../../bin/synctex"); - const synctex_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/output.pdf`; - const file_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/${this.file_name}`; - return this.CommandRunner.run - .calledWith( - `${this.project_id}-${this.user_id}`, - ['/opt/synctex', 'code', synctex_path, file_path, this.line, this.column], - `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, - this.Settings.clsi.docker.image, - 60000, - {} - ).should.equal(true); - }); + it('should execute the synctex binary', function() { + const bin_path = Path.resolve(__dirname + '/../../../bin/synctex') + const synctex_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/output.pdf` + const file_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/${this.file_name}` + return this.CommandRunner.run + .calledWith( + `${this.project_id}-${this.user_id}`, + [ + '/opt/synctex', + 'code', + synctex_path, + file_path, + this.line, + this.column + ], + `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, + this.Settings.clsi.docker.image, + 60000, + {} + ) + .should.equal(true) + }) - return it("should call the callback with the parsed output", function() { - return this.callback - .calledWith(null, [{ - page: this.page, - h: this.h, - v: this.v, - height: this.height, - width: this.width - }]) - .should.equal(true); - }); - }); + return it('should call the callback with the parsed output', function() { + return this.callback + .calledWith(null, [ + { + page: this.page, + h: this.h, + v: this.v, + height: this.height, + width: this.width + } + ]) + .should.equal(true) + }) + }) - return describe("syncFromPdf", function() { - beforeEach(function() { - this.fs.stat = sinon.stub().callsArgWith(1, null,{isFile(){ return true; }}); - this.stdout = `NODE\t${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/${this.file_name}\t${this.line}\t${this.column}\n`; - this.CommandRunner.run = sinon.stub().callsArgWith(6, null, {stdout:this.stdout}); - return this.CompileManager.syncFromPdf(this.project_id, this.user_id, this.page, this.h, this.v, this.callback); - }); + return describe('syncFromPdf', function() { + beforeEach(function() { + this.fs.stat = sinon.stub().callsArgWith(1, null, { + isFile() { + return true + } + }) + this.stdout = `NODE\t${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/${this.file_name}\t${this.line}\t${this.column}\n` + this.CommandRunner.run = sinon + .stub() + .callsArgWith(6, null, { stdout: this.stdout }) + return this.CompileManager.syncFromPdf( + this.project_id, + this.user_id, + this.page, + this.h, + this.v, + this.callback + ) + }) - it("should execute the synctex binary", function() { - const bin_path = Path.resolve(__dirname + "/../../../bin/synctex"); - const synctex_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/output.pdf`; - return this.CommandRunner.run - .calledWith( - `${this.project_id}-${this.user_id}`, - ['/opt/synctex', "pdf", synctex_path, this.page, this.h, this.v], - `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, - this.Settings.clsi.docker.image, - 60000, - {}).should.equal(true); - }); + it('should execute the synctex binary', function() { + const bin_path = Path.resolve(__dirname + '/../../../bin/synctex') + const synctex_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/output.pdf` + return this.CommandRunner.run + .calledWith( + `${this.project_id}-${this.user_id}`, + ['/opt/synctex', 'pdf', synctex_path, this.page, this.h, this.v], + `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, + this.Settings.clsi.docker.image, + 60000, + {} + ) + .should.equal(true) + }) - return it("should call the callback with the parsed output", function() { - return this.callback - .calledWith(null, [{ - file: this.file_name, - line: this.line, - column: this.column - }]) - .should.equal(true); - }); - }); - }); + return it('should call the callback with the parsed output', function() { + return this.callback + .calledWith(null, [ + { + file: this.file_name, + line: this.line, + column: this.column + } + ]) + .should.equal(true) + }) + }) + }) - return describe("wordcount", function() { - beforeEach(function() { - this.CommandRunner.run = sinon.stub().callsArg(6); - this.fs.readFile = sinon.stub().callsArgWith(2, null, (this.stdout = "Encoding: ascii\nWords in text: 2")); - this.callback = sinon.stub(); + return describe('wordcount', function() { + beforeEach(function() { + this.CommandRunner.run = sinon.stub().callsArg(6) + this.fs.readFile = sinon + .stub() + .callsArgWith( + 2, + null, + (this.stdout = 'Encoding: ascii\nWords in text: 2') + ) + this.callback = sinon.stub() - this.project_id; - this.timeout = 60 * 1000; - this.file_name = "main.tex"; - this.Settings.path.compilesDir = "/local/compile/directory"; - this.image = "example.com/image"; + this.project_id + this.timeout = 60 * 1000 + this.file_name = 'main.tex' + this.Settings.path.compilesDir = '/local/compile/directory' + this.image = 'example.com/image' - return this.CompileManager.wordcount(this.project_id, this.user_id, this.file_name, this.image, this.callback); - }); + return this.CompileManager.wordcount( + this.project_id, + this.user_id, + this.file_name, + this.image, + this.callback + ) + }) - it("should run the texcount command", function() { - this.directory = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`; - this.file_path = `$COMPILE_DIR/${this.file_name}`; - this.command =[ "texcount", "-nocol", "-inc", this.file_path, `-out=${this.file_path}.wc`]; + it('should run the texcount command', function() { + this.directory = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}` + this.file_path = `$COMPILE_DIR/${this.file_name}` + this.command = [ + 'texcount', + '-nocol', + '-inc', + this.file_path, + `-out=${this.file_path}.wc` + ] - return this.CommandRunner.run - .calledWith(`${this.project_id}-${this.user_id}`, this.command, this.directory, this.image, this.timeout, {}) - .should.equal(true); - }); + return this.CommandRunner.run + .calledWith( + `${this.project_id}-${this.user_id}`, + this.command, + this.directory, + this.image, + this.timeout, + {} + ) + .should.equal(true) + }) - return it("should call the callback with the parsed output", function() { - return this.callback - .calledWith(null, { - encode: "ascii", - textWords: 2, - headWords: 0, - outside: 0, - headers: 0, - elements: 0, - mathInline: 0, - mathDisplay: 0, - errors: 0, - messages: "" - }) - .should.equal(true); - }); - }); -}); + return it('should call the callback with the parsed output', function() { + return this.callback + .calledWith(null, { + encode: 'ascii', + textWords: 2, + headWords: 0, + outside: 0, + headers: 0, + elements: 0, + mathInline: 0, + mathDisplay: 0, + errors: 0, + messages: '' + }) + .should.equal(true) + }) + }) +}) diff --git a/services/clsi/test/unit/js/ContentTypeMapperTests.js b/services/clsi/test/unit/js/ContentTypeMapperTests.js index bbde292318..41fc37e486 100644 --- a/services/clsi/test/unit/js/ContentTypeMapperTests.js +++ b/services/clsi/test/unit/js/ContentTypeMapperTests.js @@ -10,73 +10,72 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const SandboxedModule = require('sandboxed-module'); -const sinon = require('sinon'); -require('chai').should(); -const modulePath = require('path').join(__dirname, '../../../app/js/ContentTypeMapper'); +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +require('chai').should() +const modulePath = require('path').join( + __dirname, + '../../../app/js/ContentTypeMapper' +) describe('ContentTypeMapper', function() { + beforeEach(function() { + return (this.ContentTypeMapper = SandboxedModule.require(modulePath)) + }) - beforeEach(function() { - return this.ContentTypeMapper = SandboxedModule.require(modulePath); - }); + return describe('map', function() { + it('should map .txt to text/plain', function() { + const content_type = this.ContentTypeMapper.map('example.txt') + return content_type.should.equal('text/plain') + }) - return describe('map', function() { + it('should map .csv to text/csv', function() { + const content_type = this.ContentTypeMapper.map('example.csv') + return content_type.should.equal('text/csv') + }) - it('should map .txt to text/plain', function() { - const content_type = this.ContentTypeMapper.map('example.txt'); - return content_type.should.equal('text/plain'); - }); + it('should map .pdf to application/pdf', function() { + const content_type = this.ContentTypeMapper.map('example.pdf') + return content_type.should.equal('application/pdf') + }) - it('should map .csv to text/csv', function() { - const content_type = this.ContentTypeMapper.map('example.csv'); - return content_type.should.equal('text/csv'); - }); + it('should fall back to octet-stream', function() { + const content_type = this.ContentTypeMapper.map('example.unknown') + return content_type.should.equal('application/octet-stream') + }) - it('should map .pdf to application/pdf', function() { - const content_type = this.ContentTypeMapper.map('example.pdf'); - return content_type.should.equal('application/pdf'); - }); + describe('coercing web files to plain text', function() { + it('should map .js to plain text', function() { + const content_type = this.ContentTypeMapper.map('example.js') + return content_type.should.equal('text/plain') + }) - it('should fall back to octet-stream', function() { - const content_type = this.ContentTypeMapper.map('example.unknown'); - return content_type.should.equal('application/octet-stream'); - }); + it('should map .html to plain text', function() { + const content_type = this.ContentTypeMapper.map('example.html') + return content_type.should.equal('text/plain') + }) - describe('coercing web files to plain text', function() { + return it('should map .css to plain text', function() { + const content_type = this.ContentTypeMapper.map('example.css') + return content_type.should.equal('text/plain') + }) + }) - it('should map .js to plain text', function() { - const content_type = this.ContentTypeMapper.map('example.js'); - return content_type.should.equal('text/plain'); - }); + return describe('image files', function() { + it('should map .png to image/png', function() { + const content_type = this.ContentTypeMapper.map('example.png') + return content_type.should.equal('image/png') + }) - it('should map .html to plain text', function() { - const content_type = this.ContentTypeMapper.map('example.html'); - return content_type.should.equal('text/plain'); - }); + it('should map .jpeg to image/jpeg', function() { + const content_type = this.ContentTypeMapper.map('example.jpeg') + return content_type.should.equal('image/jpeg') + }) - return it('should map .css to plain text', function() { - const content_type = this.ContentTypeMapper.map('example.css'); - return content_type.should.equal('text/plain'); - }); - }); - - return describe('image files', function() { - - it('should map .png to image/png', function() { - const content_type = this.ContentTypeMapper.map('example.png'); - return content_type.should.equal('image/png'); - }); - - it('should map .jpeg to image/jpeg', function() { - const content_type = this.ContentTypeMapper.map('example.jpeg'); - return content_type.should.equal('image/jpeg'); - }); - - return it('should map .svg to text/plain to protect against XSS (SVG can execute JS)', function() { - const content_type = this.ContentTypeMapper.map('example.svg'); - return content_type.should.equal('text/plain'); - }); - }); - }); -}); + return it('should map .svg to text/plain to protect against XSS (SVG can execute JS)', function() { + const content_type = this.ContentTypeMapper.map('example.svg') + return content_type.should.equal('text/plain') + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/DockerLockManagerTests.js b/services/clsi/test/unit/js/DockerLockManagerTests.js index 155a2464fd..9dcf9dcc14 100644 --- a/services/clsi/test/unit/js/DockerLockManagerTests.js +++ b/services/clsi/test/unit/js/DockerLockManagerTests.js @@ -9,185 +9,244 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const SandboxedModule = require('sandboxed-module'); -const sinon = require('sinon'); -require('chai').should(); -require("coffee-script"); -const modulePath = require('path').join(__dirname, '../../../app/coffee/DockerLockManager'); +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +require('chai').should() +require('coffee-script') +const modulePath = require('path').join( + __dirname, + '../../../app/coffee/DockerLockManager' +) -describe("LockManager", function() { - beforeEach(function() { - return this.LockManager = SandboxedModule.require(modulePath, { requires: { - "settings-sharelatex": (this.Settings = - {clsi: {docker: {}}}), - "logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub() }) - } - });}); +describe('LockManager', function() { + beforeEach(function() { + return (this.LockManager = SandboxedModule.require(modulePath, { + requires: { + 'settings-sharelatex': (this.Settings = { clsi: { docker: {} } }), + 'logger-sharelatex': (this.logger = { + log: sinon.stub(), + error: sinon.stub() + }) + } + })) + }) - return describe("runWithLock", function() { - describe("with a single lock", function() { - beforeEach(function(done) { - this.callback = sinon.stub(); - return this.LockManager.runWithLock("lock-one", releaseLock => - setTimeout(() => releaseLock(null, "hello", "world") - , 100) - - , (err, ...args) => { - this.callback(err,...Array.from(args)); - return done(); - }); - }); + return describe('runWithLock', function() { + describe('with a single lock', function() { + beforeEach(function(done) { + this.callback = sinon.stub() + return this.LockManager.runWithLock( + 'lock-one', + releaseLock => + setTimeout(() => releaseLock(null, 'hello', 'world'), 100), - return it("should call the callback", function() { - return this.callback.calledWith(null,"hello","world").should.equal(true); - }); - }); + (err, ...args) => { + this.callback(err, ...Array.from(args)) + return done() + } + ) + }) - describe("with two locks", function() { - beforeEach(function(done) { - this.callback1 = sinon.stub(); - this.callback2 = sinon.stub(); - this.LockManager.runWithLock("lock-one", releaseLock => - setTimeout(() => releaseLock(null, "hello", "world","one") - , 100) - - , (err, ...args) => { - return this.callback1(err,...Array.from(args)); - }); - return this.LockManager.runWithLock("lock-two", releaseLock => - setTimeout(() => releaseLock(null, "hello", "world","two") - , 200) - - , (err, ...args) => { - this.callback2(err,...Array.from(args)); - return done(); - }); - }); + return it('should call the callback', function() { + return this.callback + .calledWith(null, 'hello', 'world') + .should.equal(true) + }) + }) - it("should call the first callback", function() { - return this.callback1.calledWith(null,"hello","world","one").should.equal(true); - }); + describe('with two locks', function() { + beforeEach(function(done) { + this.callback1 = sinon.stub() + this.callback2 = sinon.stub() + this.LockManager.runWithLock( + 'lock-one', + releaseLock => + setTimeout(() => releaseLock(null, 'hello', 'world', 'one'), 100), - return it("should call the second callback", function() { - return this.callback2.calledWith(null,"hello","world","two").should.equal(true); - }); - }); + (err, ...args) => { + return this.callback1(err, ...Array.from(args)) + } + ) + return this.LockManager.runWithLock( + 'lock-two', + releaseLock => + setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 200), - return describe("with lock contention", function() { - describe("where the first lock is released quickly", function() { - beforeEach(function(done) { - this.LockManager.MAX_LOCK_WAIT_TIME = 1000; - this.LockManager.LOCK_TEST_INTERVAL = 100; - this.callback1 = sinon.stub(); - this.callback2 = sinon.stub(); - this.LockManager.runWithLock("lock", releaseLock => - setTimeout(() => releaseLock(null, "hello", "world","one") - , 100) - - , (err, ...args) => { - return this.callback1(err,...Array.from(args)); - }); - return this.LockManager.runWithLock("lock", releaseLock => - setTimeout(() => releaseLock(null, "hello", "world","two") - , 200) - - , (err, ...args) => { - this.callback2(err,...Array.from(args)); - return done(); - }); - }); + (err, ...args) => { + this.callback2(err, ...Array.from(args)) + return done() + } + ) + }) - it("should call the first callback", function() { - return this.callback1.calledWith(null,"hello","world","one").should.equal(true); - }); + it('should call the first callback', function() { + return this.callback1 + .calledWith(null, 'hello', 'world', 'one') + .should.equal(true) + }) - return it("should call the second callback", function() { - return this.callback2.calledWith(null,"hello","world","two").should.equal(true); - }); - }); + return it('should call the second callback', function() { + return this.callback2 + .calledWith(null, 'hello', 'world', 'two') + .should.equal(true) + }) + }) - describe("where the first lock is held longer than the waiting time", function() { - beforeEach(function(done) { - let doneTwo; - this.LockManager.MAX_LOCK_HOLD_TIME = 10000; - this.LockManager.MAX_LOCK_WAIT_TIME = 1000; - this.LockManager.LOCK_TEST_INTERVAL = 100; - this.callback1 = sinon.stub(); - this.callback2 = sinon.stub(); - let doneOne = (doneTwo = false); - const finish = function(key) { - if (key === 1) { doneOne = true; } - if (key === 2) { doneTwo = true; } - if (doneOne && doneTwo) { return done(); } - }; - this.LockManager.runWithLock("lock", releaseLock => - setTimeout(() => releaseLock(null, "hello", "world","one") - , 1100) - - , (err, ...args) => { - this.callback1(err,...Array.from(args)); - return finish(1); - }); - return this.LockManager.runWithLock("lock", releaseLock => - setTimeout(() => releaseLock(null, "hello", "world","two") - , 100) - - , (err, ...args) => { - this.callback2(err,...Array.from(args)); - return finish(2); - }); - }); + return describe('with lock contention', function() { + describe('where the first lock is released quickly', function() { + beforeEach(function(done) { + this.LockManager.MAX_LOCK_WAIT_TIME = 1000 + this.LockManager.LOCK_TEST_INTERVAL = 100 + this.callback1 = sinon.stub() + this.callback2 = sinon.stub() + this.LockManager.runWithLock( + 'lock', + releaseLock => + setTimeout(() => releaseLock(null, 'hello', 'world', 'one'), 100), - it("should call the first callback", function() { - return this.callback1.calledWith(null,"hello","world","one").should.equal(true); - }); + (err, ...args) => { + return this.callback1(err, ...Array.from(args)) + } + ) + return this.LockManager.runWithLock( + 'lock', + releaseLock => + setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 200), - return it("should call the second callback with an error", function() { - const error = sinon.match.instanceOf(Error); - return this.callback2.calledWith(error).should.equal(true); - }); - }); + (err, ...args) => { + this.callback2(err, ...Array.from(args)) + return done() + } + ) + }) - return describe("where the first lock is held longer than the max holding time", function() { - beforeEach(function(done) { - let doneTwo; - this.LockManager.MAX_LOCK_HOLD_TIME = 1000; - this.LockManager.MAX_LOCK_WAIT_TIME = 2000; - this.LockManager.LOCK_TEST_INTERVAL = 100; - this.callback1 = sinon.stub(); - this.callback2 = sinon.stub(); - let doneOne = (doneTwo = false); - const finish = function(key) { - if (key === 1) { doneOne = true; } - if (key === 2) { doneTwo = true; } - if (doneOne && doneTwo) { return done(); } - }; - this.LockManager.runWithLock("lock", releaseLock => - setTimeout(() => releaseLock(null, "hello", "world","one") - , 1500) - - , (err, ...args) => { - this.callback1(err,...Array.from(args)); - return finish(1); - }); - return this.LockManager.runWithLock("lock", releaseLock => - setTimeout(() => releaseLock(null, "hello", "world","two") - , 100) - - , (err, ...args) => { - this.callback2(err,...Array.from(args)); - return finish(2); - }); - }); + it('should call the first callback', function() { + return this.callback1 + .calledWith(null, 'hello', 'world', 'one') + .should.equal(true) + }) - it("should call the first callback", function() { - return this.callback1.calledWith(null,"hello","world","one").should.equal(true); - }); + return it('should call the second callback', function() { + return this.callback2 + .calledWith(null, 'hello', 'world', 'two') + .should.equal(true) + }) + }) - return it("should call the second callback", function() { - return this.callback2.calledWith(null,"hello","world","two").should.equal(true); - }); - }); - }); - }); -}); + describe('where the first lock is held longer than the waiting time', function() { + beforeEach(function(done) { + let doneTwo + this.LockManager.MAX_LOCK_HOLD_TIME = 10000 + this.LockManager.MAX_LOCK_WAIT_TIME = 1000 + this.LockManager.LOCK_TEST_INTERVAL = 100 + this.callback1 = sinon.stub() + this.callback2 = sinon.stub() + let doneOne = (doneTwo = false) + const finish = function(key) { + if (key === 1) { + doneOne = true + } + if (key === 2) { + doneTwo = true + } + if (doneOne && doneTwo) { + return done() + } + } + this.LockManager.runWithLock( + 'lock', + releaseLock => + setTimeout( + () => releaseLock(null, 'hello', 'world', 'one'), + 1100 + ), + + (err, ...args) => { + this.callback1(err, ...Array.from(args)) + return finish(1) + } + ) + return this.LockManager.runWithLock( + 'lock', + releaseLock => + setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 100), + + (err, ...args) => { + this.callback2(err, ...Array.from(args)) + return finish(2) + } + ) + }) + + it('should call the first callback', function() { + return this.callback1 + .calledWith(null, 'hello', 'world', 'one') + .should.equal(true) + }) + + return it('should call the second callback with an error', function() { + const error = sinon.match.instanceOf(Error) + return this.callback2.calledWith(error).should.equal(true) + }) + }) + + return describe('where the first lock is held longer than the max holding time', function() { + beforeEach(function(done) { + let doneTwo + this.LockManager.MAX_LOCK_HOLD_TIME = 1000 + this.LockManager.MAX_LOCK_WAIT_TIME = 2000 + this.LockManager.LOCK_TEST_INTERVAL = 100 + this.callback1 = sinon.stub() + this.callback2 = sinon.stub() + let doneOne = (doneTwo = false) + const finish = function(key) { + if (key === 1) { + doneOne = true + } + if (key === 2) { + doneTwo = true + } + if (doneOne && doneTwo) { + return done() + } + } + this.LockManager.runWithLock( + 'lock', + releaseLock => + setTimeout( + () => releaseLock(null, 'hello', 'world', 'one'), + 1500 + ), + + (err, ...args) => { + this.callback1(err, ...Array.from(args)) + return finish(1) + } + ) + return this.LockManager.runWithLock( + 'lock', + releaseLock => + setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 100), + + (err, ...args) => { + this.callback2(err, ...Array.from(args)) + return finish(2) + } + ) + }) + + it('should call the first callback', function() { + return this.callback1 + .calledWith(null, 'hello', 'world', 'one') + .should.equal(true) + }) + + return it('should call the second callback', function() { + return this.callback2 + .calledWith(null, 'hello', 'world', 'two') + .should.equal(true) + }) + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/DockerRunnerTests.js b/services/clsi/test/unit/js/DockerRunnerTests.js index 152b8b9637..e43a044c80 100644 --- a/services/clsi/test/unit/js/DockerRunnerTests.js +++ b/services/clsi/test/unit/js/DockerRunnerTests.js @@ -13,651 +13,826 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const SandboxedModule = require('sandboxed-module'); -const sinon = require('sinon'); -require('chai').should(); -const { expect } = require('chai'); -require("coffee-script"); -const modulePath = require('path').join(__dirname, '../../../app/coffee/DockerRunner'); -const Path = require("path"); - -describe("DockerRunner", function() { - beforeEach(function() { - let container, Docker, Timer; - this.container = (container = {}); - this.DockerRunner = SandboxedModule.require(modulePath, { requires: { - "settings-sharelatex": (this.Settings = { - clsi: { docker: {} - }, - path: {} - }), - "logger-sharelatex": (this.logger = { - log: sinon.stub(), - error: sinon.stub(), - info: sinon.stub(), - warn: sinon.stub() - }), - "dockerode": (Docker = (function() { - Docker = class Docker { - static initClass() { - this.prototype.getContainer = sinon.stub().returns(container); - this.prototype.createContainer = sinon.stub().yields(null, container); - this.prototype.listContainers = sinon.stub(); - } - }; - Docker.initClass(); - return Docker; - })()), - "fs": (this.fs = { stat: sinon.stub().yields(null,{isDirectory(){ return true; }}) }), - "./Metrics": { - Timer: (Timer = class Timer { - done() {} - }) - }, - "./LockManager": { - runWithLock(key, runner, callback) { return runner(callback); } - } - } - } - ); - this.Docker = Docker; - this.getContainer = Docker.prototype.getContainer; - this.createContainer = Docker.prototype.createContainer; - this.listContainers = Docker.prototype.listContainers; - - this.directory = "/local/compile/directory"; - this.mainFile = "main-file.tex"; - this.compiler = "pdflatex"; - this.image = "example.com/sharelatex/image:2016.2"; - this.env = {}; - this.callback = sinon.stub(); - this.project_id = "project-id-123"; - this.volumes = - {"/local/compile/directory": "/compile"}; - this.Settings.clsi.docker.image = (this.defaultImage = "default-image"); - return this.Settings.clsi.docker.env = {PATH: "mock-path"}; - }); - - describe("run", function() { - beforeEach(function(done){ - this.DockerRunner._getContainerOptions = sinon.stub().returns(this.options = {mockoptions: "foo"}); - this.DockerRunner._fingerprintContainer = sinon.stub().returns(this.fingerprint = "fingerprint"); - - this.name = `project-${this.project_id}-${this.fingerprint}`; - - this.command = ["mock", "command", "--outdir=$COMPILE_DIR"]; - this.command_with_dir = ["mock", "command", "--outdir=/compile"]; - this.timeout = 42000; - return done(); - }); - - describe("successfully", function() { - beforeEach(function(done){ - this.DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, (this.output = "mock-output")); - return this.DockerRunner.run(this.project_id, this.command, this.directory, this.image, this.timeout, this.env, (err, output)=> { - this.callback(err, output); - return done(); - }); - }); - - it("should generate the options for the container", function() { - return this.DockerRunner._getContainerOptions - .calledWith(this.command_with_dir, this.image, this.volumes, this.timeout) - .should.equal(true); - }); - - it("should generate the fingerprint from the returned options", function() { - return this.DockerRunner._fingerprintContainer - .calledWith(this.options) - .should.equal(true); - }); - - it("should do the run", function() { - return this.DockerRunner._runAndWaitForContainer - .calledWith(this.options, this.volumes, this.timeout) - .should.equal(true); - }); - - return it("should call the callback", function() { - return this.callback.calledWith(null, this.output).should.equal(true); - }); - }); - - describe('when path.sandboxedCompilesHostDir is set', function() { - - beforeEach(function() { - this.Settings.path.sandboxedCompilesHostDir = '/some/host/dir/compiles'; - this.directory = '/var/lib/sharelatex/data/compiles/xyz'; - this.DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, (this.output = "mock-output")); - return this.DockerRunner.run(this.project_id, this.command, this.directory, this.image, this.timeout, this.env, this.callback); - }); - - it('should re-write the bind directory', function() { - const volumes = this.DockerRunner._runAndWaitForContainer.lastCall.args[1]; - return expect(volumes).to.deep.equal({ - '/some/host/dir/compiles/xyz': '/compile' - }); - }); - - return it("should call the callback", function() { - return this.callback.calledWith(null, this.output).should.equal(true); - }); - }); - - describe("when the run throws an error", function() { - beforeEach(function() { - let firstTime = true; - this.output = "mock-output"; - this.DockerRunner._runAndWaitForContainer = (options, volumes, timeout, callback) => { - if (callback == null) { callback = function(error, output){}; } - if (firstTime) { - firstTime = false; - return callback(new Error("HTTP code is 500 which indicates error: server error")); - } else { - return callback(null, this.output); - } - }; - sinon.spy(this.DockerRunner, "_runAndWaitForContainer"); - this.DockerRunner.destroyContainer = sinon.stub().callsArg(3); - return this.DockerRunner.run(this.project_id, this.command, this.directory, this.image, this.timeout, this.env, this.callback); - }); - - it("should do the run twice", function() { - return this.DockerRunner._runAndWaitForContainer - .calledTwice.should.equal(true); - }); - - it("should destroy the container in between", function() { - return this.DockerRunner.destroyContainer - .calledWith(this.name, null) - .should.equal(true); - }); - - return it("should call the callback", function() { - return this.callback.calledWith(null, this.output).should.equal(true); - }); - }); - - describe("with no image", function() { - beforeEach(function() { - this.DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, (this.output = "mock-output")); - return this.DockerRunner.run(this.project_id, this.command, this.directory, null, this.timeout, this.env, this.callback); - }); - - return it("should use the default image", function() { - return this.DockerRunner._getContainerOptions - .calledWith(this.command_with_dir, this.defaultImage, this.volumes, this.timeout) - .should.equal(true); - }); - }); - - return describe("with image override", function() { - beforeEach(function() { - this.Settings.texliveImageNameOveride = "overrideimage.com/something"; - this.DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, (this.output = "mock-output")); - return this.DockerRunner.run(this.project_id, this.command, this.directory, this.image, this.timeout, this.env, this.callback); - }); - - return it("should use the override and keep the tag", function() { - const image = this.DockerRunner._getContainerOptions.args[0][1]; - return image.should.equal("overrideimage.com/something/image:2016.2"); - }); - }); - }); - - describe("_runAndWaitForContainer", function() { - beforeEach(function() { - this.options = {mockoptions: "foo", name: (this.name = "mock-name")}; - this.DockerRunner.startContainer = (options, volumes, attachStreamHandler, callback) => { - attachStreamHandler(null, (this.output = "mock-output")); - return callback(null, (this.containerId = "container-id")); - }; - sinon.spy(this.DockerRunner, "startContainer"); - this.DockerRunner.waitForContainer = sinon.stub().callsArgWith(2, null, (this.exitCode = 42)); - return this.DockerRunner._runAndWaitForContainer(this.options, this.volumes, this.timeout, this.callback); - }); - - it("should create/start the container", function() { - return this.DockerRunner.startContainer - .calledWith(this.options, this.volumes) - .should.equal(true); - }); - - it("should wait for the container to finish", function() { - return this.DockerRunner.waitForContainer - .calledWith(this.name, this.timeout) - .should.equal(true); - }); - - return it("should call the callback with the output", function() { - return this.callback.calledWith(null, this.output).should.equal(true); - }); - }); - - describe("startContainer", function() { - beforeEach(function() { - this.attachStreamHandler = sinon.stub(); - this.attachStreamHandler.cock = true; - this.options = {mockoptions: "foo", name: "mock-name"}; - this.container.inspect = sinon.stub().callsArgWith(0); - this.DockerRunner.attachToContainer = (containerId, attachStreamHandler, cb)=> { - attachStreamHandler(); - return cb(); - }; - return sinon.spy(this.DockerRunner, "attachToContainer"); - }); - - - - describe("when the container exists", function() { - beforeEach(function() { - this.container.inspect = sinon.stub().callsArgWith(0); - this.container.start = sinon.stub().yields(); - - return this.DockerRunner.startContainer(this.options, this.volumes, this.callback, () => {}); - }); - - it("should start the container with the given name", function() { - this.getContainer - .calledWith(this.options.name) - .should.equal(true); - return this.container.start - .called - .should.equal(true); - }); - - it("should not try to create the container", function() { - return this.createContainer.called.should.equal(false); - }); - - it("should attach to the container", function() { - return this.DockerRunner.attachToContainer.called.should.equal(true); - }); - - it("should call the callback", function() { - return this.callback.called.should.equal(true); - }); - - return it("should attach before the container starts", function() { - return sinon.assert.callOrder(this.DockerRunner.attachToContainer, this.container.start); - }); - }); - - describe("when the container does not exist", function() { - beforeEach(function(){ - const exists = false; - this.container.start = sinon.stub().yields(); - this.container.inspect = sinon.stub().callsArgWith(0, {statusCode:404}); - return this.DockerRunner.startContainer(this.options, this.volumes, this.attachStreamHandler, this.callback); - }); - - it("should create the container", function() { - return this.createContainer - .calledWith(this.options) - .should.equal(true); - }); - - it("should call the callback and stream handler", function() { - this.attachStreamHandler.called.should.equal(true); - return this.callback.called.should.equal(true); - }); - - it("should attach to the container", function() { - return this.DockerRunner.attachToContainer.called.should.equal(true); - }); - - return it("should attach before the container starts", function() { - return sinon.assert.callOrder(this.DockerRunner.attachToContainer, this.container.start); - }); - }); - - - describe("when the container is already running", function() { - beforeEach(function() { - const error = new Error(`HTTP code is 304 which indicates error: server error - start: Cannot start container ${this.name}: The container MOCKID is already running.`); - error.statusCode = 304; - this.container.start = sinon.stub().yields(error); - this.container.inspect = sinon.stub().callsArgWith(0); - return this.DockerRunner.startContainer(this.options, this.volumes, this.attachStreamHandler, this.callback); - }); - - it("should not try to create the container", function() { - return this.createContainer.called.should.equal(false); - }); - - return it("should call the callback and stream handler without an error", function() { - this.attachStreamHandler.called.should.equal(true); - return this.callback.called.should.equal(true); - }); - }); - - describe("when a volume does not exist", function() { - beforeEach(function(){ - this.fs.stat = sinon.stub().yields(new Error("no such path")); - return this.DockerRunner.startContainer(this.options, this.volumes, this.attachStreamHandler, this.callback); - }); - - it("should not try to create the container", function() { - return this.createContainer.called.should.equal(false); - }); - - return it("should call the callback with an error", function() { - return this.callback.calledWith(new Error()).should.equal(true); - }); - }); - - describe("when a volume exists but is not a directory", function() { - beforeEach(function() { - this.fs.stat = sinon.stub().yields(null, {isDirectory() { return false; }}); - return this.DockerRunner.startContainer(this.options, this.volumes, this.attachStreamHandler, this.callback); - }); - - it("should not try to create the container", function() { - return this.createContainer.called.should.equal(false); - }); - - return it("should call the callback with an error", function() { - return this.callback.calledWith(new Error()).should.equal(true); - }); - }); - - describe("when a volume does not exist, but sibling-containers are used", function() { - beforeEach(function() { - this.fs.stat = sinon.stub().yields(new Error("no such path")); - this.Settings.path.sandboxedCompilesHostDir = '/some/path'; - this.container.start = sinon.stub().yields(); - return this.DockerRunner.startContainer(this.options, this.volumes, this.callback); - }); - - afterEach(function() { - return delete this.Settings.path.sandboxedCompilesHostDir; - }); - - it("should start the container with the given name", function() { - this.getContainer - .calledWith(this.options.name) - .should.equal(true); - return this.container.start - .called - .should.equal(true); - }); - - it("should not try to create the container", function() { - return this.createContainer.called.should.equal(false); - }); - - return it("should call the callback", function() { - this.callback.called.should.equal(true); - return this.callback.calledWith(new Error()).should.equal(false); - }); - }); - - return describe("when the container tries to be created, but already has been (race condition)", function() {}); - }); - - describe("waitForContainer", function() { - beforeEach(function() { - this.containerId = "container-id"; - this.timeout = 5000; - this.container.wait = sinon.stub().yields(null, {StatusCode: (this.statusCode = 42)}); - return this.container.kill = sinon.stub().yields(); - }); - - describe("when the container returns in time", function() { - beforeEach(function() { - return this.DockerRunner.waitForContainer(this.containerId, this.timeout, this.callback); - }); - - it("should wait for the container", function() { - this.getContainer - .calledWith(this.containerId) - .should.equal(true); - return this.container.wait - .called - .should.equal(true); - }); - - return it("should call the callback with the exit", function() { - return this.callback - .calledWith(null, this.statusCode) - .should.equal(true); - }); - }); - - return describe("when the container does not return before the timeout", function() { - beforeEach(function(done) { - this.container.wait = function(callback) { - if (callback == null) { callback = function(error, exitCode) {}; } - return setTimeout(() => callback(null, {StatusCode: 42}) - , 100); - }; - this.timeout = 5; - return this.DockerRunner.waitForContainer(this.containerId, this.timeout, (...args) => { - this.callback(...Array.from(args || [])); - return done(); - }); - }); - - it("should call kill on the container", function() { - this.getContainer - .calledWith(this.containerId) - .should.equal(true); - return this.container.kill - .called - .should.equal(true); - }); - - return it("should call the callback with an error", function() { - const error = new Error("container timed out"); - error.timedout = true; - return this.callback - .calledWith(error) - .should.equal(true); - }); - }); - }); - - describe("destroyOldContainers", function() { - beforeEach(function(done) { - const oneHourInSeconds = 60 * 60; - const oneHourInMilliseconds = oneHourInSeconds * 1000; - const nowInSeconds = Date.now()/1000; - this.containers = [{ - Name: "/project-old-container-name", - Id: "old-container-id", - Created: nowInSeconds - oneHourInSeconds - 100 - }, { - Name: "/project-new-container-name", - Id: "new-container-id", - Created: (nowInSeconds - oneHourInSeconds) + 100 - }, { - Name: "/totally-not-a-project-container", - Id: "some-random-id", - Created: nowInSeconds - (2 * oneHourInSeconds ) - }]; - this.DockerRunner.MAX_CONTAINER_AGE = oneHourInMilliseconds; - this.listContainers.callsArgWith(1, null, this.containers); - this.DockerRunner.destroyContainer = sinon.stub().callsArg(3); - return this.DockerRunner.destroyOldContainers(error => { - this.callback(error); - return done(); - }); - }); - - it("should list all containers", function() { - return this.listContainers - .calledWith({all: true}) - .should.equal(true); - }); - - it("should destroy old containers", function() { - this.DockerRunner.destroyContainer - .callCount - .should.equal(1); - return this.DockerRunner.destroyContainer - .calledWith("/project-old-container-name", "old-container-id") - .should.equal(true); - }); - - it("should not destroy new containers", function() { - return this.DockerRunner.destroyContainer - .calledWith("/project-new-container-name", "new-container-id") - .should.equal(false); - }); - - it("should not destroy non-project containers", function() { - return this.DockerRunner.destroyContainer - .calledWith("/totally-not-a-project-container", "some-random-id") - .should.equal(false); - }); - - return it("should callback the callback", function() { - return this.callback.called.should.equal(true); - }); - }); - - - describe('_destroyContainer', function() { - beforeEach(function() { - this.containerId = 'some_id'; - this.fakeContainer = - {remove: sinon.stub().callsArgWith(1, null)}; - return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer); - }); - - it('should get the container', function(done) { - return this.DockerRunner._destroyContainer(this.containerId, false, err => { - this.Docker.prototype.getContainer.callCount.should.equal(1); - this.Docker.prototype.getContainer.calledWith(this.containerId).should.equal(true); - return done(); - }); - }); - - it('should try to force-destroy the container when shouldForce=true', function(done) { - return this.DockerRunner._destroyContainer(this.containerId, true, err => { - this.fakeContainer.remove.callCount.should.equal(1); - this.fakeContainer.remove.calledWith({force: true}).should.equal(true); - return done(); - }); - }); - - it('should not try to force-destroy the container when shouldForce=false', function(done) { - return this.DockerRunner._destroyContainer(this.containerId, false, err => { - this.fakeContainer.remove.callCount.should.equal(1); - this.fakeContainer.remove.calledWith({force: false}).should.equal(true); - return done(); - }); - }); - - it('should not produce an error', function(done) { - return this.DockerRunner._destroyContainer(this.containerId, false, err => { - expect(err).to.equal(null); - return done(); - }); - }); - - describe('when the container is already gone', function() { - beforeEach(function() { - this.fakeError = new Error('woops'); - this.fakeError.statusCode = 404; - this.fakeContainer = - {remove: sinon.stub().callsArgWith(1, this.fakeError)}; - return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer); - }); - - return it('should not produce an error', function(done) { - return this.DockerRunner._destroyContainer(this.containerId, false, err => { - expect(err).to.equal(null); - return done(); - }); - }); - }); - - return describe('when container.destroy produces an error', function(done) { - beforeEach(function() { - this.fakeError = new Error('woops'); - this.fakeError.statusCode = 500; - this.fakeContainer = - {remove: sinon.stub().callsArgWith(1, this.fakeError)}; - return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer); - }); - - return it('should produce an error', function(done) { - return this.DockerRunner._destroyContainer(this.containerId, false, err => { - expect(err).to.not.equal(null); - expect(err).to.equal(this.fakeError); - return done(); - }); - }); - }); - }); - - - return describe('kill', function() { - beforeEach(function() { - this.containerId = 'some_id'; - this.fakeContainer = - {kill: sinon.stub().callsArgWith(0, null)}; - return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer); - }); - - it('should get the container', function(done) { - return this.DockerRunner.kill(this.containerId, err => { - this.Docker.prototype.getContainer.callCount.should.equal(1); - this.Docker.prototype.getContainer.calledWith(this.containerId).should.equal(true); - return done(); - }); - }); - - it('should try to force-destroy the container', function(done) { - return this.DockerRunner.kill(this.containerId, err => { - this.fakeContainer.kill.callCount.should.equal(1); - return done(); - }); - }); - - it('should not produce an error', function(done) { - return this.DockerRunner.kill(this.containerId, err => { - expect(err).to.equal(undefined); - return done(); - }); - }); - - describe('when the container is not actually running', function() { - beforeEach(function() { - this.fakeError = new Error('woops'); - this.fakeError.statusCode = 500; - this.fakeError.message = 'Cannot kill container <whatever> is not running'; - this.fakeContainer = - {kill: sinon.stub().callsArgWith(0, this.fakeError)}; - return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer); - }); - - return it('should not produce an error', function(done) { - return this.DockerRunner.kill(this.containerId, err => { - expect(err).to.equal(undefined); - return done(); - }); - }); - }); - - return describe('when container.kill produces a legitimate error', function(done) { - beforeEach(function() { - this.fakeError = new Error('woops'); - this.fakeError.statusCode = 500; - this.fakeError.message = 'Totally legitimate reason to throw an error'; - this.fakeContainer = - {kill: sinon.stub().callsArgWith(0, this.fakeError)}; - return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer); - }); - - return it('should produce an error', function(done) { - return this.DockerRunner.kill(this.containerId, err => { - expect(err).to.not.equal(undefined); - expect(err).to.equal(this.fakeError); - return done(); - }); - }); - }); - }); -}); +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +require('chai').should() +const { expect } = require('chai') +require('coffee-script') +const modulePath = require('path').join( + __dirname, + '../../../app/coffee/DockerRunner' +) +const Path = require('path') + +describe('DockerRunner', function() { + beforeEach(function() { + let container, Docker, Timer + this.container = container = {} + this.DockerRunner = SandboxedModule.require(modulePath, { + requires: { + 'settings-sharelatex': (this.Settings = { + clsi: { docker: {} }, + path: {} + }), + 'logger-sharelatex': (this.logger = { + log: sinon.stub(), + error: sinon.stub(), + info: sinon.stub(), + warn: sinon.stub() + }), + dockerode: (Docker = (function() { + Docker = class Docker { + static initClass() { + this.prototype.getContainer = sinon.stub().returns(container) + this.prototype.createContainer = sinon + .stub() + .yields(null, container) + this.prototype.listContainers = sinon.stub() + } + } + Docker.initClass() + return Docker + })()), + fs: (this.fs = { + stat: sinon.stub().yields(null, { + isDirectory() { + return true + } + }) + }), + './Metrics': { + Timer: (Timer = class Timer { + done() {} + }) + }, + './LockManager': { + runWithLock(key, runner, callback) { + return runner(callback) + } + } + } + }) + this.Docker = Docker + this.getContainer = Docker.prototype.getContainer + this.createContainer = Docker.prototype.createContainer + this.listContainers = Docker.prototype.listContainers + + this.directory = '/local/compile/directory' + this.mainFile = 'main-file.tex' + this.compiler = 'pdflatex' + this.image = 'example.com/sharelatex/image:2016.2' + this.env = {} + this.callback = sinon.stub() + this.project_id = 'project-id-123' + this.volumes = { '/local/compile/directory': '/compile' } + this.Settings.clsi.docker.image = this.defaultImage = 'default-image' + return (this.Settings.clsi.docker.env = { PATH: 'mock-path' }) + }) + + describe('run', function() { + beforeEach(function(done) { + this.DockerRunner._getContainerOptions = sinon + .stub() + .returns((this.options = { mockoptions: 'foo' })) + this.DockerRunner._fingerprintContainer = sinon + .stub() + .returns((this.fingerprint = 'fingerprint')) + + this.name = `project-${this.project_id}-${this.fingerprint}` + + this.command = ['mock', 'command', '--outdir=$COMPILE_DIR'] + this.command_with_dir = ['mock', 'command', '--outdir=/compile'] + this.timeout = 42000 + return done() + }) + + describe('successfully', function() { + beforeEach(function(done) { + this.DockerRunner._runAndWaitForContainer = sinon + .stub() + .callsArgWith(3, null, (this.output = 'mock-output')) + return this.DockerRunner.run( + this.project_id, + this.command, + this.directory, + this.image, + this.timeout, + this.env, + (err, output) => { + this.callback(err, output) + return done() + } + ) + }) + + it('should generate the options for the container', function() { + return this.DockerRunner._getContainerOptions + .calledWith( + this.command_with_dir, + this.image, + this.volumes, + this.timeout + ) + .should.equal(true) + }) + + it('should generate the fingerprint from the returned options', function() { + return this.DockerRunner._fingerprintContainer + .calledWith(this.options) + .should.equal(true) + }) + + it('should do the run', function() { + return this.DockerRunner._runAndWaitForContainer + .calledWith(this.options, this.volumes, this.timeout) + .should.equal(true) + }) + + return it('should call the callback', function() { + return this.callback.calledWith(null, this.output).should.equal(true) + }) + }) + + describe('when path.sandboxedCompilesHostDir is set', function() { + beforeEach(function() { + this.Settings.path.sandboxedCompilesHostDir = '/some/host/dir/compiles' + this.directory = '/var/lib/sharelatex/data/compiles/xyz' + this.DockerRunner._runAndWaitForContainer = sinon + .stub() + .callsArgWith(3, null, (this.output = 'mock-output')) + return this.DockerRunner.run( + this.project_id, + this.command, + this.directory, + this.image, + this.timeout, + this.env, + this.callback + ) + }) + + it('should re-write the bind directory', function() { + const volumes = this.DockerRunner._runAndWaitForContainer.lastCall + .args[1] + return expect(volumes).to.deep.equal({ + '/some/host/dir/compiles/xyz': '/compile' + }) + }) + + return it('should call the callback', function() { + return this.callback.calledWith(null, this.output).should.equal(true) + }) + }) + + describe('when the run throws an error', function() { + beforeEach(function() { + let firstTime = true + this.output = 'mock-output' + this.DockerRunner._runAndWaitForContainer = ( + options, + volumes, + timeout, + callback + ) => { + if (callback == null) { + callback = function(error, output) {} + } + if (firstTime) { + firstTime = false + return callback( + new Error('HTTP code is 500 which indicates error: server error') + ) + } else { + return callback(null, this.output) + } + } + sinon.spy(this.DockerRunner, '_runAndWaitForContainer') + this.DockerRunner.destroyContainer = sinon.stub().callsArg(3) + return this.DockerRunner.run( + this.project_id, + this.command, + this.directory, + this.image, + this.timeout, + this.env, + this.callback + ) + }) + + it('should do the run twice', function() { + return this.DockerRunner._runAndWaitForContainer.calledTwice.should.equal( + true + ) + }) + + it('should destroy the container in between', function() { + return this.DockerRunner.destroyContainer + .calledWith(this.name, null) + .should.equal(true) + }) + + return it('should call the callback', function() { + return this.callback.calledWith(null, this.output).should.equal(true) + }) + }) + + describe('with no image', function() { + beforeEach(function() { + this.DockerRunner._runAndWaitForContainer = sinon + .stub() + .callsArgWith(3, null, (this.output = 'mock-output')) + return this.DockerRunner.run( + this.project_id, + this.command, + this.directory, + null, + this.timeout, + this.env, + this.callback + ) + }) + + return it('should use the default image', function() { + return this.DockerRunner._getContainerOptions + .calledWith( + this.command_with_dir, + this.defaultImage, + this.volumes, + this.timeout + ) + .should.equal(true) + }) + }) + + return describe('with image override', function() { + beforeEach(function() { + this.Settings.texliveImageNameOveride = 'overrideimage.com/something' + this.DockerRunner._runAndWaitForContainer = sinon + .stub() + .callsArgWith(3, null, (this.output = 'mock-output')) + return this.DockerRunner.run( + this.project_id, + this.command, + this.directory, + this.image, + this.timeout, + this.env, + this.callback + ) + }) + + return it('should use the override and keep the tag', function() { + const image = this.DockerRunner._getContainerOptions.args[0][1] + return image.should.equal('overrideimage.com/something/image:2016.2') + }) + }) + }) + + describe('_runAndWaitForContainer', function() { + beforeEach(function() { + this.options = { mockoptions: 'foo', name: (this.name = 'mock-name') } + this.DockerRunner.startContainer = ( + options, + volumes, + attachStreamHandler, + callback + ) => { + attachStreamHandler(null, (this.output = 'mock-output')) + return callback(null, (this.containerId = 'container-id')) + } + sinon.spy(this.DockerRunner, 'startContainer') + this.DockerRunner.waitForContainer = sinon + .stub() + .callsArgWith(2, null, (this.exitCode = 42)) + return this.DockerRunner._runAndWaitForContainer( + this.options, + this.volumes, + this.timeout, + this.callback + ) + }) + + it('should create/start the container', function() { + return this.DockerRunner.startContainer + .calledWith(this.options, this.volumes) + .should.equal(true) + }) + + it('should wait for the container to finish', function() { + return this.DockerRunner.waitForContainer + .calledWith(this.name, this.timeout) + .should.equal(true) + }) + + return it('should call the callback with the output', function() { + return this.callback.calledWith(null, this.output).should.equal(true) + }) + }) + + describe('startContainer', function() { + beforeEach(function() { + this.attachStreamHandler = sinon.stub() + this.attachStreamHandler.cock = true + this.options = { mockoptions: 'foo', name: 'mock-name' } + this.container.inspect = sinon.stub().callsArgWith(0) + this.DockerRunner.attachToContainer = ( + containerId, + attachStreamHandler, + cb + ) => { + attachStreamHandler() + return cb() + } + return sinon.spy(this.DockerRunner, 'attachToContainer') + }) + + describe('when the container exists', function() { + beforeEach(function() { + this.container.inspect = sinon.stub().callsArgWith(0) + this.container.start = sinon.stub().yields() + + return this.DockerRunner.startContainer( + this.options, + this.volumes, + this.callback, + () => {} + ) + }) + + it('should start the container with the given name', function() { + this.getContainer.calledWith(this.options.name).should.equal(true) + return this.container.start.called.should.equal(true) + }) + + it('should not try to create the container', function() { + return this.createContainer.called.should.equal(false) + }) + + it('should attach to the container', function() { + return this.DockerRunner.attachToContainer.called.should.equal(true) + }) + + it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + + return it('should attach before the container starts', function() { + return sinon.assert.callOrder( + this.DockerRunner.attachToContainer, + this.container.start + ) + }) + }) + + describe('when the container does not exist', function() { + beforeEach(function() { + const exists = false + this.container.start = sinon.stub().yields() + this.container.inspect = sinon + .stub() + .callsArgWith(0, { statusCode: 404 }) + return this.DockerRunner.startContainer( + this.options, + this.volumes, + this.attachStreamHandler, + this.callback + ) + }) + + it('should create the container', function() { + return this.createContainer.calledWith(this.options).should.equal(true) + }) + + it('should call the callback and stream handler', function() { + this.attachStreamHandler.called.should.equal(true) + return this.callback.called.should.equal(true) + }) + + it('should attach to the container', function() { + return this.DockerRunner.attachToContainer.called.should.equal(true) + }) + + return it('should attach before the container starts', function() { + return sinon.assert.callOrder( + this.DockerRunner.attachToContainer, + this.container.start + ) + }) + }) + + describe('when the container is already running', function() { + beforeEach(function() { + const error = new Error( + `HTTP code is 304 which indicates error: server error - start: Cannot start container ${this.name}: The container MOCKID is already running.` + ) + error.statusCode = 304 + this.container.start = sinon.stub().yields(error) + this.container.inspect = sinon.stub().callsArgWith(0) + return this.DockerRunner.startContainer( + this.options, + this.volumes, + this.attachStreamHandler, + this.callback + ) + }) + + it('should not try to create the container', function() { + return this.createContainer.called.should.equal(false) + }) + + return it('should call the callback and stream handler without an error', function() { + this.attachStreamHandler.called.should.equal(true) + return this.callback.called.should.equal(true) + }) + }) + + describe('when a volume does not exist', function() { + beforeEach(function() { + this.fs.stat = sinon.stub().yields(new Error('no such path')) + return this.DockerRunner.startContainer( + this.options, + this.volumes, + this.attachStreamHandler, + this.callback + ) + }) + + it('should not try to create the container', function() { + return this.createContainer.called.should.equal(false) + }) + + return it('should call the callback with an error', function() { + return this.callback.calledWith(new Error()).should.equal(true) + }) + }) + + describe('when a volume exists but is not a directory', function() { + beforeEach(function() { + this.fs.stat = sinon.stub().yields(null, { + isDirectory() { + return false + } + }) + return this.DockerRunner.startContainer( + this.options, + this.volumes, + this.attachStreamHandler, + this.callback + ) + }) + + it('should not try to create the container', function() { + return this.createContainer.called.should.equal(false) + }) + + return it('should call the callback with an error', function() { + return this.callback.calledWith(new Error()).should.equal(true) + }) + }) + + describe('when a volume does not exist, but sibling-containers are used', function() { + beforeEach(function() { + this.fs.stat = sinon.stub().yields(new Error('no such path')) + this.Settings.path.sandboxedCompilesHostDir = '/some/path' + this.container.start = sinon.stub().yields() + return this.DockerRunner.startContainer( + this.options, + this.volumes, + this.callback + ) + }) + + afterEach(function() { + return delete this.Settings.path.sandboxedCompilesHostDir + }) + + it('should start the container with the given name', function() { + this.getContainer.calledWith(this.options.name).should.equal(true) + return this.container.start.called.should.equal(true) + }) + + it('should not try to create the container', function() { + return this.createContainer.called.should.equal(false) + }) + + return it('should call the callback', function() { + this.callback.called.should.equal(true) + return this.callback.calledWith(new Error()).should.equal(false) + }) + }) + + return describe('when the container tries to be created, but already has been (race condition)', function() {}) + }) + + describe('waitForContainer', function() { + beforeEach(function() { + this.containerId = 'container-id' + this.timeout = 5000 + this.container.wait = sinon + .stub() + .yields(null, { StatusCode: (this.statusCode = 42) }) + return (this.container.kill = sinon.stub().yields()) + }) + + describe('when the container returns in time', function() { + beforeEach(function() { + return this.DockerRunner.waitForContainer( + this.containerId, + this.timeout, + this.callback + ) + }) + + it('should wait for the container', function() { + this.getContainer.calledWith(this.containerId).should.equal(true) + return this.container.wait.called.should.equal(true) + }) + + return it('should call the callback with the exit', function() { + return this.callback + .calledWith(null, this.statusCode) + .should.equal(true) + }) + }) + + return describe('when the container does not return before the timeout', function() { + beforeEach(function(done) { + this.container.wait = function(callback) { + if (callback == null) { + callback = function(error, exitCode) {} + } + return setTimeout(() => callback(null, { StatusCode: 42 }), 100) + } + this.timeout = 5 + return this.DockerRunner.waitForContainer( + this.containerId, + this.timeout, + (...args) => { + this.callback(...Array.from(args || [])) + return done() + } + ) + }) + + it('should call kill on the container', function() { + this.getContainer.calledWith(this.containerId).should.equal(true) + return this.container.kill.called.should.equal(true) + }) + + return it('should call the callback with an error', function() { + const error = new Error('container timed out') + error.timedout = true + return this.callback.calledWith(error).should.equal(true) + }) + }) + }) + + describe('destroyOldContainers', function() { + beforeEach(function(done) { + const oneHourInSeconds = 60 * 60 + const oneHourInMilliseconds = oneHourInSeconds * 1000 + const nowInSeconds = Date.now() / 1000 + this.containers = [ + { + Name: '/project-old-container-name', + Id: 'old-container-id', + Created: nowInSeconds - oneHourInSeconds - 100 + }, + { + Name: '/project-new-container-name', + Id: 'new-container-id', + Created: nowInSeconds - oneHourInSeconds + 100 + }, + { + Name: '/totally-not-a-project-container', + Id: 'some-random-id', + Created: nowInSeconds - 2 * oneHourInSeconds + } + ] + this.DockerRunner.MAX_CONTAINER_AGE = oneHourInMilliseconds + this.listContainers.callsArgWith(1, null, this.containers) + this.DockerRunner.destroyContainer = sinon.stub().callsArg(3) + return this.DockerRunner.destroyOldContainers(error => { + this.callback(error) + return done() + }) + }) + + it('should list all containers', function() { + return this.listContainers.calledWith({ all: true }).should.equal(true) + }) + + it('should destroy old containers', function() { + this.DockerRunner.destroyContainer.callCount.should.equal(1) + return this.DockerRunner.destroyContainer + .calledWith('/project-old-container-name', 'old-container-id') + .should.equal(true) + }) + + it('should not destroy new containers', function() { + return this.DockerRunner.destroyContainer + .calledWith('/project-new-container-name', 'new-container-id') + .should.equal(false) + }) + + it('should not destroy non-project containers', function() { + return this.DockerRunner.destroyContainer + .calledWith('/totally-not-a-project-container', 'some-random-id') + .should.equal(false) + }) + + return it('should callback the callback', function() { + return this.callback.called.should.equal(true) + }) + }) + + describe('_destroyContainer', function() { + beforeEach(function() { + this.containerId = 'some_id' + this.fakeContainer = { remove: sinon.stub().callsArgWith(1, null) } + return (this.Docker.prototype.getContainer = sinon + .stub() + .returns(this.fakeContainer)) + }) + + it('should get the container', function(done) { + return this.DockerRunner._destroyContainer( + this.containerId, + false, + err => { + this.Docker.prototype.getContainer.callCount.should.equal(1) + this.Docker.prototype.getContainer + .calledWith(this.containerId) + .should.equal(true) + return done() + } + ) + }) + + it('should try to force-destroy the container when shouldForce=true', function(done) { + return this.DockerRunner._destroyContainer( + this.containerId, + true, + err => { + this.fakeContainer.remove.callCount.should.equal(1) + this.fakeContainer.remove + .calledWith({ force: true }) + .should.equal(true) + return done() + } + ) + }) + + it('should not try to force-destroy the container when shouldForce=false', function(done) { + return this.DockerRunner._destroyContainer( + this.containerId, + false, + err => { + this.fakeContainer.remove.callCount.should.equal(1) + this.fakeContainer.remove + .calledWith({ force: false }) + .should.equal(true) + return done() + } + ) + }) + + it('should not produce an error', function(done) { + return this.DockerRunner._destroyContainer( + this.containerId, + false, + err => { + expect(err).to.equal(null) + return done() + } + ) + }) + + describe('when the container is already gone', function() { + beforeEach(function() { + this.fakeError = new Error('woops') + this.fakeError.statusCode = 404 + this.fakeContainer = { + remove: sinon.stub().callsArgWith(1, this.fakeError) + } + return (this.Docker.prototype.getContainer = sinon + .stub() + .returns(this.fakeContainer)) + }) + + return it('should not produce an error', function(done) { + return this.DockerRunner._destroyContainer( + this.containerId, + false, + err => { + expect(err).to.equal(null) + return done() + } + ) + }) + }) + + return describe('when container.destroy produces an error', function(done) { + beforeEach(function() { + this.fakeError = new Error('woops') + this.fakeError.statusCode = 500 + this.fakeContainer = { + remove: sinon.stub().callsArgWith(1, this.fakeError) + } + return (this.Docker.prototype.getContainer = sinon + .stub() + .returns(this.fakeContainer)) + }) + + return it('should produce an error', function(done) { + return this.DockerRunner._destroyContainer( + this.containerId, + false, + err => { + expect(err).to.not.equal(null) + expect(err).to.equal(this.fakeError) + return done() + } + ) + }) + }) + }) + + return describe('kill', function() { + beforeEach(function() { + this.containerId = 'some_id' + this.fakeContainer = { kill: sinon.stub().callsArgWith(0, null) } + return (this.Docker.prototype.getContainer = sinon + .stub() + .returns(this.fakeContainer)) + }) + + it('should get the container', function(done) { + return this.DockerRunner.kill(this.containerId, err => { + this.Docker.prototype.getContainer.callCount.should.equal(1) + this.Docker.prototype.getContainer + .calledWith(this.containerId) + .should.equal(true) + return done() + }) + }) + + it('should try to force-destroy the container', function(done) { + return this.DockerRunner.kill(this.containerId, err => { + this.fakeContainer.kill.callCount.should.equal(1) + return done() + }) + }) + + it('should not produce an error', function(done) { + return this.DockerRunner.kill(this.containerId, err => { + expect(err).to.equal(undefined) + return done() + }) + }) + + describe('when the container is not actually running', function() { + beforeEach(function() { + this.fakeError = new Error('woops') + this.fakeError.statusCode = 500 + this.fakeError.message = + 'Cannot kill container <whatever> is not running' + this.fakeContainer = { + kill: sinon.stub().callsArgWith(0, this.fakeError) + } + return (this.Docker.prototype.getContainer = sinon + .stub() + .returns(this.fakeContainer)) + }) + + return it('should not produce an error', function(done) { + return this.DockerRunner.kill(this.containerId, err => { + expect(err).to.equal(undefined) + return done() + }) + }) + }) + + return describe('when container.kill produces a legitimate error', function(done) { + beforeEach(function() { + this.fakeError = new Error('woops') + this.fakeError.statusCode = 500 + this.fakeError.message = 'Totally legitimate reason to throw an error' + this.fakeContainer = { + kill: sinon.stub().callsArgWith(0, this.fakeError) + } + return (this.Docker.prototype.getContainer = sinon + .stub() + .returns(this.fakeContainer)) + }) + + return it('should produce an error', function(done) { + return this.DockerRunner.kill(this.containerId, err => { + expect(err).to.not.equal(undefined) + expect(err).to.equal(this.fakeError) + return done() + }) + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/DraftModeManagerTests.js b/services/clsi/test/unit/js/DraftModeManagerTests.js index f270873a74..2c30b404bf 100644 --- a/services/clsi/test/unit/js/DraftModeManagerTests.js +++ b/services/clsi/test/unit/js/DraftModeManagerTests.js @@ -8,75 +8,79 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const SandboxedModule = require('sandboxed-module'); -const sinon = require('sinon'); -require('chai').should(); -const modulePath = require('path').join(__dirname, '../../../app/js/DraftModeManager'); +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +require('chai').should() +const modulePath = require('path').join( + __dirname, + '../../../app/js/DraftModeManager' +) describe('DraftModeManager', function() { - beforeEach(function() { - return this.DraftModeManager = SandboxedModule.require(modulePath, { requires: { - "fs": (this.fs = {}), - "logger-sharelatex": (this.logger = {log() {}}) - } - });}); - - describe("_injectDraftOption", function() { - it("should add draft option into documentclass with existing options", function() { - return this.DraftModeManager - ._injectDraftOption(`\ -\\documentclass[a4paper,foo=bar]{article}\ -`) - .should.equal(`\ -\\documentclass[draft,a4paper,foo=bar]{article}\ -`); - }); + beforeEach(function() { + return (this.DraftModeManager = SandboxedModule.require(modulePath, { + requires: { + fs: (this.fs = {}), + 'logger-sharelatex': (this.logger = { log() {} }) + } + })) + }) - return it("should add draft option into documentclass with no options", function() { - return this.DraftModeManager - ._injectDraftOption(`\ -\\documentclass{article}\ + describe('_injectDraftOption', function() { + it('should add draft option into documentclass with existing options', function() { + return this.DraftModeManager._injectDraftOption(`\ +\\documentclass[a4paper,foo=bar]{article}\ +`).should.equal(`\ +\\documentclass[draft,a4paper,foo=bar]{article}\ `) - .should.equal(`\ + }) + + return it('should add draft option into documentclass with no options', function() { + return this.DraftModeManager._injectDraftOption(`\ +\\documentclass{article}\ +`).should.equal(`\ \\documentclass[draft]{article}\ -`); - }); - }); - - return describe("injectDraftMode", function() { - beforeEach(function() { - this.filename = "/mock/filename.tex"; - this.callback = sinon.stub(); - const content = `\ +`) + }) + }) + + return describe('injectDraftMode', function() { + beforeEach(function() { + this.filename = '/mock/filename.tex' + this.callback = sinon.stub() + const content = `\ \\documentclass{article} \\begin{document} Hello world \\end{document}\ -`; - this.fs.readFile = sinon.stub().callsArgWith(2, null, content); - this.fs.writeFile = sinon.stub().callsArg(2); - return this.DraftModeManager.injectDraftMode(this.filename, this.callback); - }); - - it("should read the file", function() { - return this.fs.readFile - .calledWith(this.filename, "utf8") - .should.equal(true); - }); - - it("should write the modified file", function() { - return this.fs.writeFile - .calledWith(this.filename, `\ +` + this.fs.readFile = sinon.stub().callsArgWith(2, null, content) + this.fs.writeFile = sinon.stub().callsArg(2) + return this.DraftModeManager.injectDraftMode(this.filename, this.callback) + }) + + it('should read the file', function() { + return this.fs.readFile + .calledWith(this.filename, 'utf8') + .should.equal(true) + }) + + it('should write the modified file', function() { + return this.fs.writeFile + .calledWith( + this.filename, + `\ \\documentclass[draft]{article} \\begin{document} Hello world \\end{document}\ -`) - .should.equal(true); - }); - - return it("should call the callback", function() { - return this.callback.called.should.equal(true); - }); - }); -}); +` + ) + .should.equal(true) + }) + + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) +}) diff --git a/services/clsi/test/unit/js/LatexRunnerTests.js b/services/clsi/test/unit/js/LatexRunnerTests.js index 7fe8bc8164..b468b83183 100644 --- a/services/clsi/test/unit/js/LatexRunnerTests.js +++ b/services/clsi/test/unit/js/LatexRunnerTests.js @@ -9,103 +9,129 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const SandboxedModule = require('sandboxed-module'); -const sinon = require('sinon'); -require('chai').should(); -const modulePath = require('path').join(__dirname, '../../../app/js/LatexRunner'); -const Path = require("path"); +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +require('chai').should() +const modulePath = require('path').join( + __dirname, + '../../../app/js/LatexRunner' +) +const Path = require('path') -describe("LatexRunner", function() { - beforeEach(function() { - let Timer; - this.LatexRunner = SandboxedModule.require(modulePath, { requires: { - "settings-sharelatex": (this.Settings = { - docker: { - socketPath: "/var/run/docker.sock" - } - }), - "logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub() }), - "./Metrics": { - Timer: (Timer = class Timer { - done() {} - }) - }, - "./CommandRunner": (this.CommandRunner = {}) - } - }); +describe('LatexRunner', function() { + beforeEach(function() { + let Timer + this.LatexRunner = SandboxedModule.require(modulePath, { + requires: { + 'settings-sharelatex': (this.Settings = { + docker: { + socketPath: '/var/run/docker.sock' + } + }), + 'logger-sharelatex': (this.logger = { + log: sinon.stub(), + error: sinon.stub() + }), + './Metrics': { + Timer: (Timer = class Timer { + done() {} + }) + }, + './CommandRunner': (this.CommandRunner = {}) + } + }) - this.directory = "/local/compile/directory"; - this.mainFile = "main-file.tex"; - this.compiler = "pdflatex"; - this.image = "example.com/image"; - this.callback = sinon.stub(); - this.project_id = "project-id-123"; - return this.env = {'foo': '123'};}); + this.directory = '/local/compile/directory' + this.mainFile = 'main-file.tex' + this.compiler = 'pdflatex' + this.image = 'example.com/image' + this.callback = sinon.stub() + this.project_id = 'project-id-123' + return (this.env = { foo: '123' }) + }) - return describe("runLatex", function() { - beforeEach(function() { - return this.CommandRunner.run = sinon.stub().callsArg(6); - }); + return describe('runLatex', function() { + beforeEach(function() { + return (this.CommandRunner.run = sinon.stub().callsArg(6)) + }) - describe("normally", function() { - beforeEach(function() { - return this.LatexRunner.runLatex(this.project_id, { - directory: this.directory, - mainFile: this.mainFile, - compiler: this.compiler, - timeout: (this.timeout = 42000), - image: this.image, - environment: this.env - }, - this.callback); - }); + describe('normally', function() { + beforeEach(function() { + return this.LatexRunner.runLatex( + this.project_id, + { + directory: this.directory, + mainFile: this.mainFile, + compiler: this.compiler, + timeout: (this.timeout = 42000), + image: this.image, + environment: this.env + }, + this.callback + ) + }) - return it("should run the latex command", function() { - return this.CommandRunner.run - .calledWith(this.project_id, sinon.match.any, this.directory, this.image, this.timeout, this.env) - .should.equal(true); - }); - }); + return it('should run the latex command', function() { + return this.CommandRunner.run + .calledWith( + this.project_id, + sinon.match.any, + this.directory, + this.image, + this.timeout, + this.env + ) + .should.equal(true) + }) + }) - describe("with an .Rtex main file", function() { - beforeEach(function() { - return this.LatexRunner.runLatex(this.project_id, { - directory: this.directory, - mainFile: "main-file.Rtex", - compiler: this.compiler, - image: this.image, - timeout: (this.timeout = 42000) - }, - this.callback); - }); + describe('with an .Rtex main file', function() { + beforeEach(function() { + return this.LatexRunner.runLatex( + this.project_id, + { + directory: this.directory, + mainFile: 'main-file.Rtex', + compiler: this.compiler, + image: this.image, + timeout: (this.timeout = 42000) + }, + this.callback + ) + }) - return it("should run the latex command on the equivalent .tex file", function() { - const command = this.CommandRunner.run.args[0][1]; - const mainFile = command.slice(-1)[0]; - return mainFile.should.equal("$COMPILE_DIR/main-file.tex"); - }); - }); + return it('should run the latex command on the equivalent .tex file', function() { + const command = this.CommandRunner.run.args[0][1] + const mainFile = command.slice(-1)[0] + return mainFile.should.equal('$COMPILE_DIR/main-file.tex') + }) + }) - return describe("with a flags option", function() { - beforeEach(function() { - return this.LatexRunner.runLatex(this.project_id, { - directory: this.directory, - mainFile: this.mainFile, - compiler: this.compiler, - image: this.image, - timeout: (this.timeout = 42000), - flags: ["-file-line-error", "-halt-on-error"] - }, - this.callback); - }); + return describe('with a flags option', function() { + beforeEach(function() { + return this.LatexRunner.runLatex( + this.project_id, + { + directory: this.directory, + mainFile: this.mainFile, + compiler: this.compiler, + image: this.image, + timeout: (this.timeout = 42000), + flags: ['-file-line-error', '-halt-on-error'] + }, + this.callback + ) + }) - return it("should include the flags in the command", function() { - const command = this.CommandRunner.run.args[0][1]; - const flags = command.filter(arg => (arg === "-file-line-error") || (arg === "-halt-on-error")); - flags.length.should.equal(2); - flags[0].should.equal("-file-line-error"); - return flags[1].should.equal("-halt-on-error"); - }); - }); - }); -}); + return it('should include the flags in the command', function() { + const command = this.CommandRunner.run.args[0][1] + const flags = command.filter( + arg => arg === '-file-line-error' || arg === '-halt-on-error' + ) + flags.length.should.equal(2) + flags[0].should.equal('-file-line-error') + return flags[1].should.equal('-halt-on-error') + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/LockManagerTests.js b/services/clsi/test/unit/js/LockManagerTests.js index 6d1b15629a..ea6c3419f9 100644 --- a/services/clsi/test/unit/js/LockManagerTests.js +++ b/services/clsi/test/unit/js/LockManagerTests.js @@ -9,75 +9,85 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const SandboxedModule = require('sandboxed-module'); -const sinon = require('sinon'); -require('chai').should(); -const modulePath = require('path').join(__dirname, '../../../app/js/LockManager'); -const Path = require("path"); -const Errors = require("../../../app/js/Errors"); +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +require('chai').should() +const modulePath = require('path').join( + __dirname, + '../../../app/js/LockManager' +) +const Path = require('path') +const Errors = require('../../../app/js/Errors') -describe("DockerLockManager", function() { - beforeEach(function() { - this.LockManager = SandboxedModule.require(modulePath, { requires: { - "settings-sharelatex": {}, - "logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub(), err() {} }), - "fs": { - lstat:sinon.stub().callsArgWith(1), - readdir: sinon.stub().callsArgWith(1) - }, - "lockfile": (this.Lockfile = {}) - } - }); - return this.lockFile = "/local/compile/directory/.project-lock"; - }); +describe('DockerLockManager', function() { + beforeEach(function() { + this.LockManager = SandboxedModule.require(modulePath, { + requires: { + 'settings-sharelatex': {}, + 'logger-sharelatex': (this.logger = { + log: sinon.stub(), + error: sinon.stub(), + err() {} + }), + fs: { + lstat: sinon.stub().callsArgWith(1), + readdir: sinon.stub().callsArgWith(1) + }, + lockfile: (this.Lockfile = {}) + } + }) + return (this.lockFile = '/local/compile/directory/.project-lock') + }) - return describe("runWithLock", function() { - beforeEach(function() { - this.runner = sinon.stub().callsArgWith(0, null, "foo", "bar"); - return this.callback = sinon.stub(); - }); + return describe('runWithLock', function() { + beforeEach(function() { + this.runner = sinon.stub().callsArgWith(0, null, 'foo', 'bar') + return (this.callback = sinon.stub()) + }) - describe("normally", function() { - beforeEach(function() { - this.Lockfile.lock = sinon.stub().callsArgWith(2, null); - this.Lockfile.unlock = sinon.stub().callsArgWith(1, null); - return this.LockManager.runWithLock(this.lockFile, this.runner, this.callback); - }); + describe('normally', function() { + beforeEach(function() { + this.Lockfile.lock = sinon.stub().callsArgWith(2, null) + this.Lockfile.unlock = sinon.stub().callsArgWith(1, null) + return this.LockManager.runWithLock( + this.lockFile, + this.runner, + this.callback + ) + }) - it("should run the compile", function() { - return this.runner - .calledWith() - .should.equal(true); - }); + it('should run the compile', function() { + return this.runner.calledWith().should.equal(true) + }) - return it("should call the callback with the response from the compile", function() { - return this.callback - .calledWithExactly(null, "foo", "bar") - .should.equal(true); - }); - }); + return it('should call the callback with the response from the compile', function() { + return this.callback + .calledWithExactly(null, 'foo', 'bar') + .should.equal(true) + }) + }) - return describe("when the project is locked", function() { - beforeEach(function() { - this.error = new Error(); - this.error.code = "EEXIST"; - this.Lockfile.lock = sinon.stub().callsArgWith(2,this.error); - this.Lockfile.unlock = sinon.stub().callsArgWith(1, null); - return this.LockManager.runWithLock(this.lockFile, this.runner, this.callback); - }); + return describe('when the project is locked', function() { + beforeEach(function() { + this.error = new Error() + this.error.code = 'EEXIST' + this.Lockfile.lock = sinon.stub().callsArgWith(2, this.error) + this.Lockfile.unlock = sinon.stub().callsArgWith(1, null) + return this.LockManager.runWithLock( + this.lockFile, + this.runner, + this.callback + ) + }) - it("should not run the compile", function() { - return this.runner - .called - .should.equal(false); - }); + it('should not run the compile', function() { + return this.runner.called.should.equal(false) + }) - return it("should return an error", function() { - const error = new Errors.AlreadyCompilingError(); - return this.callback - .calledWithExactly(error) - .should.equal(true); - }); - }); - }); -}); + return it('should return an error', function() { + const error = new Errors.AlreadyCompilingError() + return this.callback.calledWithExactly(error).should.equal(true) + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/OutputFileFinderTests.js b/services/clsi/test/unit/js/OutputFileFinderTests.js index 5c956adb1d..e5f990466e 100644 --- a/services/clsi/test/unit/js/OutputFileFinderTests.js +++ b/services/clsi/test/unit/js/OutputFileFinderTests.js @@ -10,90 +10,96 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const SandboxedModule = require('sandboxed-module'); -const sinon = require('sinon'); -require('chai').should(); -const modulePath = require('path').join(__dirname, '../../../app/js/OutputFileFinder'); -const path = require("path"); -const { expect } = require("chai"); -const { EventEmitter } = require("events"); +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +require('chai').should() +const modulePath = require('path').join( + __dirname, + '../../../app/js/OutputFileFinder' +) +const path = require('path') +const { expect } = require('chai') +const { EventEmitter } = require('events') -describe("OutputFileFinder", function() { - beforeEach(function() { - this.OutputFileFinder = SandboxedModule.require(modulePath, { requires: { - "fs": (this.fs = {}), - "child_process": { spawn: (this.spawn = sinon.stub()) - }, - "logger-sharelatex": { log: sinon.stub(), warn: sinon.stub() } - } - }); - this.directory = "/test/dir"; - return this.callback = sinon.stub(); - }); +describe('OutputFileFinder', function() { + beforeEach(function() { + this.OutputFileFinder = SandboxedModule.require(modulePath, { + requires: { + fs: (this.fs = {}), + child_process: { spawn: (this.spawn = sinon.stub()) }, + 'logger-sharelatex': { log: sinon.stub(), warn: sinon.stub() } + } + }) + this.directory = '/test/dir' + return (this.callback = sinon.stub()) + }) - describe("findOutputFiles", function() { - beforeEach(function() { - this.resource_path = "resource/path.tex"; - this.output_paths = ["output.pdf", "extra/file.tex"]; - this.all_paths = this.output_paths.concat([this.resource_path]); - this.resources = [ - {path: (this.resource_path = "resource/path.tex")} - ]; - this.OutputFileFinder._getAllFiles = sinon.stub().callsArgWith(1, null, this.all_paths); - return this.OutputFileFinder.findOutputFiles(this.resources, this.directory, (error, outputFiles) => { - this.outputFiles = outputFiles; - - }); - }); + describe('findOutputFiles', function() { + beforeEach(function() { + this.resource_path = 'resource/path.tex' + this.output_paths = ['output.pdf', 'extra/file.tex'] + this.all_paths = this.output_paths.concat([this.resource_path]) + this.resources = [{ path: (this.resource_path = 'resource/path.tex') }] + this.OutputFileFinder._getAllFiles = sinon + .stub() + .callsArgWith(1, null, this.all_paths) + return this.OutputFileFinder.findOutputFiles( + this.resources, + this.directory, + (error, outputFiles) => { + this.outputFiles = outputFiles + } + ) + }) - return it("should only return the output files, not directories or resource paths", function() { - return expect(this.outputFiles).to.deep.equal([{ - path: "output.pdf", - type: "pdf" - }, { - path: "extra/file.tex", - type: "tex" - }]); - }); -}); - - return describe("_getAllFiles", function() { - beforeEach(function() { - this.proc = new EventEmitter(); - this.proc.stdout = new EventEmitter(); - this.spawn.returns(this.proc); - this.directory = "/base/dir"; - return this.OutputFileFinder._getAllFiles(this.directory, this.callback); - }); - - describe("successfully", function() { - beforeEach(function() { - this.proc.stdout.emit( - "data", - ["/base/dir/main.tex", "/base/dir/chapters/chapter1.tex"].join("\n") + "\n" - ); - return this.proc.emit("close", 0); - }); - - return it("should call the callback with the relative file paths", function() { - return this.callback.calledWith( - null, - ["main.tex", "chapters/chapter1.tex"] - ).should.equal(true); - }); - }); + return it('should only return the output files, not directories or resource paths', function() { + return expect(this.outputFiles).to.deep.equal([ + { + path: 'output.pdf', + type: 'pdf' + }, + { + path: 'extra/file.tex', + type: 'tex' + } + ]) + }) + }) - return describe("when the directory doesn't exist", function() { - beforeEach(function() { - return this.proc.emit("close", 1); - }); - - return it("should call the callback with a blank array", function() { - return this.callback.calledWith( - null, - [] - ).should.equal(true); - }); - }); - }); -}); + return describe('_getAllFiles', function() { + beforeEach(function() { + this.proc = new EventEmitter() + this.proc.stdout = new EventEmitter() + this.spawn.returns(this.proc) + this.directory = '/base/dir' + return this.OutputFileFinder._getAllFiles(this.directory, this.callback) + }) + + describe('successfully', function() { + beforeEach(function() { + this.proc.stdout.emit( + 'data', + ['/base/dir/main.tex', '/base/dir/chapters/chapter1.tex'].join('\n') + + '\n' + ) + return this.proc.emit('close', 0) + }) + + return it('should call the callback with the relative file paths', function() { + return this.callback + .calledWith(null, ['main.tex', 'chapters/chapter1.tex']) + .should.equal(true) + }) + }) + + return describe("when the directory doesn't exist", function() { + beforeEach(function() { + return this.proc.emit('close', 1) + }) + + return it('should call the callback with a blank array', function() { + return this.callback.calledWith(null, []).should.equal(true) + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/OutputFileOptimiserTests.js b/services/clsi/test/unit/js/OutputFileOptimiserTests.js index 13b8d60c0a..4546f08431 100644 --- a/services/clsi/test/unit/js/OutputFileOptimiserTests.js +++ b/services/clsi/test/unit/js/OutputFileOptimiserTests.js @@ -10,139 +10,187 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const SandboxedModule = require('sandboxed-module'); -const sinon = require('sinon'); -require('chai').should(); -const modulePath = require('path').join(__dirname, '../../../app/js/OutputFileOptimiser'); -const path = require("path"); -const { expect } = require("chai"); -const { EventEmitter } = require("events"); +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +require('chai').should() +const modulePath = require('path').join( + __dirname, + '../../../app/js/OutputFileOptimiser' +) +const path = require('path') +const { expect } = require('chai') +const { EventEmitter } = require('events') -describe("OutputFileOptimiser", function() { - beforeEach(function() { - this.OutputFileOptimiser = SandboxedModule.require(modulePath, { requires: { - "fs": (this.fs = {}), - "path": (this.Path = {}), - "child_process": { spawn: (this.spawn = sinon.stub()) - }, - "logger-sharelatex": { log: sinon.stub(), warn: sinon.stub() }, - "./Metrics" : {} - } - }); - this.directory = "/test/dir"; - return this.callback = sinon.stub(); - }); +describe('OutputFileOptimiser', function() { + beforeEach(function() { + this.OutputFileOptimiser = SandboxedModule.require(modulePath, { + requires: { + fs: (this.fs = {}), + path: (this.Path = {}), + child_process: { spawn: (this.spawn = sinon.stub()) }, + 'logger-sharelatex': { log: sinon.stub(), warn: sinon.stub() }, + './Metrics': {} + } + }) + this.directory = '/test/dir' + return (this.callback = sinon.stub()) + }) - describe("optimiseFile", function() { - beforeEach(function() { - this.src = "./output.pdf"; - return this.dst = "./output.pdf"; - }); + describe('optimiseFile', function() { + beforeEach(function() { + this.src = './output.pdf' + return (this.dst = './output.pdf') + }) - describe("when the file is not a pdf file", function() { - beforeEach(function(done){ - this.src = "./output.log"; - this.OutputFileOptimiser.checkIfPDFIsOptimised = sinon.stub().callsArgWith(1, null, false); - this.OutputFileOptimiser.optimisePDF = sinon.stub().callsArgWith(2, null); - return this.OutputFileOptimiser.optimiseFile(this.src, this.dst, done); - }); + describe('when the file is not a pdf file', function() { + beforeEach(function(done) { + this.src = './output.log' + this.OutputFileOptimiser.checkIfPDFIsOptimised = sinon + .stub() + .callsArgWith(1, null, false) + this.OutputFileOptimiser.optimisePDF = sinon + .stub() + .callsArgWith(2, null) + return this.OutputFileOptimiser.optimiseFile(this.src, this.dst, done) + }) - it("should not check if the file is optimised", function() { - return this.OutputFileOptimiser.checkIfPDFIsOptimised.calledWith(this.src).should.equal(false); - }); + it('should not check if the file is optimised', function() { + return this.OutputFileOptimiser.checkIfPDFIsOptimised + .calledWith(this.src) + .should.equal(false) + }) - return it("should not optimise the file", function() { - return this.OutputFileOptimiser.optimisePDF.calledWith(this.src, this.dst).should.equal(false); - }); - }); + return it('should not optimise the file', function() { + return this.OutputFileOptimiser.optimisePDF + .calledWith(this.src, this.dst) + .should.equal(false) + }) + }) - describe("when the pdf file is not optimised", function() { - beforeEach(function(done) { - this.OutputFileOptimiser.checkIfPDFIsOptimised = sinon.stub().callsArgWith(1, null, false); - this.OutputFileOptimiser.optimisePDF = sinon.stub().callsArgWith(2, null); - return this.OutputFileOptimiser.optimiseFile(this.src, this.dst, done); - }); + describe('when the pdf file is not optimised', function() { + beforeEach(function(done) { + this.OutputFileOptimiser.checkIfPDFIsOptimised = sinon + .stub() + .callsArgWith(1, null, false) + this.OutputFileOptimiser.optimisePDF = sinon + .stub() + .callsArgWith(2, null) + return this.OutputFileOptimiser.optimiseFile(this.src, this.dst, done) + }) - it("should check if the pdf is optimised", function() { - return this.OutputFileOptimiser.checkIfPDFIsOptimised.calledWith(this.src).should.equal(true); - }); + it('should check if the pdf is optimised', function() { + return this.OutputFileOptimiser.checkIfPDFIsOptimised + .calledWith(this.src) + .should.equal(true) + }) - return it("should optimise the pdf", function() { - return this.OutputFileOptimiser.optimisePDF.calledWith(this.src, this.dst).should.equal(true); - }); - }); + return it('should optimise the pdf', function() { + return this.OutputFileOptimiser.optimisePDF + .calledWith(this.src, this.dst) + .should.equal(true) + }) + }) - return describe("when the pdf file is optimised", function() { - beforeEach(function(done) { - this.OutputFileOptimiser.checkIfPDFIsOptimised = sinon.stub().callsArgWith(1, null, true); - this.OutputFileOptimiser.optimisePDF = sinon.stub().callsArgWith(2, null); - return this.OutputFileOptimiser.optimiseFile(this.src, this.dst, done); - }); + return describe('when the pdf file is optimised', function() { + beforeEach(function(done) { + this.OutputFileOptimiser.checkIfPDFIsOptimised = sinon + .stub() + .callsArgWith(1, null, true) + this.OutputFileOptimiser.optimisePDF = sinon + .stub() + .callsArgWith(2, null) + return this.OutputFileOptimiser.optimiseFile(this.src, this.dst, done) + }) - it("should check if the pdf is optimised", function() { - return this.OutputFileOptimiser.checkIfPDFIsOptimised.calledWith(this.src).should.equal(true); - }); + it('should check if the pdf is optimised', function() { + return this.OutputFileOptimiser.checkIfPDFIsOptimised + .calledWith(this.src) + .should.equal(true) + }) - return it("should not optimise the pdf", function() { - return this.OutputFileOptimiser.optimisePDF.calledWith(this.src, this.dst).should.equal(false); - }); - }); - }); + return it('should not optimise the pdf', function() { + return this.OutputFileOptimiser.optimisePDF + .calledWith(this.src, this.dst) + .should.equal(false) + }) + }) + }) - return describe("checkIfPDFISOptimised", function() { - beforeEach(function() { - this.callback = sinon.stub(); - this.fd = 1234; - this.fs.open = sinon.stub().yields(null, this.fd); - this.fs.read = sinon.stub().withArgs(this.fd).yields(null, 100, new Buffer("hello /Linearized 1")); - this.fs.close = sinon.stub().withArgs(this.fd).yields(null); - return this.OutputFileOptimiser.checkIfPDFIsOptimised(this.src, this.callback); - }); + return describe('checkIfPDFISOptimised', function() { + beforeEach(function() { + this.callback = sinon.stub() + this.fd = 1234 + this.fs.open = sinon.stub().yields(null, this.fd) + this.fs.read = sinon + .stub() + .withArgs(this.fd) + .yields(null, 100, new Buffer('hello /Linearized 1')) + this.fs.close = sinon + .stub() + .withArgs(this.fd) + .yields(null) + return this.OutputFileOptimiser.checkIfPDFIsOptimised( + this.src, + this.callback + ) + }) - describe("for a linearised file", function() { - beforeEach(function() { - this.fs.read = sinon.stub().withArgs(this.fd).yields(null, 100, new Buffer("hello /Linearized 1")); - return this.OutputFileOptimiser.checkIfPDFIsOptimised(this.src, this.callback); - }); + describe('for a linearised file', function() { + beforeEach(function() { + this.fs.read = sinon + .stub() + .withArgs(this.fd) + .yields(null, 100, new Buffer('hello /Linearized 1')) + return this.OutputFileOptimiser.checkIfPDFIsOptimised( + this.src, + this.callback + ) + }) - it("should open the file", function() { - return this.fs.open.calledWith(this.src, "r").should.equal(true); - }); + it('should open the file', function() { + return this.fs.open.calledWith(this.src, 'r').should.equal(true) + }) - it("should read the header", function() { - return this.fs.read.calledWith(this.fd).should.equal(true); - }); + it('should read the header', function() { + return this.fs.read.calledWith(this.fd).should.equal(true) + }) - it("should close the file", function() { - return this.fs.close.calledWith(this.fd).should.equal(true); - }); + it('should close the file', function() { + return this.fs.close.calledWith(this.fd).should.equal(true) + }) - return it("should call the callback with a true result", function() { - return this.callback.calledWith(null, true).should.equal(true); - }); - }); + return it('should call the callback with a true result', function() { + return this.callback.calledWith(null, true).should.equal(true) + }) + }) - return describe("for an unlinearised file", function() { - beforeEach(function() { - this.fs.read = sinon.stub().withArgs(this.fd).yields(null, 100, new Buffer("hello not linearized 1")); - return this.OutputFileOptimiser.checkIfPDFIsOptimised(this.src, this.callback); - }); + return describe('for an unlinearised file', function() { + beforeEach(function() { + this.fs.read = sinon + .stub() + .withArgs(this.fd) + .yields(null, 100, new Buffer('hello not linearized 1')) + return this.OutputFileOptimiser.checkIfPDFIsOptimised( + this.src, + this.callback + ) + }) - it("should open the file", function() { - return this.fs.open.calledWith(this.src, "r").should.equal(true); - }); + it('should open the file', function() { + return this.fs.open.calledWith(this.src, 'r').should.equal(true) + }) - it("should read the header", function() { - return this.fs.read.calledWith(this.fd).should.equal(true); - }); + it('should read the header', function() { + return this.fs.read.calledWith(this.fd).should.equal(true) + }) - it("should close the file", function() { - return this.fs.close.calledWith(this.fd).should.equal(true); - }); + it('should close the file', function() { + return this.fs.close.calledWith(this.fd).should.equal(true) + }) - return it("should call the callback with a false result", function() { - return this.callback.calledWith(null, false).should.equal(true); - }); - }); - }); -}); + return it('should call the callback with a false result', function() { + return this.callback.calledWith(null, false).should.equal(true) + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js b/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js index 5f77a80785..0d84fc2455 100644 --- a/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js +++ b/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js @@ -11,79 +11,90 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const SandboxedModule = require('sandboxed-module'); -const sinon = require('sinon'); -require('chai').should(); -const modulePath = require('path').join(__dirname, '../../../app/js/ProjectPersistenceManager'); -const tk = require("timekeeper"); +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +require('chai').should() +const modulePath = require('path').join( + __dirname, + '../../../app/js/ProjectPersistenceManager' +) +const tk = require('timekeeper') -describe("ProjectPersistenceManager", function() { - beforeEach(function() { - this.ProjectPersistenceManager = SandboxedModule.require(modulePath, { requires: { - "./UrlCache": (this.UrlCache = {}), - "./CompileManager": (this.CompileManager = {}), - "logger-sharelatex": (this.logger = { log: sinon.stub() }), - "./db": (this.db = {}) - } - }); - this.callback = sinon.stub(); - this.project_id = "project-id-123"; - return this.user_id = "1234"; - }); +describe('ProjectPersistenceManager', function() { + beforeEach(function() { + this.ProjectPersistenceManager = SandboxedModule.require(modulePath, { + requires: { + './UrlCache': (this.UrlCache = {}), + './CompileManager': (this.CompileManager = {}), + 'logger-sharelatex': (this.logger = { log: sinon.stub() }), + './db': (this.db = {}) + } + }) + this.callback = sinon.stub() + this.project_id = 'project-id-123' + return (this.user_id = '1234') + }) - describe("clearExpiredProjects", function() { - beforeEach(function() { - this.project_ids = [ - "project-id-1", - "project-id-2" - ]; - this.ProjectPersistenceManager._findExpiredProjectIds = sinon.stub().callsArgWith(0, null, this.project_ids); - this.ProjectPersistenceManager.clearProjectFromCache = sinon.stub().callsArg(1); - this.CompileManager.clearExpiredProjects = sinon.stub().callsArg(1); - return this.ProjectPersistenceManager.clearExpiredProjects(this.callback); - }); + describe('clearExpiredProjects', function() { + beforeEach(function() { + this.project_ids = ['project-id-1', 'project-id-2'] + this.ProjectPersistenceManager._findExpiredProjectIds = sinon + .stub() + .callsArgWith(0, null, this.project_ids) + this.ProjectPersistenceManager.clearProjectFromCache = sinon + .stub() + .callsArg(1) + this.CompileManager.clearExpiredProjects = sinon.stub().callsArg(1) + return this.ProjectPersistenceManager.clearExpiredProjects(this.callback) + }) - it("should clear each expired project", function() { - return Array.from(this.project_ids).map((project_id) => - this.ProjectPersistenceManager.clearProjectFromCache - .calledWith(project_id) - .should.equal(true)); - }); + it('should clear each expired project', function() { + return Array.from(this.project_ids).map(project_id => + this.ProjectPersistenceManager.clearProjectFromCache + .calledWith(project_id) + .should.equal(true) + ) + }) - return it("should call the callback", function() { - return this.callback.called.should.equal(true); - }); - }); + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) - return describe("clearProject", function() { - beforeEach(function() { - this.ProjectPersistenceManager._clearProjectFromDatabase = sinon.stub().callsArg(1); - this.UrlCache.clearProject = sinon.stub().callsArg(1); - this.CompileManager.clearProject = sinon.stub().callsArg(2); - return this.ProjectPersistenceManager.clearProject(this.project_id, this.user_id, this.callback); - }); + return describe('clearProject', function() { + beforeEach(function() { + this.ProjectPersistenceManager._clearProjectFromDatabase = sinon + .stub() + .callsArg(1) + this.UrlCache.clearProject = sinon.stub().callsArg(1) + this.CompileManager.clearProject = sinon.stub().callsArg(2) + return this.ProjectPersistenceManager.clearProject( + this.project_id, + this.user_id, + this.callback + ) + }) - it("should clear the project from the database", function() { - return this.ProjectPersistenceManager._clearProjectFromDatabase - .calledWith(this.project_id) - .should.equal(true); - }); + it('should clear the project from the database', function() { + return this.ProjectPersistenceManager._clearProjectFromDatabase + .calledWith(this.project_id) + .should.equal(true) + }) - it("should clear all the cached Urls for the project", function() { - return this.UrlCache.clearProject - .calledWith(this.project_id) - .should.equal(true); - }); + it('should clear all the cached Urls for the project', function() { + return this.UrlCache.clearProject + .calledWith(this.project_id) + .should.equal(true) + }) - it("should clear the project compile folder", function() { - return this.CompileManager.clearProject - .calledWith(this.project_id, this.user_id) - .should.equal(true); - }); + it('should clear the project compile folder', function() { + return this.CompileManager.clearProject + .calledWith(this.project_id, this.user_id) + .should.equal(true) + }) - return it("should call the callback", function() { - return this.callback.called.should.equal(true); - }); - }); -}); - + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) +}) diff --git a/services/clsi/test/unit/js/RequestParserTests.js b/services/clsi/test/unit/js/RequestParserTests.js index 725988f623..e2d8b02610 100644 --- a/services/clsi/test/unit/js/RequestParserTests.js +++ b/services/clsi/test/unit/js/RequestParserTests.js @@ -9,378 +9,412 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const SandboxedModule = require('sandboxed-module'); -const sinon = require('sinon'); -require('chai').should(); -const { expect } = require('chai'); -const modulePath = require('path').join(__dirname, '../../../app/js/RequestParser'); -const tk = require("timekeeper"); +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +require('chai').should() +const { expect } = require('chai') +const modulePath = require('path').join( + __dirname, + '../../../app/js/RequestParser' +) +const tk = require('timekeeper') -describe("RequestParser", function() { - beforeEach(function() { - tk.freeze(); - this.callback = sinon.stub(); - this.validResource = { - path: "main.tex", - date: "12:00 01/02/03", - content: "Hello world" - }; - this.validRequest = { - compile: { - token: "token-123", - options: { - imageName: "basicImageName/here:2017-1", - compiler: "pdflatex", - timeout: 42 - }, - resources: [] - } - }; - return this.RequestParser = SandboxedModule.require(modulePath, { requires: { - "settings-sharelatex": (this.settings = {}) - } - });}); +describe('RequestParser', function() { + beforeEach(function() { + tk.freeze() + this.callback = sinon.stub() + this.validResource = { + path: 'main.tex', + date: '12:00 01/02/03', + content: 'Hello world' + } + this.validRequest = { + compile: { + token: 'token-123', + options: { + imageName: 'basicImageName/here:2017-1', + compiler: 'pdflatex', + timeout: 42 + }, + resources: [] + } + } + return (this.RequestParser = SandboxedModule.require(modulePath, { + requires: { + 'settings-sharelatex': (this.settings = {}) + } + })) + }) - afterEach(function() { return tk.reset(); }); + afterEach(function() { + return tk.reset() + }) - describe("without a top level object", function() { - beforeEach(function() { - return this.RequestParser.parse([], this.callback); - }); + describe('without a top level object', function() { + beforeEach(function() { + return this.RequestParser.parse([], this.callback) + }) - return it("should return an error", function() { - return this.callback.calledWith("top level object should have a compile attribute") - .should.equal(true); - }); - }); + return it('should return an error', function() { + return this.callback + .calledWith('top level object should have a compile attribute') + .should.equal(true) + }) + }) - describe("without a compile attribute", function() { - beforeEach(function() { - return this.RequestParser.parse({}, this.callback); - }); + describe('without a compile attribute', function() { + beforeEach(function() { + return this.RequestParser.parse({}, this.callback) + }) - return it("should return an error", function() { - return this.callback.calledWith("top level object should have a compile attribute") - .should.equal(true); - }); - }); + return it('should return an error', function() { + return this.callback + .calledWith('top level object should have a compile attribute') + .should.equal(true) + }) + }) - describe("without a valid compiler", function() { - beforeEach(function() { - this.validRequest.compile.options.compiler = "not-a-compiler"; - return this.RequestParser.parse(this.validRequest, this.callback); - }); + describe('without a valid compiler', function() { + beforeEach(function() { + this.validRequest.compile.options.compiler = 'not-a-compiler' + return this.RequestParser.parse(this.validRequest, this.callback) + }) - return it("should return an error", function() { - return this.callback.calledWith("compiler attribute should be one of: pdflatex, latex, xelatex, lualatex") - .should.equal(true); - }); - }); + return it('should return an error', function() { + return this.callback + .calledWith( + 'compiler attribute should be one of: pdflatex, latex, xelatex, lualatex' + ) + .should.equal(true) + }) + }) - describe("without a compiler specified", function() { - beforeEach(function() { - delete this.validRequest.compile.options.compiler; - return this.RequestParser.parse(this.validRequest, (error, data) => { - this.data = data; - - }); - }); + describe('without a compiler specified', function() { + beforeEach(function() { + delete this.validRequest.compile.options.compiler + return this.RequestParser.parse(this.validRequest, (error, data) => { + this.data = data + }) + }) - return it("should set the compiler to pdflatex by default", function() { - return this.data.compiler.should.equal("pdflatex"); - }); - }); + return it('should set the compiler to pdflatex by default', function() { + return this.data.compiler.should.equal('pdflatex') + }) + }) - describe("with imageName set", function() { - beforeEach(function() { - return this.RequestParser.parse(this.validRequest, (error, data) => { - this.data = data; - - }); - }); + describe('with imageName set', function() { + beforeEach(function() { + return this.RequestParser.parse(this.validRequest, (error, data) => { + this.data = data + }) + }) - return it("should set the imageName", function() { - return this.data.imageName.should.equal("basicImageName/here:2017-1"); - }); - }); + return it('should set the imageName', function() { + return this.data.imageName.should.equal('basicImageName/here:2017-1') + }) + }) - describe("with flags set", function() { - beforeEach(function() { - this.validRequest.compile.options.flags = ["-file-line-error"]; - return this.RequestParser.parse(this.validRequest, (error, data) => { - this.data = data; - - }); - }); + describe('with flags set', function() { + beforeEach(function() { + this.validRequest.compile.options.flags = ['-file-line-error'] + return this.RequestParser.parse(this.validRequest, (error, data) => { + this.data = data + }) + }) - return it("should set the flags attribute", function() { - return expect(this.data.flags).to.deep.equal(["-file-line-error"]); - }); -}); + return it('should set the flags attribute', function() { + return expect(this.data.flags).to.deep.equal(['-file-line-error']) + }) + }) - describe("with flags not specified", function() { - beforeEach(function() { - return this.RequestParser.parse(this.validRequest, (error, data) => { - this.data = data; - - }); - }); + describe('with flags not specified', function() { + beforeEach(function() { + return this.RequestParser.parse(this.validRequest, (error, data) => { + this.data = data + }) + }) - return it("it should have an empty flags list", function() { - return expect(this.data.flags).to.deep.equal([]); - }); -}); + return it('it should have an empty flags list', function() { + return expect(this.data.flags).to.deep.equal([]) + }) + }) - describe("without a timeout specified", function() { - beforeEach(function() { - delete this.validRequest.compile.options.timeout; - return this.RequestParser.parse(this.validRequest, (error, data) => { - this.data = data; - - }); - }); + describe('without a timeout specified', function() { + beforeEach(function() { + delete this.validRequest.compile.options.timeout + return this.RequestParser.parse(this.validRequest, (error, data) => { + this.data = data + }) + }) - return it("should set the timeout to MAX_TIMEOUT", function() { - return this.data.timeout.should.equal(this.RequestParser.MAX_TIMEOUT * 1000); - }); - }); + return it('should set the timeout to MAX_TIMEOUT', function() { + return this.data.timeout.should.equal( + this.RequestParser.MAX_TIMEOUT * 1000 + ) + }) + }) - describe("with a timeout larger than the maximum", function() { - beforeEach(function() { - this.validRequest.compile.options.timeout = this.RequestParser.MAX_TIMEOUT + 1; - return this.RequestParser.parse(this.validRequest, (error, data) => { - this.data = data; - - }); - }); + describe('with a timeout larger than the maximum', function() { + beforeEach(function() { + this.validRequest.compile.options.timeout = + this.RequestParser.MAX_TIMEOUT + 1 + return this.RequestParser.parse(this.validRequest, (error, data) => { + this.data = data + }) + }) - return it("should set the timeout to MAX_TIMEOUT", function() { - return this.data.timeout.should.equal(this.RequestParser.MAX_TIMEOUT * 1000); - }); - }); + return it('should set the timeout to MAX_TIMEOUT', function() { + return this.data.timeout.should.equal( + this.RequestParser.MAX_TIMEOUT * 1000 + ) + }) + }) - describe("with a timeout", function() { - beforeEach(function() { - return this.RequestParser.parse(this.validRequest, (error, data) => { - this.data = data; - - }); - }); + describe('with a timeout', function() { + beforeEach(function() { + return this.RequestParser.parse(this.validRequest, (error, data) => { + this.data = data + }) + }) - return it("should set the timeout (in milliseconds)", function() { - return this.data.timeout.should.equal(this.validRequest.compile.options.timeout * 1000); - }); - }); + return it('should set the timeout (in milliseconds)', function() { + return this.data.timeout.should.equal( + this.validRequest.compile.options.timeout * 1000 + ) + }) + }) - describe("with a resource without a path", function() { - beforeEach(function() { - delete this.validResource.path; - this.validRequest.compile.resources.push(this.validResource); - return this.RequestParser.parse(this.validRequest, this.callback); - }); + describe('with a resource without a path', function() { + beforeEach(function() { + delete this.validResource.path + this.validRequest.compile.resources.push(this.validResource) + return this.RequestParser.parse(this.validRequest, this.callback) + }) - return it("should return an error", function() { - return this.callback.calledWith("all resources should have a path attribute") - .should.equal(true); - }); - }); + return it('should return an error', function() { + return this.callback + .calledWith('all resources should have a path attribute') + .should.equal(true) + }) + }) - describe("with a resource with a path", function() { - beforeEach(function() { - this.validResource.path = (this.path = "test.tex"); - this.validRequest.compile.resources.push(this.validResource); - this.RequestParser.parse(this.validRequest, this.callback); - return this.data = this.callback.args[0][1];}); + describe('with a resource with a path', function() { + beforeEach(function() { + this.validResource.path = this.path = 'test.tex' + this.validRequest.compile.resources.push(this.validResource) + this.RequestParser.parse(this.validRequest, this.callback) + return (this.data = this.callback.args[0][1]) + }) - return it("should return the path in the parsed response", function() { - return this.data.resources[0].path.should.equal(this.path); - }); - }); + return it('should return the path in the parsed response', function() { + return this.data.resources[0].path.should.equal(this.path) + }) + }) - describe("with a resource with a malformed modified date", function() { - beforeEach(function() { - this.validResource.modified = "not-a-date"; - this.validRequest.compile.resources.push(this.validResource); - return this.RequestParser.parse(this.validRequest, this.callback); - }); + describe('with a resource with a malformed modified date', function() { + beforeEach(function() { + this.validResource.modified = 'not-a-date' + this.validRequest.compile.resources.push(this.validResource) + return this.RequestParser.parse(this.validRequest, this.callback) + }) - return it("should return an error", function() { - return this.callback - .calledWith( - "resource modified date could not be understood: "+ - this.validResource.modified - ) - .should.equal(true); - }); - }); + return it('should return an error', function() { + return this.callback + .calledWith( + 'resource modified date could not be understood: ' + + this.validResource.modified + ) + .should.equal(true) + }) + }) - describe("with a resource with a valid date", function() { - beforeEach(function() { - this.date = "12:00 01/02/03"; - this.validResource.modified = this.date; - this.validRequest.compile.resources.push(this.validResource); - this.RequestParser.parse(this.validRequest, this.callback); - return this.data = this.callback.args[0][1];}); + describe('with a resource with a valid date', function() { + beforeEach(function() { + this.date = '12:00 01/02/03' + this.validResource.modified = this.date + this.validRequest.compile.resources.push(this.validResource) + this.RequestParser.parse(this.validRequest, this.callback) + return (this.data = this.callback.args[0][1]) + }) - return it("should return the date as a Javascript Date object", function() { - (this.data.resources[0].modified instanceof Date).should.equal(true); - return this.data.resources[0].modified.getTime().should.equal(Date.parse(this.date)); - }); - }); + return it('should return the date as a Javascript Date object', function() { + ;(this.data.resources[0].modified instanceof Date).should.equal(true) + return this.data.resources[0].modified + .getTime() + .should.equal(Date.parse(this.date)) + }) + }) - describe("with a resource without either a content or URL attribute", function() { - beforeEach(function() { - delete this.validResource.url; - delete this.validResource.content; - this.validRequest.compile.resources.push(this.validResource); - return this.RequestParser.parse(this.validRequest, this.callback); - }); + describe('with a resource without either a content or URL attribute', function() { + beforeEach(function() { + delete this.validResource.url + delete this.validResource.content + this.validRequest.compile.resources.push(this.validResource) + return this.RequestParser.parse(this.validRequest, this.callback) + }) - return it("should return an error", function() { - return this.callback.calledWith("all resources should have either a url or content attribute") - .should.equal(true); - }); - }); + return it('should return an error', function() { + return this.callback + .calledWith( + 'all resources should have either a url or content attribute' + ) + .should.equal(true) + }) + }) - describe("with a resource where the content is not a string", function() { - beforeEach(function() { - this.validResource.content = []; - this.validRequest.compile.resources.push(this.validResource); - return this.RequestParser.parse((this.validRequest), this.callback); - }); + describe('with a resource where the content is not a string', function() { + beforeEach(function() { + this.validResource.content = [] + this.validRequest.compile.resources.push(this.validResource) + return this.RequestParser.parse(this.validRequest, this.callback) + }) - return it("should return an error", function() { - return this.callback.calledWith("content attribute should be a string") - .should.equal(true); - }); - }); + return it('should return an error', function() { + return this.callback + .calledWith('content attribute should be a string') + .should.equal(true) + }) + }) - describe("with a resource where the url is not a string", function() { - beforeEach(function() { - this.validResource.url = []; - this.validRequest.compile.resources.push(this.validResource); - return this.RequestParser.parse((this.validRequest), this.callback); - }); + describe('with a resource where the url is not a string', function() { + beforeEach(function() { + this.validResource.url = [] + this.validRequest.compile.resources.push(this.validResource) + return this.RequestParser.parse(this.validRequest, this.callback) + }) - return it("should return an error", function() { - return this.callback.calledWith("url attribute should be a string") - .should.equal(true); - }); - }); + return it('should return an error', function() { + return this.callback + .calledWith('url attribute should be a string') + .should.equal(true) + }) + }) - describe("with a resource with a url", function() { - beforeEach(function() { - this.validResource.url = (this.url = "www.example.com"); - this.validRequest.compile.resources.push(this.validResource); - this.RequestParser.parse((this.validRequest), this.callback); - return this.data = this.callback.args[0][1];}); + describe('with a resource with a url', function() { + beforeEach(function() { + this.validResource.url = this.url = 'www.example.com' + this.validRequest.compile.resources.push(this.validResource) + this.RequestParser.parse(this.validRequest, this.callback) + return (this.data = this.callback.args[0][1]) + }) - return it("should return the url in the parsed response", function() { - return this.data.resources[0].url.should.equal(this.url); - }); - }); + return it('should return the url in the parsed response', function() { + return this.data.resources[0].url.should.equal(this.url) + }) + }) - describe("with a resource with a content attribute", function() { - beforeEach(function() { - this.validResource.content = (this.content = "Hello world"); - this.validRequest.compile.resources.push(this.validResource); - this.RequestParser.parse((this.validRequest), this.callback); - return this.data = this.callback.args[0][1];}); + describe('with a resource with a content attribute', function() { + beforeEach(function() { + this.validResource.content = this.content = 'Hello world' + this.validRequest.compile.resources.push(this.validResource) + this.RequestParser.parse(this.validRequest, this.callback) + return (this.data = this.callback.args[0][1]) + }) - return it("should return the content in the parsed response", function() { - return this.data.resources[0].content.should.equal(this.content); - }); - }); + return it('should return the content in the parsed response', function() { + return this.data.resources[0].content.should.equal(this.content) + }) + }) - describe("without a root resource path", function() { - beforeEach(function() { - delete this.validRequest.compile.rootResourcePath; - this.RequestParser.parse((this.validRequest), this.callback); - return this.data = this.callback.args[0][1];}); + describe('without a root resource path', function() { + beforeEach(function() { + delete this.validRequest.compile.rootResourcePath + this.RequestParser.parse(this.validRequest, this.callback) + return (this.data = this.callback.args[0][1]) + }) - return it("should set the root resource path to 'main.tex' by default", function() { - return this.data.rootResourcePath.should.equal("main.tex"); - }); - }); + return it("should set the root resource path to 'main.tex' by default", function() { + return this.data.rootResourcePath.should.equal('main.tex') + }) + }) - describe("with a root resource path", function() { - beforeEach(function() { - this.validRequest.compile.rootResourcePath = (this.path = "test.tex"); - this.RequestParser.parse((this.validRequest), this.callback); - return this.data = this.callback.args[0][1];}); + describe('with a root resource path', function() { + beforeEach(function() { + this.validRequest.compile.rootResourcePath = this.path = 'test.tex' + this.RequestParser.parse(this.validRequest, this.callback) + return (this.data = this.callback.args[0][1]) + }) - return it("should return the root resource path in the parsed response", function() { - return this.data.rootResourcePath.should.equal(this.path); - }); - }); + return it('should return the root resource path in the parsed response', function() { + return this.data.rootResourcePath.should.equal(this.path) + }) + }) - describe("with a root resource path that is not a string", function() { - beforeEach(function() { - this.validRequest.compile.rootResourcePath = []; - return this.RequestParser.parse((this.validRequest), this.callback); - }); + describe('with a root resource path that is not a string', function() { + beforeEach(function() { + this.validRequest.compile.rootResourcePath = [] + return this.RequestParser.parse(this.validRequest, this.callback) + }) - return it("should return an error", function() { - return this.callback.calledWith("rootResourcePath attribute should be a string") - .should.equal(true); - }); - }); + return it('should return an error', function() { + return this.callback + .calledWith('rootResourcePath attribute should be a string') + .should.equal(true) + }) + }) - describe("with a root resource path that needs escaping", function() { - beforeEach(function() { - this.badPath = "`rm -rf foo`.tex"; - this.goodPath = "rm -rf foo.tex"; - this.validRequest.compile.rootResourcePath = this.badPath; - this.validRequest.compile.resources.push({ - path: this.badPath, - date: "12:00 01/02/03", - content: "Hello world" - }); - this.RequestParser.parse(this.validRequest, this.callback); - return this.data = this.callback.args[0][1];}); + describe('with a root resource path that needs escaping', function() { + beforeEach(function() { + this.badPath = '`rm -rf foo`.tex' + this.goodPath = 'rm -rf foo.tex' + this.validRequest.compile.rootResourcePath = this.badPath + this.validRequest.compile.resources.push({ + path: this.badPath, + date: '12:00 01/02/03', + content: 'Hello world' + }) + this.RequestParser.parse(this.validRequest, this.callback) + return (this.data = this.callback.args[0][1]) + }) - it("should return the escaped resource", function() { - return this.data.rootResourcePath.should.equal(this.goodPath); - }); + it('should return the escaped resource', function() { + return this.data.rootResourcePath.should.equal(this.goodPath) + }) - return it("should also escape the resource path", function() { - return this.data.resources[0].path.should.equal(this.goodPath); - }); - }); + return it('should also escape the resource path', function() { + return this.data.resources[0].path.should.equal(this.goodPath) + }) + }) - describe("with a root resource path that has a relative path", function() { - beforeEach(function() { - this.validRequest.compile.rootResourcePath = "foo/../../bar.tex"; - this.RequestParser.parse(this.validRequest, this.callback); - return this.data = this.callback.args[0][1];}); + describe('with a root resource path that has a relative path', function() { + beforeEach(function() { + this.validRequest.compile.rootResourcePath = 'foo/../../bar.tex' + this.RequestParser.parse(this.validRequest, this.callback) + return (this.data = this.callback.args[0][1]) + }) - return it("should return an error", function() { - return this.callback.calledWith("relative path in root resource") - .should.equal(true); - }); - }); + return it('should return an error', function() { + return this.callback + .calledWith('relative path in root resource') + .should.equal(true) + }) + }) - describe("with a root resource path that has unescaped + relative path", function() { - beforeEach(function() { - this.validRequest.compile.rootResourcePath = "foo/#../bar.tex"; - this.RequestParser.parse(this.validRequest, this.callback); - return this.data = this.callback.args[0][1];}); + describe('with a root resource path that has unescaped + relative path', function() { + beforeEach(function() { + this.validRequest.compile.rootResourcePath = 'foo/#../bar.tex' + this.RequestParser.parse(this.validRequest, this.callback) + return (this.data = this.callback.args[0][1]) + }) - return it("should return an error", function() { - return this.callback.calledWith("relative path in root resource") - .should.equal(true); - }); - }); + return it('should return an error', function() { + return this.callback + .calledWith('relative path in root resource') + .should.equal(true) + }) + }) - return describe("with an unknown syncType", function() { - beforeEach(function() { - this.validRequest.compile.options.syncType = "unexpected"; - this.RequestParser.parse(this.validRequest, this.callback); - return this.data = this.callback.args[0][1];}); + return describe('with an unknown syncType', function() { + beforeEach(function() { + this.validRequest.compile.options.syncType = 'unexpected' + this.RequestParser.parse(this.validRequest, this.callback) + return (this.data = this.callback.args[0][1]) + }) - return it("should return an error", function() { - return this.callback.calledWith("syncType attribute should be one of: full, incremental") - .should.equal(true); - }); - }); -}); + return it('should return an error', function() { + return this.callback + .calledWith('syncType attribute should be one of: full, incremental') + .should.equal(true) + }) + }) +}) diff --git a/services/clsi/test/unit/js/ResourceStateManagerTests.js b/services/clsi/test/unit/js/ResourceStateManagerTests.js index fe52cc584d..c0e89ef7ad 100644 --- a/services/clsi/test/unit/js/ResourceStateManagerTests.js +++ b/services/clsi/test/unit/js/ResourceStateManagerTests.js @@ -9,145 +9,200 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const SandboxedModule = require('sandboxed-module'); -const sinon = require('sinon'); -const should = require('chai').should(); -const modulePath = require('path').join(__dirname, '../../../app/js/ResourceStateManager'); -const Path = require("path"); -const Errors = require("../../../app/js/Errors"); +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +const should = require('chai').should() +const modulePath = require('path').join( + __dirname, + '../../../app/js/ResourceStateManager' +) +const Path = require('path') +const Errors = require('../../../app/js/Errors') -describe("ResourceStateManager", function() { - beforeEach(function() { - this.ResourceStateManager = SandboxedModule.require(modulePath, { requires: { - "fs": (this.fs = {}), - "logger-sharelatex": {log: sinon.stub(), err: sinon.stub()}, - "./SafeReader": (this.SafeReader = {}) - } - }); - this.basePath = "/path/to/write/files/to"; - this.resources = [ - {path: "resource-1-mock"}, - {path: "resource-2-mock"}, - {path: "resource-3-mock"} - ]; - this.state = "1234567890"; - this.resourceFileName = `${this.basePath}/.project-sync-state`; - this.resourceFileContents = `${this.resources[0].path}\n${this.resources[1].path}\n${this.resources[2].path}\nstateHash:${this.state}`; - return this.callback = sinon.stub(); - }); +describe('ResourceStateManager', function() { + beforeEach(function() { + this.ResourceStateManager = SandboxedModule.require(modulePath, { + requires: { + fs: (this.fs = {}), + 'logger-sharelatex': { log: sinon.stub(), err: sinon.stub() }, + './SafeReader': (this.SafeReader = {}) + } + }) + this.basePath = '/path/to/write/files/to' + this.resources = [ + { path: 'resource-1-mock' }, + { path: 'resource-2-mock' }, + { path: 'resource-3-mock' } + ] + this.state = '1234567890' + this.resourceFileName = `${this.basePath}/.project-sync-state` + this.resourceFileContents = `${this.resources[0].path}\n${this.resources[1].path}\n${this.resources[2].path}\nstateHash:${this.state}` + return (this.callback = sinon.stub()) + }) - describe("saveProjectState", function() { - beforeEach(function() { - return this.fs.writeFile = sinon.stub().callsArg(2); - }); + describe('saveProjectState', function() { + beforeEach(function() { + return (this.fs.writeFile = sinon.stub().callsArg(2)) + }) - describe("when the state is specified", function() { - beforeEach(function() { - return this.ResourceStateManager.saveProjectState(this.state, this.resources, this.basePath, this.callback); - }); + describe('when the state is specified', function() { + beforeEach(function() { + return this.ResourceStateManager.saveProjectState( + this.state, + this.resources, + this.basePath, + this.callback + ) + }) - it("should write the resource list to disk", function() { - return this.fs.writeFile - .calledWith(this.resourceFileName, this.resourceFileContents) - .should.equal(true); - }); + it('should write the resource list to disk', function() { + return this.fs.writeFile + .calledWith(this.resourceFileName, this.resourceFileContents) + .should.equal(true) + }) - return it("should call the callback", function() { - return this.callback.called.should.equal(true); - }); - }); + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) - return describe("when the state is undefined", function() { - beforeEach(function() { - this.state = undefined; - this.fs.unlink = sinon.stub().callsArg(1); - return this.ResourceStateManager.saveProjectState(this.state, this.resources, this.basePath, this.callback); - }); + return describe('when the state is undefined', function() { + beforeEach(function() { + this.state = undefined + this.fs.unlink = sinon.stub().callsArg(1) + return this.ResourceStateManager.saveProjectState( + this.state, + this.resources, + this.basePath, + this.callback + ) + }) - it("should unlink the resource file", function() { - return this.fs.unlink - .calledWith(this.resourceFileName) - .should.equal(true); - }); + it('should unlink the resource file', function() { + return this.fs.unlink + .calledWith(this.resourceFileName) + .should.equal(true) + }) - it("should not write the resource list to disk", function() { - return this.fs.writeFile.called.should.equal(false); - }); + it('should not write the resource list to disk', function() { + return this.fs.writeFile.called.should.equal(false) + }) - return it("should call the callback", function() { - return this.callback.called.should.equal(true); - }); - }); - }); + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) + }) - describe("checkProjectStateMatches", function() { + describe('checkProjectStateMatches', function() { + describe('when the state matches', function() { + beforeEach(function() { + this.SafeReader.readFile = sinon + .stub() + .callsArgWith(3, null, this.resourceFileContents) + return this.ResourceStateManager.checkProjectStateMatches( + this.state, + this.basePath, + this.callback + ) + }) - describe("when the state matches", function() { - beforeEach(function() { - this.SafeReader.readFile = sinon.stub().callsArgWith(3, null, this.resourceFileContents); - return this.ResourceStateManager.checkProjectStateMatches(this.state, this.basePath, this.callback); - }); + it('should read the resource file', function() { + return this.SafeReader.readFile + .calledWith(this.resourceFileName) + .should.equal(true) + }) - it("should read the resource file", function() { - return this.SafeReader.readFile - .calledWith(this.resourceFileName) - .should.equal(true); - }); + return it('should call the callback with the results', function() { + return this.callback + .calledWithMatch(null, this.resources) + .should.equal(true) + }) + }) - return it("should call the callback with the results", function() { - return this.callback.calledWithMatch(null, this.resources).should.equal(true); - }); - }); + return describe('when the state does not match', function() { + beforeEach(function() { + this.SafeReader.readFile = sinon + .stub() + .callsArgWith(3, null, this.resourceFileContents) + return this.ResourceStateManager.checkProjectStateMatches( + 'not-the-original-state', + this.basePath, + this.callback + ) + }) - return describe("when the state does not match", function() { - beforeEach(function() { - this.SafeReader.readFile = sinon.stub().callsArgWith(3, null, this.resourceFileContents); - return this.ResourceStateManager.checkProjectStateMatches("not-the-original-state", this.basePath, this.callback); - }); + return it('should call the callback with an error', function() { + const error = new Errors.FilesOutOfSyncError( + 'invalid state for incremental update' + ) + return this.callback.calledWith(error).should.equal(true) + }) + }) + }) - return it("should call the callback with an error", function() { - const error = new Errors.FilesOutOfSyncError("invalid state for incremental update"); - return this.callback.calledWith(error).should.equal(true); - }); - }); - }); + return describe('checkResourceFiles', function() { + describe('when all the files are present', function() { + beforeEach(function() { + this.allFiles = [ + this.resources[0].path, + this.resources[1].path, + this.resources[2].path + ] + return this.ResourceStateManager.checkResourceFiles( + this.resources, + this.allFiles, + this.basePath, + this.callback + ) + }) - return describe("checkResourceFiles", function() { - describe("when all the files are present", function() { - beforeEach(function() { - this.allFiles = [ this.resources[0].path, this.resources[1].path, this.resources[2].path]; - return this.ResourceStateManager.checkResourceFiles(this.resources, this.allFiles, this.basePath, this.callback); - }); + return it('should call the callback', function() { + return this.callback.calledWithExactly().should.equal(true) + }) + }) - return it("should call the callback", function() { - return this.callback.calledWithExactly().should.equal(true); - }); - }); + describe('when there is a missing file', function() { + beforeEach(function() { + this.allFiles = [this.resources[0].path, this.resources[1].path] + this.fs.stat = sinon.stub().callsArgWith(1, new Error()) + return this.ResourceStateManager.checkResourceFiles( + this.resources, + this.allFiles, + this.basePath, + this.callback + ) + }) - describe("when there is a missing file", function() { - beforeEach(function() { - this.allFiles = [ this.resources[0].path, this.resources[1].path]; - this.fs.stat = sinon.stub().callsArgWith(1, new Error()); - return this.ResourceStateManager.checkResourceFiles(this.resources, this.allFiles, this.basePath, this.callback); - }); + return it('should call the callback with an error', function() { + const error = new Errors.FilesOutOfSyncError( + 'resource files missing in incremental update' + ) + return this.callback.calledWith(error).should.equal(true) + }) + }) - return it("should call the callback with an error", function() { - const error = new Errors.FilesOutOfSyncError("resource files missing in incremental update"); - return this.callback.calledWith(error).should.equal(true); - }); - }); - - return describe("when a resource contains a relative path", function() { - beforeEach(function() { - this.resources[0].path = "../foo/bar.tex"; - this.allFiles = [ this.resources[0].path, this.resources[1].path, this.resources[2].path]; - return this.ResourceStateManager.checkResourceFiles(this.resources, this.allFiles, this.basePath, this.callback); - }); - - return it("should call the callback with an error", function() { - return this.callback.calledWith(new Error("relative path in resource file list")).should.equal(true); - }); - }); - }); -}); + return describe('when a resource contains a relative path', function() { + beforeEach(function() { + this.resources[0].path = '../foo/bar.tex' + this.allFiles = [ + this.resources[0].path, + this.resources[1].path, + this.resources[2].path + ] + return this.ResourceStateManager.checkResourceFiles( + this.resources, + this.allFiles, + this.basePath, + this.callback + ) + }) + return it('should call the callback with an error', function() { + return this.callback + .calledWith(new Error('relative path in resource file list')) + .should.equal(true) + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/ResourceWriterTests.js b/services/clsi/test/unit/js/ResourceWriterTests.js index 830954739f..189908dc6b 100644 --- a/services/clsi/test/unit/js/ResourceWriterTests.js +++ b/services/clsi/test/unit/js/ResourceWriterTests.js @@ -10,405 +10,491 @@ * DS206: Consider reworking classes to avoid initClass * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const SandboxedModule = require('sandboxed-module'); -const sinon = require('sinon'); -const should = require('chai').should(); -const modulePath = require('path').join(__dirname, '../../../app/js/ResourceWriter'); -const path = require("path"); +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +const should = require('chai').should() +const modulePath = require('path').join( + __dirname, + '../../../app/js/ResourceWriter' +) +const path = require('path') -describe("ResourceWriter", function() { - beforeEach(function() { - let Timer; - this.ResourceWriter = SandboxedModule.require(modulePath, { requires: { - "fs": (this.fs = { - mkdir: sinon.stub().callsArg(1), - unlink: sinon.stub().callsArg(1) - }), - "./ResourceStateManager": (this.ResourceStateManager = {}), - "wrench": (this.wrench = {}), - "./UrlCache" : (this.UrlCache = {}), - "mkdirp" : (this.mkdirp = sinon.stub().callsArg(1)), - "./OutputFileFinder": (this.OutputFileFinder = {}), - "logger-sharelatex": {log: sinon.stub(), err: sinon.stub()}, - "./Metrics": (this.Metrics = { - Timer: (Timer = (function() { - Timer = class Timer { - static initClass() { - this.prototype.done = sinon.stub(); - } - }; - Timer.initClass(); - return Timer; - })()) - }) - } - } - ); - this.project_id = "project-id-123"; - this.basePath = "/path/to/write/files/to"; - return this.callback = sinon.stub(); - }); +describe('ResourceWriter', function() { + beforeEach(function() { + let Timer + this.ResourceWriter = SandboxedModule.require(modulePath, { + requires: { + fs: (this.fs = { + mkdir: sinon.stub().callsArg(1), + unlink: sinon.stub().callsArg(1) + }), + './ResourceStateManager': (this.ResourceStateManager = {}), + wrench: (this.wrench = {}), + './UrlCache': (this.UrlCache = {}), + mkdirp: (this.mkdirp = sinon.stub().callsArg(1)), + './OutputFileFinder': (this.OutputFileFinder = {}), + 'logger-sharelatex': { log: sinon.stub(), err: sinon.stub() }, + './Metrics': (this.Metrics = { + Timer: (Timer = (function() { + Timer = class Timer { + static initClass() { + this.prototype.done = sinon.stub() + } + } + Timer.initClass() + return Timer + })()) + }) + } + }) + this.project_id = 'project-id-123' + this.basePath = '/path/to/write/files/to' + return (this.callback = sinon.stub()) + }) - describe("syncResourcesToDisk on a full request", function() { - beforeEach(function() { - this.resources = [ - "resource-1-mock", - "resource-2-mock", - "resource-3-mock" - ]; - this.ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3); - this.ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2); - this.ResourceStateManager.saveProjectState = sinon.stub().callsArg(3); - return this.ResourceWriter.syncResourcesToDisk({ - project_id: this.project_id, - syncState: (this.syncState = "0123456789abcdef"), - resources: this.resources - }, this.basePath, this.callback); - }); + describe('syncResourcesToDisk on a full request', function() { + beforeEach(function() { + this.resources = ['resource-1-mock', 'resource-2-mock', 'resource-3-mock'] + this.ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3) + this.ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2) + this.ResourceStateManager.saveProjectState = sinon.stub().callsArg(3) + return this.ResourceWriter.syncResourcesToDisk( + { + project_id: this.project_id, + syncState: (this.syncState = '0123456789abcdef'), + resources: this.resources + }, + this.basePath, + this.callback + ) + }) - it("should remove old files", function() { - return this.ResourceWriter._removeExtraneousFiles - .calledWith(this.resources, this.basePath) - .should.equal(true); - }); + it('should remove old files', function() { + return this.ResourceWriter._removeExtraneousFiles + .calledWith(this.resources, this.basePath) + .should.equal(true) + }) - it("should write each resource to disk", function() { - return Array.from(this.resources).map((resource) => - this.ResourceWriter._writeResourceToDisk - .calledWith(this.project_id, resource, this.basePath) - .should.equal(true)); - }); + it('should write each resource to disk', function() { + return Array.from(this.resources).map(resource => + this.ResourceWriter._writeResourceToDisk + .calledWith(this.project_id, resource, this.basePath) + .should.equal(true) + ) + }) - it("should store the sync state and resource list", function() { - return this.ResourceStateManager.saveProjectState - .calledWith(this.syncState, this.resources, this.basePath) - .should.equal(true); - }); + it('should store the sync state and resource list', function() { + return this.ResourceStateManager.saveProjectState + .calledWith(this.syncState, this.resources, this.basePath) + .should.equal(true) + }) - return it("should call the callback", function() { - return this.callback.called.should.equal(true); - }); - }); + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) - describe("syncResourcesToDisk on an incremental update", function() { - beforeEach(function() { - this.resources = [ - "resource-1-mock" - ]; - this.ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3); - this.ResourceWriter._removeExtraneousFiles = sinon.stub().callsArgWith(2, null, (this.outputFiles = []), (this.allFiles = [])); - this.ResourceStateManager.checkProjectStateMatches = sinon.stub().callsArgWith(2, null, this.resources); - this.ResourceStateManager.saveProjectState = sinon.stub().callsArg(3); - this.ResourceStateManager.checkResourceFiles = sinon.stub().callsArg(3); - return this.ResourceWriter.syncResourcesToDisk({ - project_id: this.project_id, - syncType: "incremental", - syncState: (this.syncState = "1234567890abcdef"), - resources: this.resources - }, this.basePath, this.callback); - }); + describe('syncResourcesToDisk on an incremental update', function() { + beforeEach(function() { + this.resources = ['resource-1-mock'] + this.ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3) + this.ResourceWriter._removeExtraneousFiles = sinon + .stub() + .callsArgWith(2, null, (this.outputFiles = []), (this.allFiles = [])) + this.ResourceStateManager.checkProjectStateMatches = sinon + .stub() + .callsArgWith(2, null, this.resources) + this.ResourceStateManager.saveProjectState = sinon.stub().callsArg(3) + this.ResourceStateManager.checkResourceFiles = sinon.stub().callsArg(3) + return this.ResourceWriter.syncResourcesToDisk( + { + project_id: this.project_id, + syncType: 'incremental', + syncState: (this.syncState = '1234567890abcdef'), + resources: this.resources + }, + this.basePath, + this.callback + ) + }) - it("should check the sync state matches", function() { - return this.ResourceStateManager.checkProjectStateMatches - .calledWith(this.syncState, this.basePath) - .should.equal(true); - }); + it('should check the sync state matches', function() { + return this.ResourceStateManager.checkProjectStateMatches + .calledWith(this.syncState, this.basePath) + .should.equal(true) + }) - it("should remove old files", function() { - return this.ResourceWriter._removeExtraneousFiles - .calledWith(this.resources, this.basePath) - .should.equal(true); - }); + it('should remove old files', function() { + return this.ResourceWriter._removeExtraneousFiles + .calledWith(this.resources, this.basePath) + .should.equal(true) + }) - it("should check each resource exists", function() { - return this.ResourceStateManager.checkResourceFiles - .calledWith(this.resources, this.allFiles, this.basePath) - .should.equal(true); - }); + it('should check each resource exists', function() { + return this.ResourceStateManager.checkResourceFiles + .calledWith(this.resources, this.allFiles, this.basePath) + .should.equal(true) + }) - it("should write each resource to disk", function() { - return Array.from(this.resources).map((resource) => - this.ResourceWriter._writeResourceToDisk - .calledWith(this.project_id, resource, this.basePath) - .should.equal(true)); - }); + it('should write each resource to disk', function() { + return Array.from(this.resources).map(resource => + this.ResourceWriter._writeResourceToDisk + .calledWith(this.project_id, resource, this.basePath) + .should.equal(true) + ) + }) - return it("should call the callback", function() { - return this.callback.called.should.equal(true); - }); - }); + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) - describe("syncResourcesToDisk on an incremental update when the state does not match", function() { - beforeEach(function() { - this.resources = [ - "resource-1-mock" - ]; - this.ResourceStateManager.checkProjectStateMatches = sinon.stub().callsArgWith(2, (this.error = new Error())); - return this.ResourceWriter.syncResourcesToDisk({ - project_id: this.project_id, - syncType: "incremental", - syncState: (this.syncState = "1234567890abcdef"), - resources: this.resources - }, this.basePath, this.callback); - }); + describe('syncResourcesToDisk on an incremental update when the state does not match', function() { + beforeEach(function() { + this.resources = ['resource-1-mock'] + this.ResourceStateManager.checkProjectStateMatches = sinon + .stub() + .callsArgWith(2, (this.error = new Error())) + return this.ResourceWriter.syncResourcesToDisk( + { + project_id: this.project_id, + syncType: 'incremental', + syncState: (this.syncState = '1234567890abcdef'), + resources: this.resources + }, + this.basePath, + this.callback + ) + }) - it("should check whether the sync state matches", function() { - return this.ResourceStateManager.checkProjectStateMatches - .calledWith(this.syncState, this.basePath) - .should.equal(true); - }); + it('should check whether the sync state matches', function() { + return this.ResourceStateManager.checkProjectStateMatches + .calledWith(this.syncState, this.basePath) + .should.equal(true) + }) - return it("should call the callback with an error", function() { - return this.callback.calledWith(this.error).should.equal(true); - }); - }); + return it('should call the callback with an error', function() { + return this.callback.calledWith(this.error).should.equal(true) + }) + }) + describe('_removeExtraneousFiles', function() { + beforeEach(function() { + this.output_files = [ + { + path: 'output.pdf', + type: 'pdf' + }, + { + path: 'extra/file.tex', + type: 'tex' + }, + { + path: 'extra.aux', + type: 'aux' + }, + { + path: 'cache/_chunk1' + }, + { + path: 'figures/image-eps-converted-to.pdf', + type: 'pdf' + }, + { + path: 'foo/main-figure0.md5', + type: 'md5' + }, + { + path: 'foo/main-figure0.dpth', + type: 'dpth' + }, + { + path: 'foo/main-figure0.pdf', + type: 'pdf' + }, + { + path: '_minted-main/default-pyg-prefix.pygstyle', + type: 'pygstyle' + }, + { + path: '_minted-main/default.pygstyle', + type: 'pygstyle' + }, + { + path: + '_minted-main/35E248B60965545BD232AE9F0FE9750D504A7AF0CD3BAA7542030FC560DFCC45.pygtex', + type: 'pygtex' + }, + { + path: '_markdown_main/30893013dec5d869a415610079774c2f.md.tex', + type: 'tex' + } + ] + this.resources = 'mock-resources' + this.OutputFileFinder.findOutputFiles = sinon + .stub() + .callsArgWith(2, null, this.output_files) + this.ResourceWriter._deleteFileIfNotDirectory = sinon.stub().callsArg(1) + return this.ResourceWriter._removeExtraneousFiles( + this.resources, + this.basePath, + this.callback + ) + }) - describe("_removeExtraneousFiles", function() { - beforeEach(function() { - this.output_files = [{ - path: "output.pdf", - type: "pdf" - }, { - path: "extra/file.tex", - type: "tex" - }, { - path: "extra.aux", - type: "aux" - }, { - path: "cache/_chunk1" - },{ - path: "figures/image-eps-converted-to.pdf", - type: "pdf" - },{ - path: "foo/main-figure0.md5", - type: "md5" - }, { - path: "foo/main-figure0.dpth", - type: "dpth" - }, { - path: "foo/main-figure0.pdf", - type: "pdf" - }, { - path: "_minted-main/default-pyg-prefix.pygstyle", - type: "pygstyle" - }, { - path: "_minted-main/default.pygstyle", - type: "pygstyle" - }, { - path: "_minted-main/35E248B60965545BD232AE9F0FE9750D504A7AF0CD3BAA7542030FC560DFCC45.pygtex", - type: "pygtex" - }, { - path: "_markdown_main/30893013dec5d869a415610079774c2f.md.tex", - type: "tex" - }]; - this.resources = "mock-resources"; - this.OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, this.output_files); - this.ResourceWriter._deleteFileIfNotDirectory = sinon.stub().callsArg(1); - return this.ResourceWriter._removeExtraneousFiles(this.resources, this.basePath, this.callback); - }); + it('should find the existing output files', function() { + return this.OutputFileFinder.findOutputFiles + .calledWith(this.resources, this.basePath) + .should.equal(true) + }) - it("should find the existing output files", function() { - return this.OutputFileFinder.findOutputFiles - .calledWith(this.resources, this.basePath) - .should.equal(true); - }); + it('should delete the output files', function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, 'output.pdf')) + .should.equal(true) + }) - it("should delete the output files", function() { - return this.ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(this.basePath, "output.pdf")) - .should.equal(true); - }); + it('should delete the extra files', function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, 'extra/file.tex')) + .should.equal(true) + }) - it("should delete the extra files", function() { - return this.ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(this.basePath, "extra/file.tex")) - .should.equal(true); - }); + it('should not delete the extra aux files', function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, 'extra.aux')) + .should.equal(false) + }) - it("should not delete the extra aux files", function() { - return this.ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(this.basePath, "extra.aux")) - .should.equal(false); - }); - - it("should not delete the knitr cache file", function() { - return this.ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(this.basePath, "cache/_chunk1")) - .should.equal(false); - }); + it('should not delete the knitr cache file', function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, 'cache/_chunk1')) + .should.equal(false) + }) - it("should not delete the epstopdf converted files", function() { - return this.ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(this.basePath, "figures/image-eps-converted-to.pdf")) - .should.equal(false); - }); + it('should not delete the epstopdf converted files', function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith( + path.join(this.basePath, 'figures/image-eps-converted-to.pdf') + ) + .should.equal(false) + }) - it("should not delete the tikz md5 files", function() { - return this.ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(this.basePath, "foo/main-figure0.md5")) - .should.equal(false); - }); + it('should not delete the tikz md5 files', function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, 'foo/main-figure0.md5')) + .should.equal(false) + }) - it("should not delete the tikz dpth files", function() { - return this.ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(this.basePath, "foo/main-figure0.dpth")) - .should.equal(false); - }); + it('should not delete the tikz dpth files', function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, 'foo/main-figure0.dpth')) + .should.equal(false) + }) - it("should not delete the tikz pdf files", function() { - return this.ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(this.basePath, "foo/main-figure0.pdf")) - .should.equal(false); - }); + it('should not delete the tikz pdf files', function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, 'foo/main-figure0.pdf')) + .should.equal(false) + }) - it("should not delete the minted pygstyle files", function() { - return this.ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(this.basePath, "_minted-main/default-pyg-prefix.pygstyle")) - .should.equal(false); - }); + it('should not delete the minted pygstyle files', function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith( + path.join(this.basePath, '_minted-main/default-pyg-prefix.pygstyle') + ) + .should.equal(false) + }) - it("should not delete the minted default pygstyle files", function() { - return this.ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(this.basePath, "_minted-main/default.pygstyle")) - .should.equal(false); - }); + it('should not delete the minted default pygstyle files', function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, '_minted-main/default.pygstyle')) + .should.equal(false) + }) - it("should not delete the minted default pygtex files", function() { - return this.ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(this.basePath, "_minted-main/35E248B60965545BD232AE9F0FE9750D504A7AF0CD3BAA7542030FC560DFCC45.pygtex")) - .should.equal(false); - }); + it('should not delete the minted default pygtex files', function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith( + path.join( + this.basePath, + '_minted-main/35E248B60965545BD232AE9F0FE9750D504A7AF0CD3BAA7542030FC560DFCC45.pygtex' + ) + ) + .should.equal(false) + }) - it("should not delete the markdown md.tex files", function() { - return this.ResourceWriter._deleteFileIfNotDirectory - .calledWith(path.join(this.basePath, "_markdown_main/30893013dec5d869a415610079774c2f.md.tex")) - .should.equal(false); - }); + it('should not delete the markdown md.tex files', function() { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith( + path.join( + this.basePath, + '_markdown_main/30893013dec5d869a415610079774c2f.md.tex' + ) + ) + .should.equal(false) + }) - it("should call the callback", function() { - return this.callback.called.should.equal(true); - }); + it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) - return it("should time the request", function() { - return this.Metrics.Timer.prototype.done.called.should.equal(true); - }); - }); + return it('should time the request', function() { + return this.Metrics.Timer.prototype.done.called.should.equal(true) + }) + }) - describe("_writeResourceToDisk", function() { - describe("with a url based resource", function() { - beforeEach(function() { - this.resource = { - path: "main.tex", - url: "http://www.example.com/main.tex", - modified: Date.now() - }; - this.UrlCache.downloadUrlToFile = sinon.stub().callsArgWith(4, "fake error downloading file"); - return this.ResourceWriter._writeResourceToDisk(this.project_id, this.resource, this.basePath, this.callback); - }); + describe('_writeResourceToDisk', function() { + describe('with a url based resource', function() { + beforeEach(function() { + this.resource = { + path: 'main.tex', + url: 'http://www.example.com/main.tex', + modified: Date.now() + } + this.UrlCache.downloadUrlToFile = sinon + .stub() + .callsArgWith(4, 'fake error downloading file') + return this.ResourceWriter._writeResourceToDisk( + this.project_id, + this.resource, + this.basePath, + this.callback + ) + }) - it("should ensure the directory exists", function() { - return this.mkdirp - .calledWith(path.dirname(path.join(this.basePath, this.resource.path))) - .should.equal(true); - }); + it('should ensure the directory exists', function() { + return this.mkdirp + .calledWith( + path.dirname(path.join(this.basePath, this.resource.path)) + ) + .should.equal(true) + }) - it("should write the URL from the cache", function() { - return this.UrlCache.downloadUrlToFile - .calledWith(this.project_id, this.resource.url, path.join(this.basePath, this.resource.path), this.resource.modified) - .should.equal(true); - }); - - it("should call the callback", function() { - return this.callback.called.should.equal(true); - }); + it('should write the URL from the cache', function() { + return this.UrlCache.downloadUrlToFile + .calledWith( + this.project_id, + this.resource.url, + path.join(this.basePath, this.resource.path), + this.resource.modified + ) + .should.equal(true) + }) - return it("should not return an error if the resource writer errored", function() { - return should.not.exist(this.callback.args[0][0]); - }); - }); + it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) - describe("with a content based resource", function() { - beforeEach(function() { - this.resource = { - path: "main.tex", - content: "Hello world" - }; - this.fs.writeFile = sinon.stub().callsArg(2); - return this.ResourceWriter._writeResourceToDisk(this.project_id, this.resource, this.basePath, this.callback); - }); + return it('should not return an error if the resource writer errored', function() { + return should.not.exist(this.callback.args[0][0]) + }) + }) - it("should ensure the directory exists", function() { - return this.mkdirp - .calledWith(path.dirname(path.join(this.basePath, this.resource.path))) - .should.equal(true); - }); + describe('with a content based resource', function() { + beforeEach(function() { + this.resource = { + path: 'main.tex', + content: 'Hello world' + } + this.fs.writeFile = sinon.stub().callsArg(2) + return this.ResourceWriter._writeResourceToDisk( + this.project_id, + this.resource, + this.basePath, + this.callback + ) + }) - it("should write the contents to disk", function() { - return this.fs.writeFile - .calledWith(path.join(this.basePath, this.resource.path), this.resource.content) - .should.equal(true); - }); - - return it("should call the callback", function() { - return this.callback.called.should.equal(true); - }); - }); + it('should ensure the directory exists', function() { + return this.mkdirp + .calledWith( + path.dirname(path.join(this.basePath, this.resource.path)) + ) + .should.equal(true) + }) - return describe("with a file path that breaks out of the root folder", function() { - beforeEach(function() { - this.resource = { - path: "../../main.tex", - content: "Hello world" - }; - this.fs.writeFile = sinon.stub().callsArg(2); - return this.ResourceWriter._writeResourceToDisk(this.project_id, this.resource, this.basePath, this.callback); - }); + it('should write the contents to disk', function() { + return this.fs.writeFile + .calledWith( + path.join(this.basePath, this.resource.path), + this.resource.content + ) + .should.equal(true) + }) - it("should not write to disk", function() { - return this.fs.writeFile.called.should.equal(false); - }); + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) - return it("should return an error", function() { - return this.callback - .calledWith(new Error("resource path is outside root directory")) - .should.equal(true); - }); - }); - }); - - return describe("checkPath", function() { - describe("with a valid path", function() { - beforeEach(function() { - return this.ResourceWriter.checkPath("foo", "bar", this.callback); - }); + return describe('with a file path that breaks out of the root folder', function() { + beforeEach(function() { + this.resource = { + path: '../../main.tex', + content: 'Hello world' + } + this.fs.writeFile = sinon.stub().callsArg(2) + return this.ResourceWriter._writeResourceToDisk( + this.project_id, + this.resource, + this.basePath, + this.callback + ) + }) - return it("should return the joined path", function() { - return this.callback.calledWith(null, "foo/bar") - .should.equal(true); - }); - }); + it('should not write to disk', function() { + return this.fs.writeFile.called.should.equal(false) + }) - describe("with an invalid path", function() { - beforeEach(function() { - return this.ResourceWriter.checkPath("foo", "baz/../../bar", this.callback); - }); + return it('should return an error', function() { + return this.callback + .calledWith(new Error('resource path is outside root directory')) + .should.equal(true) + }) + }) + }) - return it("should return an error", function() { - return this.callback.calledWith(new Error("resource path is outside root directory")) - .should.equal(true); - }); - }); + return describe('checkPath', function() { + describe('with a valid path', function() { + beforeEach(function() { + return this.ResourceWriter.checkPath('foo', 'bar', this.callback) + }) - return describe("with another invalid path matching on a prefix", function() { - beforeEach(function() { - return this.ResourceWriter.checkPath("foo", "../foobar/baz", this.callback); - }); + return it('should return the joined path', function() { + return this.callback.calledWith(null, 'foo/bar').should.equal(true) + }) + }) - return it("should return an error", function() { - return this.callback.calledWith(new Error("resource path is outside root directory")) - .should.equal(true); - }); - }); - }); -}); + describe('with an invalid path', function() { + beforeEach(function() { + return this.ResourceWriter.checkPath( + 'foo', + 'baz/../../bar', + this.callback + ) + }) + + return it('should return an error', function() { + return this.callback + .calledWith(new Error('resource path is outside root directory')) + .should.equal(true) + }) + }) + + return describe('with another invalid path matching on a prefix', function() { + beforeEach(function() { + return this.ResourceWriter.checkPath( + 'foo', + '../foobar/baz', + this.callback + ) + }) + + return it('should return an error', function() { + return this.callback + .calledWith(new Error('resource path is outside root directory')) + .should.equal(true) + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js b/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js index e754ea7547..b9545a4ce1 100644 --- a/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js +++ b/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js @@ -9,217 +9,229 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const should = require('chai').should(); -const SandboxedModule = require('sandboxed-module'); -const assert = require('assert'); -const path = require('path'); -const sinon = require('sinon'); -const modulePath = path.join(__dirname, "../../../app/js/StaticServerForbidSymlinks"); -const { expect } = require("chai"); +const should = require('chai').should() +const SandboxedModule = require('sandboxed-module') +const assert = require('assert') +const path = require('path') +const sinon = require('sinon') +const modulePath = path.join( + __dirname, + '../../../app/js/StaticServerForbidSymlinks' +) +const { expect } = require('chai') -describe("StaticServerForbidSymlinks", function() { +describe('StaticServerForbidSymlinks', function() { + beforeEach(function() { + this.settings = { + path: { + compilesDir: '/compiles/here' + } + } - beforeEach(function() { + this.fs = {} + this.ForbidSymlinks = SandboxedModule.require(modulePath, { + requires: { + 'settings-sharelatex': this.settings, + 'logger-sharelatex': { + log() {}, + warn() {}, + error() {} + }, + fs: this.fs + } + }) - this.settings = { - path: { - compilesDir: "/compiles/here" - } - }; + this.dummyStatic = (rootDir, options) => (req, res, next) => + // console.log "dummyStatic serving file", rootDir, "called with", req.url + // serve it + next() - this.fs = {}; - this.ForbidSymlinks = SandboxedModule.require(modulePath, { requires: { - "settings-sharelatex":this.settings, - "logger-sharelatex": { - log() {}, - warn() {}, - error() {} - }, - "fs":this.fs - } - } - ); + this.StaticServerForbidSymlinks = this.ForbidSymlinks( + this.dummyStatic, + this.settings.path.compilesDir + ) + this.req = { + params: { + project_id: '12345' + } + } - this.dummyStatic = (rootDir, options) => - (req, res, next) => - // console.log "dummyStatic serving file", rootDir, "called with", req.url - // serve it - next() - - ; + this.res = {} + return (this.req.url = '/12345/output.pdf') + }) - this.StaticServerForbidSymlinks = this.ForbidSymlinks(this.dummyStatic, this.settings.path.compilesDir); - this.req = { - params: { - project_id:"12345" - } - }; + describe('sending a normal file through', function() { + beforeEach(function() { + return (this.fs.realpath = sinon + .stub() + .callsArgWith( + 1, + null, + `${this.settings.path.compilesDir}/${this.req.params.project_id}/output.pdf` + )) + }) - this.res = {}; - return this.req.url = "/12345/output.pdf"; - }); + return it('should call next', function(done) { + this.res.sendStatus = function(resCode) { + resCode.should.equal(200) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res, done) + }) + }) + describe('with a missing file', function() { + beforeEach(function() { + return (this.fs.realpath = sinon + .stub() + .callsArgWith( + 1, + { code: 'ENOENT' }, + `${this.settings.path.compilesDir}/${this.req.params.project_id}/unknown.pdf` + )) + }) - describe("sending a normal file through", function() { - beforeEach(function() { - return this.fs.realpath = sinon.stub().callsArgWith(1, null, `${this.settings.path.compilesDir}/${this.req.params.project_id}/output.pdf`); - }); + return it('should send a 404', function(done) { + this.res.sendStatus = function(resCode) { + resCode.should.equal(404) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res) + }) + }) - return it("should call next", function(done){ - this.res.sendStatus = function(resCode){ - resCode.should.equal(200); - return done(); - }; - return this.StaticServerForbidSymlinks(this.req, this.res, done); - }); - }); + describe('with a symlink file', function() { + beforeEach(function() { + return (this.fs.realpath = sinon + .stub() + .callsArgWith(1, null, `/etc/${this.req.params.project_id}/output.pdf`)) + }) + return it('should send a 404', function(done) { + this.res.sendStatus = function(resCode) { + resCode.should.equal(404) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res) + }) + }) - describe("with a missing file", function() { - beforeEach(function() { - return this.fs.realpath = sinon.stub().callsArgWith(1, {code: 'ENOENT'}, `${this.settings.path.compilesDir}/${this.req.params.project_id}/unknown.pdf`); - }); + describe('with a relative file', function() { + beforeEach(function() { + return (this.req.url = '/12345/../67890/output.pdf') + }) - return it("should send a 404", function(done){ - this.res.sendStatus = function(resCode){ - resCode.should.equal(404); - return done(); - }; - return this.StaticServerForbidSymlinks(this.req, this.res); - }); - }); + return it('should send a 404', function(done) { + this.res.sendStatus = function(resCode) { + resCode.should.equal(404) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res) + }) + }) + describe('with a unnormalized file containing .', function() { + beforeEach(function() { + return (this.req.url = '/12345/foo/./output.pdf') + }) - describe("with a symlink file", function() { - beforeEach(function() { - return this.fs.realpath = sinon.stub().callsArgWith(1, null, `/etc/${this.req.params.project_id}/output.pdf`); - }); + return it('should send a 404', function(done) { + this.res.sendStatus = function(resCode) { + resCode.should.equal(404) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res) + }) + }) - return it("should send a 404", function(done){ - this.res.sendStatus = function(resCode){ - resCode.should.equal(404); - return done(); - }; - return this.StaticServerForbidSymlinks(this.req, this.res); - }); - }); + describe('with a file containing an empty path', function() { + beforeEach(function() { + return (this.req.url = '/12345/foo//output.pdf') + }) + return it('should send a 404', function(done) { + this.res.sendStatus = function(resCode) { + resCode.should.equal(404) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res) + }) + }) - describe("with a relative file", function() { - beforeEach(function() { - return this.req.url = "/12345/../67890/output.pdf"; - }); + describe('with a non-project file', function() { + beforeEach(function() { + return (this.req.url = '/.foo/output.pdf') + }) - return it("should send a 404", function(done){ - this.res.sendStatus = function(resCode){ - resCode.should.equal(404); - return done(); - }; - return this.StaticServerForbidSymlinks(this.req, this.res); - }); - }); + return it('should send a 404', function(done) { + this.res.sendStatus = function(resCode) { + resCode.should.equal(404) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res) + }) + }) + describe('with a file outside the compiledir', function() { + beforeEach(function() { + return (this.req.url = '/../bar/output.pdf') + }) - describe("with a unnormalized file containing .", function() { - beforeEach(function() { - return this.req.url = "/12345/foo/./output.pdf"; - }); + return it('should send a 404', function(done) { + this.res.sendStatus = function(resCode) { + resCode.should.equal(404) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res) + }) + }) - return it("should send a 404", function(done){ - this.res.sendStatus = function(resCode){ - resCode.should.equal(404); - return done(); - }; - return this.StaticServerForbidSymlinks(this.req, this.res); - }); - }); + describe('with a file with no leading /', function() { + beforeEach(function() { + return (this.req.url = './../bar/output.pdf') + }) + return it('should send a 404', function(done) { + this.res.sendStatus = function(resCode) { + resCode.should.equal(404) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res) + }) + }) - describe("with a file containing an empty path", function() { - beforeEach(function() { - return this.req.url = "/12345/foo//output.pdf"; - }); + describe('with a github style path', function() { + beforeEach(function() { + this.req.url = '/henryoswald-latex_example/output/output.log' + return (this.fs.realpath = sinon + .stub() + .callsArgWith( + 1, + null, + `${this.settings.path.compilesDir}/henryoswald-latex_example/output/output.log` + )) + }) - return it("should send a 404", function(done){ - this.res.sendStatus = function(resCode){ - resCode.should.equal(404); - return done(); - }; - return this.StaticServerForbidSymlinks(this.req, this.res); - }); - }); + return it('should call next', function(done) { + this.res.sendStatus = function(resCode) { + resCode.should.equal(200) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res, done) + }) + }) - describe("with a non-project file", function() { - beforeEach(function() { - return this.req.url = "/.foo/output.pdf"; - }); - - return it("should send a 404", function(done){ - this.res.sendStatus = function(resCode){ - resCode.should.equal(404); - return done(); - }; - return this.StaticServerForbidSymlinks(this.req, this.res); - }); - }); - - describe("with a file outside the compiledir", function() { - beforeEach(function() { - return this.req.url = "/../bar/output.pdf"; - }); - - return it("should send a 404", function(done){ - this.res.sendStatus = function(resCode){ - resCode.should.equal(404); - return done(); - }; - return this.StaticServerForbidSymlinks(this.req, this.res); - }); - }); - - - describe("with a file with no leading /", function() { - beforeEach(function() { - return this.req.url = "./../bar/output.pdf"; - }); - - return it("should send a 404", function(done){ - this.res.sendStatus = function(resCode){ - resCode.should.equal(404); - return done(); - }; - return this.StaticServerForbidSymlinks(this.req, this.res); - }); - }); - - describe("with a github style path", function() { - beforeEach(function() { - this.req.url = "/henryoswald-latex_example/output/output.log"; - return this.fs.realpath = sinon.stub().callsArgWith(1, null, `${this.settings.path.compilesDir}/henryoswald-latex_example/output/output.log`); - }); - - return it("should call next", function(done){ - this.res.sendStatus = function(resCode){ - resCode.should.equal(200); - return done(); - }; - return this.StaticServerForbidSymlinks(this.req, this.res, done); - }); - }); - - return describe("with an error from fs.realpath", function() { - - beforeEach(function() { - return this.fs.realpath = sinon.stub().callsArgWith(1, "error"); - }); - - return it("should send a 500", function(done){ - this.res.sendStatus = function(resCode){ - resCode.should.equal(500); - return done(); - }; - return this.StaticServerForbidSymlinks(this.req, this.res); - }); - }); -}); + return describe('with an error from fs.realpath', function() { + beforeEach(function() { + return (this.fs.realpath = sinon.stub().callsArgWith(1, 'error')) + }) + return it('should send a 500', function(done) { + this.res.sendStatus = function(resCode) { + resCode.should.equal(500) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res) + }) + }) +}) diff --git a/services/clsi/test/unit/js/TikzManager.js b/services/clsi/test/unit/js/TikzManager.js index f35d261988..1a9874cbe0 100644 --- a/services/clsi/test/unit/js/TikzManager.js +++ b/services/clsi/test/unit/js/TikzManager.js @@ -8,148 +8,180 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const SandboxedModule = require('sandboxed-module'); -const sinon = require('sinon'); -require('chai').should(); -const modulePath = require('path').join(__dirname, '../../../app/js/TikzManager'); +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +require('chai').should() +const modulePath = require('path').join( + __dirname, + '../../../app/js/TikzManager' +) describe('TikzManager', function() { - beforeEach(function() { - return this.TikzManager = SandboxedModule.require(modulePath, { requires: { - "./ResourceWriter": (this.ResourceWriter = {}), - "./SafeReader": (this.SafeReader = {}), - "fs": (this.fs = {}), - "logger-sharelatex": (this.logger = {log() {}}) - } - });}); + beforeEach(function() { + return (this.TikzManager = SandboxedModule.require(modulePath, { + requires: { + './ResourceWriter': (this.ResourceWriter = {}), + './SafeReader': (this.SafeReader = {}), + fs: (this.fs = {}), + 'logger-sharelatex': (this.logger = { log() {} }) + } + })) + }) - describe("checkMainFile", function() { - beforeEach(function() { - this.compileDir = "compile-dir"; - this.mainFile = "main.tex"; - return this.callback = sinon.stub(); - }); + describe('checkMainFile', function() { + beforeEach(function() { + this.compileDir = 'compile-dir' + this.mainFile = 'main.tex' + return (this.callback = sinon.stub()) + }) - describe("if there is already an output.tex file in the resources", function() { - beforeEach(function() { - this.resources = [{path:"main.tex"},{path:"output.tex"}]; - return this.TikzManager.checkMainFile(this.compileDir, this.mainFile, this.resources, this.callback); - }); + describe('if there is already an output.tex file in the resources', function() { + beforeEach(function() { + this.resources = [{ path: 'main.tex' }, { path: 'output.tex' }] + return this.TikzManager.checkMainFile( + this.compileDir, + this.mainFile, + this.resources, + this.callback + ) + }) - return it("should call the callback with false ", function() { - return this.callback.calledWithExactly(null, false) - .should.equal(true); - }); - }); + return it('should call the callback with false ', function() { + return this.callback.calledWithExactly(null, false).should.equal(true) + }) + }) - return describe("if there is no output.tex file in the resources", function() { - beforeEach(function() { - this.resources = [{path:"main.tex"}]; - return this.ResourceWriter.checkPath = sinon.stub() - .withArgs(this.compileDir, this.mainFile) - .callsArgWith(2, null, `${this.compileDir}/${this.mainFile}`); - }); + return describe('if there is no output.tex file in the resources', function() { + beforeEach(function() { + this.resources = [{ path: 'main.tex' }] + return (this.ResourceWriter.checkPath = sinon + .stub() + .withArgs(this.compileDir, this.mainFile) + .callsArgWith(2, null, `${this.compileDir}/${this.mainFile}`)) + }) - describe("and the main file contains tikzexternalize", function() { - beforeEach(function() { - this.SafeReader.readFile = sinon.stub() - .withArgs(`${this.compileDir}/${this.mainFile}`) - .callsArgWith(3, null, "hello \\tikzexternalize"); - return this.TikzManager.checkMainFile(this.compileDir, this.mainFile, this.resources, this.callback); - }); + describe('and the main file contains tikzexternalize', function() { + beforeEach(function() { + this.SafeReader.readFile = sinon + .stub() + .withArgs(`${this.compileDir}/${this.mainFile}`) + .callsArgWith(3, null, 'hello \\tikzexternalize') + return this.TikzManager.checkMainFile( + this.compileDir, + this.mainFile, + this.resources, + this.callback + ) + }) - it("should look at the file on disk", function() { - return this.SafeReader.readFile - .calledWith(`${this.compileDir}/${this.mainFile}`) - .should.equal(true); - }); + it('should look at the file on disk', function() { + return this.SafeReader.readFile + .calledWith(`${this.compileDir}/${this.mainFile}`) + .should.equal(true) + }) - return it("should call the callback with true ", function() { - return this.callback.calledWithExactly(null, true) - .should.equal(true); - }); - }); + return it('should call the callback with true ', function() { + return this.callback.calledWithExactly(null, true).should.equal(true) + }) + }) - describe("and the main file does not contain tikzexternalize", function() { - beforeEach(function() { - this.SafeReader.readFile = sinon.stub() - .withArgs(`${this.compileDir}/${this.mainFile}`) - .callsArgWith(3, null, "hello"); - return this.TikzManager.checkMainFile(this.compileDir, this.mainFile, this.resources, this.callback); - }); + describe('and the main file does not contain tikzexternalize', function() { + beforeEach(function() { + this.SafeReader.readFile = sinon + .stub() + .withArgs(`${this.compileDir}/${this.mainFile}`) + .callsArgWith(3, null, 'hello') + return this.TikzManager.checkMainFile( + this.compileDir, + this.mainFile, + this.resources, + this.callback + ) + }) - it("should look at the file on disk", function() { - return this.SafeReader.readFile - .calledWith(`${this.compileDir}/${this.mainFile}`) - .should.equal(true); - }); + it('should look at the file on disk', function() { + return this.SafeReader.readFile + .calledWith(`${this.compileDir}/${this.mainFile}`) + .should.equal(true) + }) - return it("should call the callback with false", function() { - return this.callback.calledWithExactly(null, false) - .should.equal(true); - }); - }); + return it('should call the callback with false', function() { + return this.callback.calledWithExactly(null, false).should.equal(true) + }) + }) - return describe("and the main file contains \\usepackage{pstool}", function() { - beforeEach(function() { - this.SafeReader.readFile = sinon.stub() - .withArgs(`${this.compileDir}/${this.mainFile}`) - .callsArgWith(3, null, "hello \\usepackage[random-options]{pstool}"); - return this.TikzManager.checkMainFile(this.compileDir, this.mainFile, this.resources, this.callback); - }); + return describe('and the main file contains \\usepackage{pstool}', function() { + beforeEach(function() { + this.SafeReader.readFile = sinon + .stub() + .withArgs(`${this.compileDir}/${this.mainFile}`) + .callsArgWith(3, null, 'hello \\usepackage[random-options]{pstool}') + return this.TikzManager.checkMainFile( + this.compileDir, + this.mainFile, + this.resources, + this.callback + ) + }) - it("should look at the file on disk", function() { - return this.SafeReader.readFile - .calledWith(`${this.compileDir}/${this.mainFile}`) - .should.equal(true); - }); + it('should look at the file on disk', function() { + return this.SafeReader.readFile + .calledWith(`${this.compileDir}/${this.mainFile}`) + .should.equal(true) + }) - return it("should call the callback with true ", function() { - return this.callback.calledWithExactly(null, true) - .should.equal(true); - }); - }); - }); - }); + return it('should call the callback with true ', function() { + return this.callback.calledWithExactly(null, true).should.equal(true) + }) + }) + }) + }) - return describe("injectOutputFile", function() { - beforeEach(function() { - this.rootDir = "/mock"; - this.filename = "filename.tex"; - this.callback = sinon.stub(); - this.content = `\ + return describe('injectOutputFile', function() { + beforeEach(function() { + this.rootDir = '/mock' + this.filename = 'filename.tex' + this.callback = sinon.stub() + this.content = `\ \\documentclass{article} \\usepackage{tikz} \\tikzexternalize \\begin{document} Hello world \\end{document}\ -`; - this.fs.readFile = sinon.stub().callsArgWith(2, null, this.content); - this.fs.writeFile = sinon.stub().callsArg(3); - this.ResourceWriter.checkPath = sinon.stub().callsArgWith(2, null, `${this.rootDir}/${this.filename}`); - return this.TikzManager.injectOutputFile(this.rootDir, this.filename, this.callback); - }); +` + this.fs.readFile = sinon.stub().callsArgWith(2, null, this.content) + this.fs.writeFile = sinon.stub().callsArg(3) + this.ResourceWriter.checkPath = sinon + .stub() + .callsArgWith(2, null, `${this.rootDir}/${this.filename}`) + return this.TikzManager.injectOutputFile( + this.rootDir, + this.filename, + this.callback + ) + }) - it("sould check the path", function() { - return this.ResourceWriter.checkPath.calledWith(this.rootDir, this.filename) - .should.equal(true); - }); + it('sould check the path', function() { + return this.ResourceWriter.checkPath + .calledWith(this.rootDir, this.filename) + .should.equal(true) + }) - it("should read the file", function() { - return this.fs.readFile - .calledWith(`${this.rootDir}/${this.filename}`, "utf8") - .should.equal(true); - }); + it('should read the file', function() { + return this.fs.readFile + .calledWith(`${this.rootDir}/${this.filename}`, 'utf8') + .should.equal(true) + }) - it("should write out the same file as output.tex", function() { - return this.fs.writeFile - .calledWith(`${this.rootDir}/output.tex`, this.content, {flag: 'wx'}) - .should.equal(true); - }); + it('should write out the same file as output.tex', function() { + return this.fs.writeFile + .calledWith(`${this.rootDir}/output.tex`, this.content, { flag: 'wx' }) + .should.equal(true) + }) - return it("should call the callback", function() { - return this.callback.called.should.equal(true); - }); - }); -}); + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) +}) diff --git a/services/clsi/test/unit/js/UrlCacheTests.js b/services/clsi/test/unit/js/UrlCacheTests.js index 7f024507f2..f056a6eb22 100644 --- a/services/clsi/test/unit/js/UrlCacheTests.js +++ b/services/clsi/test/unit/js/UrlCacheTests.js @@ -10,259 +10,347 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const SandboxedModule = require('sandboxed-module'); -const sinon = require('sinon'); -require('chai').should(); -const modulePath = require('path').join(__dirname, '../../../app/js/UrlCache'); -const { EventEmitter } = require("events"); +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +require('chai').should() +const modulePath = require('path').join(__dirname, '../../../app/js/UrlCache') +const { EventEmitter } = require('events') -describe("UrlCache", function() { - beforeEach(function() { - this.callback = sinon.stub(); - this.url = "www.example.com/file"; - this.project_id = "project-id-123"; - return this.UrlCache = SandboxedModule.require(modulePath, { requires: { - "./db" : {}, - "./UrlFetcher" : (this.UrlFetcher = {}), - "logger-sharelatex": (this.logger = {log: sinon.stub()}), - "settings-sharelatex": (this.Settings = { path: {clsiCacheDir: "/cache/dir"} }), - "fs": (this.fs = {}) - } - });}); - - describe("_doesUrlNeedDownloading", function() { - beforeEach(function() { - this.lastModified = new Date(); - return this.lastModifiedRoundedToSeconds = new Date(Math.floor(this.lastModified.getTime() / 1000) * 1000); - }); +describe('UrlCache', function() { + beforeEach(function() { + this.callback = sinon.stub() + this.url = 'www.example.com/file' + this.project_id = 'project-id-123' + return (this.UrlCache = SandboxedModule.require(modulePath, { + requires: { + './db': {}, + './UrlFetcher': (this.UrlFetcher = {}), + 'logger-sharelatex': (this.logger = { log: sinon.stub() }), + 'settings-sharelatex': (this.Settings = { + path: { clsiCacheDir: '/cache/dir' } + }), + fs: (this.fs = {}) + } + })) + }) - describe("when URL does not exist in cache", function() { - beforeEach(function() { - this.UrlCache._findUrlDetails = sinon.stub().callsArgWith(2, null, null); - return this.UrlCache._doesUrlNeedDownloading(this.project_id, this.url, this.lastModified, this.callback); - }); + describe('_doesUrlNeedDownloading', function() { + beforeEach(function() { + this.lastModified = new Date() + return (this.lastModifiedRoundedToSeconds = new Date( + Math.floor(this.lastModified.getTime() / 1000) * 1000 + )) + }) - return it("should return the callback with true", function() { - return this.callback.calledWith(null, true).should.equal(true); - }); - }); + describe('when URL does not exist in cache', function() { + beforeEach(function() { + this.UrlCache._findUrlDetails = sinon.stub().callsArgWith(2, null, null) + return this.UrlCache._doesUrlNeedDownloading( + this.project_id, + this.url, + this.lastModified, + this.callback + ) + }) - return describe("when URL does exist in cache", function() { - beforeEach(function() { - this.urlDetails = {}; - return this.UrlCache._findUrlDetails = sinon.stub().callsArgWith(2, null, this.urlDetails); - }); + return it('should return the callback with true', function() { + return this.callback.calledWith(null, true).should.equal(true) + }) + }) - describe("when the modified date is more recent than the cached modified date", function() { - beforeEach(function() { - this.urlDetails.lastModified = new Date(this.lastModified.getTime() - 1000); - return this.UrlCache._doesUrlNeedDownloading(this.project_id, this.url, this.lastModified, this.callback); - }); + return describe('when URL does exist in cache', function() { + beforeEach(function() { + this.urlDetails = {} + return (this.UrlCache._findUrlDetails = sinon + .stub() + .callsArgWith(2, null, this.urlDetails)) + }) - it("should get the url details", function() { - return this.UrlCache._findUrlDetails - .calledWith(this.project_id, this.url) - .should.equal(true); - }); + describe('when the modified date is more recent than the cached modified date', function() { + beforeEach(function() { + this.urlDetails.lastModified = new Date( + this.lastModified.getTime() - 1000 + ) + return this.UrlCache._doesUrlNeedDownloading( + this.project_id, + this.url, + this.lastModified, + this.callback + ) + }) - return it("should return the callback with true", function() { - return this.callback.calledWith(null, true).should.equal(true); - }); - }); + it('should get the url details', function() { + return this.UrlCache._findUrlDetails + .calledWith(this.project_id, this.url) + .should.equal(true) + }) - describe("when the cached modified date is more recent than the modified date", function() { - beforeEach(function() { - this.urlDetails.lastModified = new Date(this.lastModified.getTime() + 1000); - return this.UrlCache._doesUrlNeedDownloading(this.project_id, this.url, this.lastModified, this.callback); - }); + return it('should return the callback with true', function() { + return this.callback.calledWith(null, true).should.equal(true) + }) + }) - return it("should return the callback with false", function() { - return this.callback.calledWith(null, false).should.equal(true); - }); - }); + describe('when the cached modified date is more recent than the modified date', function() { + beforeEach(function() { + this.urlDetails.lastModified = new Date( + this.lastModified.getTime() + 1000 + ) + return this.UrlCache._doesUrlNeedDownloading( + this.project_id, + this.url, + this.lastModified, + this.callback + ) + }) - describe("when the cached modified date is equal to the modified date", function() { - beforeEach(function() { - this.urlDetails.lastModified = this.lastModified; - return this.UrlCache._doesUrlNeedDownloading(this.project_id, this.url, this.lastModified, this.callback); - }); + return it('should return the callback with false', function() { + return this.callback.calledWith(null, false).should.equal(true) + }) + }) - return it("should return the callback with false", function() { - return this.callback.calledWith(null, false).should.equal(true); - }); - }); + describe('when the cached modified date is equal to the modified date', function() { + beforeEach(function() { + this.urlDetails.lastModified = this.lastModified + return this.UrlCache._doesUrlNeedDownloading( + this.project_id, + this.url, + this.lastModified, + this.callback + ) + }) - describe("when the provided modified date does not exist", function() { - beforeEach(function() { - this.lastModified = null; - return this.UrlCache._doesUrlNeedDownloading(this.project_id, this.url, this.lastModified, this.callback); - }); + return it('should return the callback with false', function() { + return this.callback.calledWith(null, false).should.equal(true) + }) + }) - return it("should return the callback with true", function() { - return this.callback.calledWith(null, true).should.equal(true); - }); - }); + describe('when the provided modified date does not exist', function() { + beforeEach(function() { + this.lastModified = null + return this.UrlCache._doesUrlNeedDownloading( + this.project_id, + this.url, + this.lastModified, + this.callback + ) + }) - return describe("when the URL does not have a modified date", function() { - beforeEach(function() { - this.urlDetails.lastModified = null; - return this.UrlCache._doesUrlNeedDownloading(this.project_id, this.url, this.lastModified, this.callback); - }); + return it('should return the callback with true', function() { + return this.callback.calledWith(null, true).should.equal(true) + }) + }) - return it("should return the callback with true", function() { - return this.callback.calledWith(null, true).should.equal(true); - }); - }); - }); - }); + return describe('when the URL does not have a modified date', function() { + beforeEach(function() { + this.urlDetails.lastModified = null + return this.UrlCache._doesUrlNeedDownloading( + this.project_id, + this.url, + this.lastModified, + this.callback + ) + }) - describe("_ensureUrlIsInCache", function() { - beforeEach(function() { - this.UrlFetcher.pipeUrlToFile = sinon.stub().callsArg(2); - return this.UrlCache._updateOrCreateUrlDetails = sinon.stub().callsArg(3); - }); - - describe("when the URL needs updating", function() { - beforeEach(function() { - this.UrlCache._doesUrlNeedDownloading = sinon.stub().callsArgWith(3, null, true); - return this.UrlCache._ensureUrlIsInCache(this.project_id, this.url, this.lastModified, this.callback); - }); + return it('should return the callback with true', function() { + return this.callback.calledWith(null, true).should.equal(true) + }) + }) + }) + }) - it("should check that the url needs downloading", function() { - return this.UrlCache._doesUrlNeedDownloading - .calledWith(this.project_id, this.url, this.lastModifiedRoundedToSeconds) - .should.equal(true); - }); + describe('_ensureUrlIsInCache', function() { + beforeEach(function() { + this.UrlFetcher.pipeUrlToFile = sinon.stub().callsArg(2) + return (this.UrlCache._updateOrCreateUrlDetails = sinon + .stub() + .callsArg(3)) + }) - it("should download the URL to the cache file", function() { - return this.UrlFetcher.pipeUrlToFile - .calledWith(this.url, this.UrlCache._cacheFilePathForUrl(this.project_id, this.url)) - .should.equal(true); - }); - + describe('when the URL needs updating', function() { + beforeEach(function() { + this.UrlCache._doesUrlNeedDownloading = sinon + .stub() + .callsArgWith(3, null, true) + return this.UrlCache._ensureUrlIsInCache( + this.project_id, + this.url, + this.lastModified, + this.callback + ) + }) - it("should update the database entry", function() { - return this.UrlCache._updateOrCreateUrlDetails - .calledWith(this.project_id, this.url, this.lastModifiedRoundedToSeconds) - .should.equal(true); - }); + it('should check that the url needs downloading', function() { + return this.UrlCache._doesUrlNeedDownloading + .calledWith( + this.project_id, + this.url, + this.lastModifiedRoundedToSeconds + ) + .should.equal(true) + }) - return it("should return the callback with the cache file path", function() { - return this.callback - .calledWith(null, this.UrlCache._cacheFilePathForUrl(this.project_id, this.url)) - .should.equal(true); - }); - }); + it('should download the URL to the cache file', function() { + return this.UrlFetcher.pipeUrlToFile + .calledWith( + this.url, + this.UrlCache._cacheFilePathForUrl(this.project_id, this.url) + ) + .should.equal(true) + }) - return describe("when the URL does not need updating", function() { - beforeEach(function() { - this.UrlCache._doesUrlNeedDownloading = sinon.stub().callsArgWith(3, null, false); - return this.UrlCache._ensureUrlIsInCache(this.project_id, this.url, this.lastModified, this.callback); - }); - - it("should not download the URL to the cache file", function() { - return this.UrlFetcher.pipeUrlToFile - .called.should.equal(false); - }); + it('should update the database entry', function() { + return this.UrlCache._updateOrCreateUrlDetails + .calledWith( + this.project_id, + this.url, + this.lastModifiedRoundedToSeconds + ) + .should.equal(true) + }) - return it("should return the callback with the cache file path", function() { - return this.callback - .calledWith(null, this.UrlCache._cacheFilePathForUrl(this.project_id, this.url)) - .should.equal(true); - }); - }); - }); + return it('should return the callback with the cache file path', function() { + return this.callback + .calledWith( + null, + this.UrlCache._cacheFilePathForUrl(this.project_id, this.url) + ) + .should.equal(true) + }) + }) - describe("downloadUrlToFile", function() { - beforeEach(function() { - this.cachePath = "path/to/cached/url"; - this.destPath = "path/to/destination"; - this.UrlCache._copyFile = sinon.stub().callsArg(2); - this.UrlCache._ensureUrlIsInCache = sinon.stub().callsArgWith(3, null, this.cachePath); - return this.UrlCache.downloadUrlToFile(this.project_id, this.url, this.destPath, this.lastModified, this.callback); - }); + return describe('when the URL does not need updating', function() { + beforeEach(function() { + this.UrlCache._doesUrlNeedDownloading = sinon + .stub() + .callsArgWith(3, null, false) + return this.UrlCache._ensureUrlIsInCache( + this.project_id, + this.url, + this.lastModified, + this.callback + ) + }) - it("should ensure the URL is downloaded and updated in the cache", function() { - return this.UrlCache._ensureUrlIsInCache - .calledWith(this.project_id, this.url, this.lastModified) - .should.equal(true); - }); + it('should not download the URL to the cache file', function() { + return this.UrlFetcher.pipeUrlToFile.called.should.equal(false) + }) - it("should copy the file to the new location", function() { - return this.UrlCache._copyFile - .calledWith(this.cachePath, this.destPath) - .should.equal(true); - }); + return it('should return the callback with the cache file path', function() { + return this.callback + .calledWith( + null, + this.UrlCache._cacheFilePathForUrl(this.project_id, this.url) + ) + .should.equal(true) + }) + }) + }) - return it("should call the callback", function() { - return this.callback.called.should.equal(true); - }); - }); + describe('downloadUrlToFile', function() { + beforeEach(function() { + this.cachePath = 'path/to/cached/url' + this.destPath = 'path/to/destination' + this.UrlCache._copyFile = sinon.stub().callsArg(2) + this.UrlCache._ensureUrlIsInCache = sinon + .stub() + .callsArgWith(3, null, this.cachePath) + return this.UrlCache.downloadUrlToFile( + this.project_id, + this.url, + this.destPath, + this.lastModified, + this.callback + ) + }) - describe("_deleteUrlCacheFromDisk", function() { - beforeEach(function() { - this.fs.unlink = sinon.stub().callsArg(1); - return this.UrlCache._deleteUrlCacheFromDisk(this.project_id, this.url, this.callback); - }); + it('should ensure the URL is downloaded and updated in the cache', function() { + return this.UrlCache._ensureUrlIsInCache + .calledWith(this.project_id, this.url, this.lastModified) + .should.equal(true) + }) - it("should delete the cache file", function() { - return this.fs.unlink - .calledWith(this.UrlCache._cacheFilePathForUrl(this.project_id, this.url)) - .should.equal(true); - }); + it('should copy the file to the new location', function() { + return this.UrlCache._copyFile + .calledWith(this.cachePath, this.destPath) + .should.equal(true) + }) - return it("should call the callback", function() { - return this.callback.called.should.equal(true); - }); - }); + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) - describe("_clearUrlFromCache", function() { - beforeEach(function() { - this.UrlCache._deleteUrlCacheFromDisk = sinon.stub().callsArg(2); - this.UrlCache._clearUrlDetails = sinon.stub().callsArg(2); - return this.UrlCache._clearUrlFromCache(this.project_id, this.url, this.callback); - }); + describe('_deleteUrlCacheFromDisk', function() { + beforeEach(function() { + this.fs.unlink = sinon.stub().callsArg(1) + return this.UrlCache._deleteUrlCacheFromDisk( + this.project_id, + this.url, + this.callback + ) + }) - it("should delete the file on disk", function() { - return this.UrlCache._deleteUrlCacheFromDisk - .calledWith(this.project_id, this.url) - .should.equal(true); - }); + it('should delete the cache file', function() { + return this.fs.unlink + .calledWith( + this.UrlCache._cacheFilePathForUrl(this.project_id, this.url) + ) + .should.equal(true) + }) - it("should clear the entry in the database", function() { - return this.UrlCache._clearUrlDetails - .calledWith(this.project_id, this.url) - .should.equal(true); - }); + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) - return it("should call the callback", function() { - return this.callback.called.should.equal(true); - }); - }); + describe('_clearUrlFromCache', function() { + beforeEach(function() { + this.UrlCache._deleteUrlCacheFromDisk = sinon.stub().callsArg(2) + this.UrlCache._clearUrlDetails = sinon.stub().callsArg(2) + return this.UrlCache._clearUrlFromCache( + this.project_id, + this.url, + this.callback + ) + }) - return describe("clearProject", function() { - beforeEach(function() { - this.urls = [ - "www.example.com/file1", - "www.example.com/file2" - ]; - this.UrlCache._findAllUrlsInProject = sinon.stub().callsArgWith(1, null, this.urls); - this.UrlCache._clearUrlFromCache = sinon.stub().callsArg(2); - return this.UrlCache.clearProject(this.project_id, this.callback); - }); + it('should delete the file on disk', function() { + return this.UrlCache._deleteUrlCacheFromDisk + .calledWith(this.project_id, this.url) + .should.equal(true) + }) - it("should clear the cache for each url in the project", function() { - return Array.from(this.urls).map((url) => - this.UrlCache._clearUrlFromCache - .calledWith(this.project_id, url) - .should.equal(true)); - }); + it('should clear the entry in the database', function() { + return this.UrlCache._clearUrlDetails + .calledWith(this.project_id, this.url) + .should.equal(true) + }) - return it("should call the callback", function() { - return this.callback.called.should.equal(true); - }); - }); -}); - - + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) + return describe('clearProject', function() { + beforeEach(function() { + this.urls = ['www.example.com/file1', 'www.example.com/file2'] + this.UrlCache._findAllUrlsInProject = sinon + .stub() + .callsArgWith(1, null, this.urls) + this.UrlCache._clearUrlFromCache = sinon.stub().callsArg(2) + return this.UrlCache.clearProject(this.project_id, this.callback) + }) + + it('should clear the cache for each url in the project', function() { + return Array.from(this.urls).map(url => + this.UrlCache._clearUrlFromCache + .calledWith(this.project_id, url) + .should.equal(true) + ) + }) + + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) +}) diff --git a/services/clsi/test/unit/js/UrlFetcherTests.js b/services/clsi/test/unit/js/UrlFetcherTests.js index 453a386755..e5ce52b95a 100644 --- a/services/clsi/test/unit/js/UrlFetcherTests.js +++ b/services/clsi/test/unit/js/UrlFetcherTests.js @@ -8,152 +8,165 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const SandboxedModule = require('sandboxed-module'); -const sinon = require('sinon'); -require('chai').should(); -const modulePath = require('path').join(__dirname, '../../../app/js/UrlFetcher'); -const { EventEmitter } = require("events"); +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +require('chai').should() +const modulePath = require('path').join(__dirname, '../../../app/js/UrlFetcher') +const { EventEmitter } = require('events') -describe("UrlFetcher", function() { - beforeEach(function() { - this.callback = sinon.stub(); - this.url = "https://www.example.com/file/here?query=string"; - return this.UrlFetcher = SandboxedModule.require(modulePath, { requires: { - request: { defaults: (this.defaults = sinon.stub().returns(this.request = {})) - }, - fs: (this.fs = {}), - "logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub() }), - "settings-sharelatex": (this.settings = {}) - } - });}); +describe('UrlFetcher', function() { + beforeEach(function() { + this.callback = sinon.stub() + this.url = 'https://www.example.com/file/here?query=string' + return (this.UrlFetcher = SandboxedModule.require(modulePath, { + requires: { + request: { + defaults: (this.defaults = sinon.stub().returns((this.request = {}))) + }, + fs: (this.fs = {}), + 'logger-sharelatex': (this.logger = { + log: sinon.stub(), + error: sinon.stub() + }), + 'settings-sharelatex': (this.settings = {}) + } + })) + }) - it("should turn off the cookie jar in request", function() { - return this.defaults.calledWith({jar: false}) - .should.equal(true); - }); + it('should turn off the cookie jar in request', function() { + return this.defaults.calledWith({ jar: false }).should.equal(true) + }) - describe("rewrite url domain if filestoreDomainOveride is set", function() { - beforeEach(function() { - this.path = "/path/to/file/on/disk"; - this.request.get = sinon.stub().returns(this.urlStream = new EventEmitter); - this.urlStream.pipe = sinon.stub(); - this.urlStream.pause = sinon.stub(); - this.urlStream.resume = sinon.stub(); - this.fs.createWriteStream = sinon.stub().returns(this.fileStream = new EventEmitter); - return this.fs.unlink = (file, callback) => callback(); - }); + describe('rewrite url domain if filestoreDomainOveride is set', function() { + beforeEach(function() { + this.path = '/path/to/file/on/disk' + this.request.get = sinon + .stub() + .returns((this.urlStream = new EventEmitter())) + this.urlStream.pipe = sinon.stub() + this.urlStream.pause = sinon.stub() + this.urlStream.resume = sinon.stub() + this.fs.createWriteStream = sinon + .stub() + .returns((this.fileStream = new EventEmitter())) + return (this.fs.unlink = (file, callback) => callback()) + }) - it("should use the normal domain when override not set", function(done){ - this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { - this.request.get.args[0][0].url.should.equal(this.url); - return done(); - }); - this.res = {statusCode: 200}; - this.urlStream.emit("response", this.res); - this.urlStream.emit("end"); - return this.fileStream.emit("finish"); - }); + it('should use the normal domain when override not set', function(done) { + this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { + this.request.get.args[0][0].url.should.equal(this.url) + return done() + }) + this.res = { statusCode: 200 } + this.urlStream.emit('response', this.res) + this.urlStream.emit('end') + return this.fileStream.emit('finish') + }) + return it('should use override domain when filestoreDomainOveride is set', function(done) { + this.settings.filestoreDomainOveride = '192.11.11.11' + this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { + this.request.get.args[0][0].url.should.equal( + '192.11.11.11/file/here?query=string' + ) + return done() + }) + this.res = { statusCode: 200 } + this.urlStream.emit('response', this.res) + this.urlStream.emit('end') + return this.fileStream.emit('finish') + }) + }) - return it("should use override domain when filestoreDomainOveride is set", function(done){ - this.settings.filestoreDomainOveride = "192.11.11.11"; - this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { - this.request.get.args[0][0].url.should.equal("192.11.11.11/file/here?query=string"); - return done(); - }); - this.res = {statusCode: 200}; - this.urlStream.emit("response", this.res); - this.urlStream.emit("end"); - return this.fileStream.emit("finish"); - }); - }); + return describe('pipeUrlToFile', function() { + beforeEach(function(done) { + this.path = '/path/to/file/on/disk' + this.request.get = sinon + .stub() + .returns((this.urlStream = new EventEmitter())) + this.urlStream.pipe = sinon.stub() + this.urlStream.pause = sinon.stub() + this.urlStream.resume = sinon.stub() + this.fs.createWriteStream = sinon + .stub() + .returns((this.fileStream = new EventEmitter())) + this.fs.unlink = (file, callback) => callback() + return done() + }) - return describe("pipeUrlToFile", function() { - beforeEach(function(done){ - this.path = "/path/to/file/on/disk"; - this.request.get = sinon.stub().returns(this.urlStream = new EventEmitter); - this.urlStream.pipe = sinon.stub(); - this.urlStream.pause = sinon.stub(); - this.urlStream.resume = sinon.stub(); - this.fs.createWriteStream = sinon.stub().returns(this.fileStream = new EventEmitter); - this.fs.unlink = (file, callback) => callback(); - return done(); - }); + describe('successfully', function() { + beforeEach(function(done) { + this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { + this.callback() + return done() + }) + this.res = { statusCode: 200 } + this.urlStream.emit('response', this.res) + this.urlStream.emit('end') + return this.fileStream.emit('finish') + }) - describe("successfully", function() { - beforeEach(function(done){ - this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { - this.callback(); - return done(); - }); - this.res = {statusCode: 200}; - this.urlStream.emit("response", this.res); - this.urlStream.emit("end"); - return this.fileStream.emit("finish"); - }); + it('should request the URL', function() { + return this.request.get + .calledWith(sinon.match({ url: this.url })) + .should.equal(true) + }) + it('should open the file for writing', function() { + return this.fs.createWriteStream + .calledWith(this.path) + .should.equal(true) + }) - it("should request the URL", function() { - return this.request.get - .calledWith(sinon.match({"url": this.url})) - .should.equal(true); - }); + it('should pipe the URL to the file', function() { + return this.urlStream.pipe + .calledWith(this.fileStream) + .should.equal(true) + }) - it("should open the file for writing", function() { - return this.fs.createWriteStream - .calledWith(this.path) - .should.equal(true); - }); + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) - it("should pipe the URL to the file", function() { - return this.urlStream.pipe - .calledWith(this.fileStream) - .should.equal(true); - }); - - return it("should call the callback", function() { - return this.callback.called.should.equal(true); - }); - }); + describe('with non success status code', function() { + beforeEach(function(done) { + this.UrlFetcher.pipeUrlToFile(this.url, this.path, err => { + this.callback(err) + return done() + }) + this.res = { statusCode: 404 } + this.urlStream.emit('response', this.res) + return this.urlStream.emit('end') + }) - describe("with non success status code", function() { - beforeEach(function(done){ - this.UrlFetcher.pipeUrlToFile(this.url, this.path, err=> { - this.callback(err); - return done(); - }); - this.res = {statusCode: 404}; - this.urlStream.emit("response", this.res); - return this.urlStream.emit("end"); - }); + return it('should call the callback with an error', function() { + return this.callback + .calledWith(new Error('URL returned non-success status code: 404')) + .should.equal(true) + }) + }) - return it("should call the callback with an error", function() { - return this.callback - .calledWith(new Error("URL returned non-success status code: 404")) - .should.equal(true); - }); - }); + return describe('with error', function() { + beforeEach(function(done) { + this.UrlFetcher.pipeUrlToFile(this.url, this.path, err => { + this.callback(err) + return done() + }) + return this.urlStream.emit( + 'error', + (this.error = new Error('something went wrong')) + ) + }) - return describe("with error", function() { - beforeEach(function(done){ - this.UrlFetcher.pipeUrlToFile(this.url, this.path, err=> { - this.callback(err); - return done(); - }); - return this.urlStream.emit("error", (this.error = new Error("something went wrong"))); - }); - - it("should call the callback with the error", function() { - return this.callback - .calledWith(this.error) - .should.equal(true); - }); - - return it("should only call the callback once, even if end is called", function() { - this.urlStream.emit("end"); - return this.callback.calledOnce.should.equal(true); - }); - }); - }); -}); + it('should call the callback with the error', function() { + return this.callback.calledWith(this.error).should.equal(true) + }) + return it('should only call the callback once, even if end is called', function() { + this.urlStream.emit('end') + return this.callback.calledOnce.should.equal(true) + }) + }) + }) +}) From 035786b20453ddee268f10db94c6874b601c4119 Mon Sep 17 00:00:00 2001 From: decaffeinate <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:15:54 +0100 Subject: [PATCH 526/754] decaffeinate: Rename BrokenLatexFileTests.coffee and 9 other files from .coffee to .js --- .../{BrokenLatexFileTests.coffee => BrokenLatexFileTests.js} | 0 .../coffee/{DeleteOldFilesTest.coffee => DeleteOldFilesTest.js} | 0 .../{ExampleDocumentTests.coffee => ExampleDocumentTests.js} | 0 .../{SimpleLatexFileTests.coffee => SimpleLatexFileTests.js} | 0 .../acceptance/coffee/{SynctexTests.coffee => SynctexTests.js} | 0 .../acceptance/coffee/{TimeoutTests.coffee => TimeoutTests.js} | 0 .../coffee/{UrlCachingTests.coffee => UrlCachingTests.js} | 0 .../coffee/{WordcountTests.coffee => WordcountTests.js} | 0 .../test/acceptance/coffee/helpers/{Client.coffee => Client.js} | 0 .../test/acceptance/coffee/helpers/{ClsiApp.coffee => ClsiApp.js} | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename services/clsi/test/acceptance/coffee/{BrokenLatexFileTests.coffee => BrokenLatexFileTests.js} (100%) rename services/clsi/test/acceptance/coffee/{DeleteOldFilesTest.coffee => DeleteOldFilesTest.js} (100%) rename services/clsi/test/acceptance/coffee/{ExampleDocumentTests.coffee => ExampleDocumentTests.js} (100%) rename services/clsi/test/acceptance/coffee/{SimpleLatexFileTests.coffee => SimpleLatexFileTests.js} (100%) rename services/clsi/test/acceptance/coffee/{SynctexTests.coffee => SynctexTests.js} (100%) rename services/clsi/test/acceptance/coffee/{TimeoutTests.coffee => TimeoutTests.js} (100%) rename services/clsi/test/acceptance/coffee/{UrlCachingTests.coffee => UrlCachingTests.js} (100%) rename services/clsi/test/acceptance/coffee/{WordcountTests.coffee => WordcountTests.js} (100%) rename services/clsi/test/acceptance/coffee/helpers/{Client.coffee => Client.js} (100%) rename services/clsi/test/acceptance/coffee/helpers/{ClsiApp.coffee => ClsiApp.js} (100%) diff --git a/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.coffee b/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.js similarity index 100% rename from services/clsi/test/acceptance/coffee/BrokenLatexFileTests.coffee rename to services/clsi/test/acceptance/coffee/BrokenLatexFileTests.js diff --git a/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.coffee b/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.js similarity index 100% rename from services/clsi/test/acceptance/coffee/DeleteOldFilesTest.coffee rename to services/clsi/test/acceptance/coffee/DeleteOldFilesTest.js diff --git a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.js similarity index 100% rename from services/clsi/test/acceptance/coffee/ExampleDocumentTests.coffee rename to services/clsi/test/acceptance/coffee/ExampleDocumentTests.js diff --git a/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee b/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.js similarity index 100% rename from services/clsi/test/acceptance/coffee/SimpleLatexFileTests.coffee rename to services/clsi/test/acceptance/coffee/SimpleLatexFileTests.js diff --git a/services/clsi/test/acceptance/coffee/SynctexTests.coffee b/services/clsi/test/acceptance/coffee/SynctexTests.js similarity index 100% rename from services/clsi/test/acceptance/coffee/SynctexTests.coffee rename to services/clsi/test/acceptance/coffee/SynctexTests.js diff --git a/services/clsi/test/acceptance/coffee/TimeoutTests.coffee b/services/clsi/test/acceptance/coffee/TimeoutTests.js similarity index 100% rename from services/clsi/test/acceptance/coffee/TimeoutTests.coffee rename to services/clsi/test/acceptance/coffee/TimeoutTests.js diff --git a/services/clsi/test/acceptance/coffee/UrlCachingTests.coffee b/services/clsi/test/acceptance/coffee/UrlCachingTests.js similarity index 100% rename from services/clsi/test/acceptance/coffee/UrlCachingTests.coffee rename to services/clsi/test/acceptance/coffee/UrlCachingTests.js diff --git a/services/clsi/test/acceptance/coffee/WordcountTests.coffee b/services/clsi/test/acceptance/coffee/WordcountTests.js similarity index 100% rename from services/clsi/test/acceptance/coffee/WordcountTests.coffee rename to services/clsi/test/acceptance/coffee/WordcountTests.js diff --git a/services/clsi/test/acceptance/coffee/helpers/Client.coffee b/services/clsi/test/acceptance/coffee/helpers/Client.js similarity index 100% rename from services/clsi/test/acceptance/coffee/helpers/Client.coffee rename to services/clsi/test/acceptance/coffee/helpers/Client.js diff --git a/services/clsi/test/acceptance/coffee/helpers/ClsiApp.coffee b/services/clsi/test/acceptance/coffee/helpers/ClsiApp.js similarity index 100% rename from services/clsi/test/acceptance/coffee/helpers/ClsiApp.coffee rename to services/clsi/test/acceptance/coffee/helpers/ClsiApp.js From 3d3861cb244d58745ba59dd63443e86ae040705a Mon Sep 17 00:00:00 2001 From: decaffeinate <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:16:00 +0100 Subject: [PATCH 527/754] decaffeinate: Convert BrokenLatexFileTests.coffee and 9 other files to JS --- .../acceptance/coffee/BrokenLatexFileTests.js | 100 +++-- .../acceptance/coffee/DeleteOldFilesTest.js | 77 ++-- .../acceptance/coffee/ExampleDocumentTests.js | 281 +++++++----- .../acceptance/coffee/SimpleLatexFileTests.js | 84 ++-- .../test/acceptance/coffee/SynctexTests.js | 87 ++-- .../test/acceptance/coffee/TimeoutTests.js | 70 +-- .../test/acceptance/coffee/UrlCachingTests.js | 418 ++++++++++-------- .../test/acceptance/coffee/WordcountTests.js | 76 ++-- .../test/acceptance/coffee/helpers/Client.js | 230 ++++++---- .../test/acceptance/coffee/helpers/ClsiApp.js | 66 ++- 10 files changed, 883 insertions(+), 606 deletions(-) diff --git a/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.js b/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.js index 8ab4344f5d..5aea625778 100644 --- a/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.js +++ b/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.js @@ -1,48 +1,70 @@ -Client = require "./helpers/Client" -request = require "request" -require("chai").should() -ClsiApp = require "./helpers/ClsiApp" +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const Client = require("./helpers/Client"); +const request = require("request"); +require("chai").should(); +const ClsiApp = require("./helpers/ClsiApp"); -describe "Broken LaTeX file", -> - before (done)-> - @broken_request = - resources: [ - path: "main.tex" - content: ''' - \\documentclass{articl % :( - \\begin{documen % :( - Broken - \\end{documen % :( - ''' +describe("Broken LaTeX file", function() { + before(function(done){ + this.broken_request = { + resources: [{ + path: "main.tex", + content: `\ +\\documentclass{articl % :( +\\begin{documen % :( +Broken +\\end{documen % :(\ +` + } ] - @correct_request = - resources: [ - path: "main.tex" - content: ''' - \\documentclass{article} - \\begin{document} - Hello world - \\end{document} - ''' + }; + this.correct_request = { + resources: [{ + path: "main.tex", + content: `\ +\\documentclass{article} +\\begin{document} +Hello world +\\end{document}\ +` + } ] - ClsiApp.ensureRunning done + }; + return ClsiApp.ensureRunning(done); + }); - describe "on first run", -> - before (done) -> - @project_id = Client.randomId() - Client.compile @project_id, @broken_request, (@error, @res, @body) => done() + describe("on first run", function() { + before(function(done) { + this.project_id = Client.randomId(); + return Client.compile(this.project_id, this.broken_request, (error, res, body) => { this.error = error; this.res = res; this.body = body; return done(); }); + }); - it "should return a failure status", -> - @body.compile.status.should.equal "failure" + return it("should return a failure status", function() { + return this.body.compile.status.should.equal("failure"); + }); + }); - describe "on second run", -> - before (done) -> - @project_id = Client.randomId() - Client.compile @project_id, @correct_request, () => - Client.compile @project_id, @broken_request, (@error, @res, @body) => - done() + return describe("on second run", function() { + before(function(done) { + this.project_id = Client.randomId(); + return Client.compile(this.project_id, this.correct_request, () => { + return Client.compile(this.project_id, this.broken_request, (error, res, body) => { + this.error = error; + this.res = res; + this.body = body; + return done(); + }); + }); + }); - it "should return a failure status", -> - @body.compile.status.should.equal "failure" + return it("should return a failure status", function() { + return this.body.compile.status.should.equal("failure"); + }); + }); +}); diff --git a/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.js b/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.js index 1cb67765ca..d6958c20bd 100644 --- a/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.js +++ b/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.js @@ -1,36 +1,55 @@ -Client = require "./helpers/Client" -request = require "request" -require("chai").should() -ClsiApp = require "./helpers/ClsiApp" +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const Client = require("./helpers/Client"); +const request = require("request"); +require("chai").should(); +const ClsiApp = require("./helpers/ClsiApp"); -describe "Deleting Old Files", -> - before (done)-> - @request = - resources: [ - path: "main.tex" - content: ''' - \\documentclass{article} - \\begin{document} - Hello world - \\end{document} - ''' +describe("Deleting Old Files", function() { + before(function(done){ + this.request = { + resources: [{ + path: "main.tex", + content: `\ +\\documentclass{article} +\\begin{document} +Hello world +\\end{document}\ +` + } ] - ClsiApp.ensureRunning done + }; + return ClsiApp.ensureRunning(done); + }); - describe "on first run", -> - before (done) -> - @project_id = Client.randomId() - Client.compile @project_id, @request, (@error, @res, @body) => done() + return describe("on first run", function() { + before(function(done) { + this.project_id = Client.randomId(); + return Client.compile(this.project_id, this.request, (error, res, body) => { this.error = error; this.res = res; this.body = body; return done(); }); + }); - it "should return a success status", -> - @body.compile.status.should.equal "success" + it("should return a success status", function() { + return this.body.compile.status.should.equal("success"); + }); - describe "after file has been deleted", -> - before (done) -> - @request.resources = [] - Client.compile @project_id, @request, (@error, @res, @body) => - done() + return describe("after file has been deleted", function() { + before(function(done) { + this.request.resources = []; + return Client.compile(this.project_id, this.request, (error, res, body) => { + this.error = error; + this.res = res; + this.body = body; + return done(); + }); + }); - it "should return a failure status", -> - @body.compile.status.should.equal "failure" + return it("should return a failure status", function() { + return this.body.compile.status.should.equal("failure"); + }); + }); + }); +}); diff --git a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.js b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.js index f8e4a777b5..fe899707a8 100644 --- a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.js +++ b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.js @@ -1,129 +1,182 @@ -Client = require "./helpers/Client" -request = require "request" -require("chai").should() -fs = require "fs" -ChildProcess = require "child_process" -ClsiApp = require "./helpers/ClsiApp" -logger = require("logger-sharelatex") -Path = require("path") -fixturePath = (path) -> Path.normalize(__dirname + "/../fixtures/" + path) -process = require "process" -console.log process.pid, process.ppid, process.getuid(),process.getgroups(), "PID" -try - console.log "creating tmp directory", fixturePath("tmp") - fs.mkdirSync(fixturePath("tmp")) -catch err - console.log err, fixturePath("tmp"), "unable to create fixture tmp path" +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__ + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const Client = require("./helpers/Client"); +const request = require("request"); +require("chai").should(); +const fs = require("fs"); +const ChildProcess = require("child_process"); +const ClsiApp = require("./helpers/ClsiApp"); +const logger = require("logger-sharelatex"); +const Path = require("path"); +const fixturePath = path => Path.normalize(__dirname + "/../fixtures/" + path); +const process = require("process"); +console.log(process.pid, process.ppid, process.getuid(),process.getgroups(), "PID"); +try { + console.log("creating tmp directory", fixturePath("tmp")); + fs.mkdirSync(fixturePath("tmp")); +} catch (error) { + const err = error; + console.log(err, fixturePath("tmp"), "unable to create fixture tmp path"); +} -MOCHA_LATEX_TIMEOUT = 60 * 1000 +const MOCHA_LATEX_TIMEOUT = 60 * 1000; -convertToPng = (pdfPath, pngPath, callback = (error) ->) -> - command = "convert #{fixturePath(pdfPath)} #{fixturePath(pngPath)}" - console.log "COMMAND" - console.log command - convert = ChildProcess.exec command - stdout = "" - convert.stdout.on "data", (chunk) -> console.log "STDOUT", chunk.toString() - convert.stderr.on "data", (chunk) -> console.log "STDERR", chunk.toString() - convert.on "exit", () -> - callback() +const convertToPng = function(pdfPath, pngPath, callback) { + if (callback == null) { callback = function(error) {}; } + const command = `convert ${fixturePath(pdfPath)} ${fixturePath(pngPath)}`; + console.log("COMMAND"); + console.log(command); + const convert = ChildProcess.exec(command); + const stdout = ""; + convert.stdout.on("data", chunk => console.log("STDOUT", chunk.toString())); + convert.stderr.on("data", chunk => console.log("STDERR", chunk.toString())); + return convert.on("exit", () => callback()); +}; -compare = (originalPath, generatedPath, callback = (error, same) ->) -> - diff_file = "#{fixturePath(generatedPath)}-diff.png" - proc = ChildProcess.exec "compare -metric mae #{fixturePath(originalPath)} #{fixturePath(generatedPath)} #{diff_file}" - stderr = "" - proc.stderr.on "data", (chunk) -> stderr += chunk - proc.on "exit", () -> - if stderr.trim() == "0 (0)" - # remove output diff if test matches expected image - fs.unlink diff_file, (err) -> - if err - throw err - callback null, true - else - console.log "compare result", stderr - callback null, false +const compare = function(originalPath, generatedPath, callback) { + if (callback == null) { callback = function(error, same) {}; } + const diff_file = `${fixturePath(generatedPath)}-diff.png`; + const proc = ChildProcess.exec(`compare -metric mae ${fixturePath(originalPath)} ${fixturePath(generatedPath)} ${diff_file}`); + let stderr = ""; + proc.stderr.on("data", chunk => stderr += chunk); + return proc.on("exit", function() { + if (stderr.trim() === "0 (0)") { + // remove output diff if test matches expected image + fs.unlink(diff_file, function(err) { + if (err) { + throw err; + } + }); + return callback(null, true); + } else { + console.log("compare result", stderr); + return callback(null, false); + } + }); +}; -checkPdfInfo = (pdfPath, callback = (error, output) ->) -> - proc = ChildProcess.exec "pdfinfo #{fixturePath(pdfPath)}" - stdout = "" - proc.stdout.on "data", (chunk) -> stdout += chunk - proc.stderr.on "data", (chunk) -> console.log "STDERR", chunk.toString() - proc.on "exit", () -> - if stdout.match(/Optimized:\s+yes/) - callback null, true - else - callback null, false +const checkPdfInfo = function(pdfPath, callback) { + if (callback == null) { callback = function(error, output) {}; } + const proc = ChildProcess.exec(`pdfinfo ${fixturePath(pdfPath)}`); + let stdout = ""; + proc.stdout.on("data", chunk => stdout += chunk); + proc.stderr.on("data", chunk => console.log("STDERR", chunk.toString())); + return proc.on("exit", function() { + if (stdout.match(/Optimized:\s+yes/)) { + return callback(null, true); + } else { + return callback(null, false); + } + }); +}; -compareMultiplePages = (project_id, callback = (error) ->) -> - compareNext = (page_no, callback) -> - path = "tmp/#{project_id}-source-#{page_no}.png" - fs.stat fixturePath(path), (error, stat) -> - if error? - callback() - else - compare "tmp/#{project_id}-source-#{page_no}.png", "tmp/#{project_id}-generated-#{page_no}.png", (error, same) => - throw error if error? - same.should.equal true - compareNext page_no + 1, callback - compareNext 0, callback +const compareMultiplePages = function(project_id, callback) { + if (callback == null) { callback = function(error) {}; } + var compareNext = function(page_no, callback) { + const path = `tmp/${project_id}-source-${page_no}.png`; + return fs.stat(fixturePath(path), function(error, stat) { + if (error != null) { + return callback(); + } else { + return compare(`tmp/${project_id}-source-${page_no}.png`, `tmp/${project_id}-generated-${page_no}.png`, (error, same) => { + if (error != null) { throw error; } + same.should.equal(true); + return compareNext(page_no + 1, callback); + }); + } + }); + }; + return compareNext(0, callback); +}; -comparePdf = (project_id, example_dir, callback = (error) ->) -> - console.log "CONVERT" - console.log "tmp/#{project_id}.pdf", "tmp/#{project_id}-generated.png" - convertToPng "tmp/#{project_id}.pdf", "tmp/#{project_id}-generated.png", (error) => - throw error if error? - convertToPng "examples/#{example_dir}/output.pdf", "tmp/#{project_id}-source.png", (error) => - throw error if error? - fs.stat fixturePath("tmp/#{project_id}-source-0.png"), (error, stat) => - if error? - compare "tmp/#{project_id}-source.png", "tmp/#{project_id}-generated.png", (error, same) => - throw error if error? - same.should.equal true - callback() - else - compareMultiplePages project_id, (error) -> - throw error if error? - callback() +const comparePdf = function(project_id, example_dir, callback) { + if (callback == null) { callback = function(error) {}; } + console.log("CONVERT"); + console.log(`tmp/${project_id}.pdf`, `tmp/${project_id}-generated.png`); + return convertToPng(`tmp/${project_id}.pdf`, `tmp/${project_id}-generated.png`, error => { + if (error != null) { throw error; } + return convertToPng(`examples/${example_dir}/output.pdf`, `tmp/${project_id}-source.png`, error => { + if (error != null) { throw error; } + return fs.stat(fixturePath(`tmp/${project_id}-source-0.png`), (error, stat) => { + if (error != null) { + return compare(`tmp/${project_id}-source.png`, `tmp/${project_id}-generated.png`, (error, same) => { + if (error != null) { throw error; } + same.should.equal(true); + return callback(); + }); + } else { + return compareMultiplePages(project_id, function(error) { + if (error != null) { throw error; } + return callback(); + }); + } + }); + }); + }); +}; -downloadAndComparePdf = (project_id, example_dir, url, callback = (error) ->) -> - writeStream = fs.createWriteStream(fixturePath("tmp/#{project_id}.pdf")) - request.get(url).pipe(writeStream) - console.log("writing file out", fixturePath("tmp/#{project_id}.pdf")) - writeStream.on "close", () => - checkPdfInfo "tmp/#{project_id}.pdf", (error, optimised) => - throw error if error? - optimised.should.equal true - comparePdf project_id, example_dir, callback +const downloadAndComparePdf = function(project_id, example_dir, url, callback) { + if (callback == null) { callback = function(error) {}; } + const writeStream = fs.createWriteStream(fixturePath(`tmp/${project_id}.pdf`)); + request.get(url).pipe(writeStream); + console.log("writing file out", fixturePath(`tmp/${project_id}.pdf`)); + return writeStream.on("close", () => { + return checkPdfInfo(`tmp/${project_id}.pdf`, (error, optimised) => { + if (error != null) { throw error; } + optimised.should.equal(true); + return comparePdf(project_id, example_dir, callback); + }); + }); +}; -Client.runServer(4242, fixturePath("examples")) +Client.runServer(4242, fixturePath("examples")); -describe "Example Documents", -> - before (done) -> - ChildProcess.exec("rm test/acceptance/fixtures/tmp/*").on "exit", () -> - ClsiApp.ensureRunning done +describe("Example Documents", function() { + before(done => + ChildProcess.exec("rm test/acceptance/fixtures/tmp/*").on("exit", () => ClsiApp.ensureRunning(done)) + ); - for example_dir in fs.readdirSync fixturePath("examples") - do (example_dir) -> - describe example_dir, -> - before -> - @project_id = Client.randomId() + "_" + example_dir + return Array.from(fs.readdirSync(fixturePath("examples"))).map((example_dir) => + (example_dir => + describe(example_dir, function() { + before(function() { + return this.project_id = Client.randomId() + "_" + example_dir; + }); - it "should generate the correct pdf", (done) -> - this.timeout(MOCHA_LATEX_TIMEOUT) - Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => - if error || body?.compile?.status is "failure" - console.log "DEBUG: error", error, "body", JSON.stringify(body) - pdf = Client.getOutputFile body, "pdf" - downloadAndComparePdf(@project_id, example_dir, pdf.url, done) + it("should generate the correct pdf", function(done) { + this.timeout(MOCHA_LATEX_TIMEOUT); + return Client.compileDirectory(this.project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => { + if (error || (__guard__(body != null ? body.compile : undefined, x => x.status) === "failure")) { + console.log("DEBUG: error", error, "body", JSON.stringify(body)); + } + const pdf = Client.getOutputFile(body, "pdf"); + return downloadAndComparePdf(this.project_id, example_dir, pdf.url, done); + }); + }); - it "should generate the correct pdf on the second run as well", (done) -> - this.timeout(MOCHA_LATEX_TIMEOUT) - Client.compileDirectory @project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => - if error || body?.compile?.status is "failure" - console.log "DEBUG: error", error, "body", JSON.stringify(body) - pdf = Client.getOutputFile body, "pdf" - downloadAndComparePdf(@project_id, example_dir, pdf.url, done) + return it("should generate the correct pdf on the second run as well", function(done) { + this.timeout(MOCHA_LATEX_TIMEOUT); + return Client.compileDirectory(this.project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => { + if (error || (__guard__(body != null ? body.compile : undefined, x => x.status) === "failure")) { + console.log("DEBUG: error", error, "body", JSON.stringify(body)); + } + const pdf = Client.getOutputFile(body, "pdf"); + return downloadAndComparePdf(this.project_id, example_dir, pdf.url, done); + }); + }); + }) + )(example_dir)); +}); + +function __guard__(value, transform) { + return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; +} \ No newline at end of file diff --git a/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.js b/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.js index 95b667ba20..79789e80f7 100644 --- a/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.js +++ b/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.js @@ -1,41 +1,57 @@ -Client = require "./helpers/Client" -request = require "request" -require("chai").should() -ClsiApp = require "./helpers/ClsiApp" +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const Client = require("./helpers/Client"); +const request = require("request"); +require("chai").should(); +const ClsiApp = require("./helpers/ClsiApp"); -describe "Simple LaTeX file", -> - before (done) -> - @project_id = Client.randomId() - @request = - resources: [ - path: "main.tex" - content: ''' - \\documentclass{article} - \\begin{document} - Hello world - \\end{document} - ''' +describe("Simple LaTeX file", function() { + before(function(done) { + this.project_id = Client.randomId(); + this.request = { + resources: [{ + path: "main.tex", + content: `\ +\\documentclass{article} +\\begin{document} +Hello world +\\end{document}\ +` + } ] - ClsiApp.ensureRunning => - Client.compile @project_id, @request, (@error, @res, @body) => done() + }; + return ClsiApp.ensureRunning(() => { + return Client.compile(this.project_id, this.request, (error, res, body) => { this.error = error; this.res = res; this.body = body; return done(); }); + }); + }); - it "should return the PDF", -> - pdf = Client.getOutputFile(@body, "pdf") - pdf.type.should.equal "pdf" + it("should return the PDF", function() { + const pdf = Client.getOutputFile(this.body, "pdf"); + return pdf.type.should.equal("pdf"); + }); - it "should return the log", -> - log = Client.getOutputFile(@body, "log") - log.type.should.equal "log" + it("should return the log", function() { + const log = Client.getOutputFile(this.body, "log"); + return log.type.should.equal("log"); + }); - it "should provide the pdf for download", (done) -> - pdf = Client.getOutputFile(@body, "pdf") - request.get pdf.url, (error, res, body) -> - res.statusCode.should.equal 200 - done() + it("should provide the pdf for download", function(done) { + const pdf = Client.getOutputFile(this.body, "pdf"); + return request.get(pdf.url, function(error, res, body) { + res.statusCode.should.equal(200); + return done(); + }); + }); - it "should provide the log for download", (done) -> - log = Client.getOutputFile(@body, "pdf") - request.get log.url, (error, res, body) -> - res.statusCode.should.equal 200 - done() + return it("should provide the log for download", function(done) { + const log = Client.getOutputFile(this.body, "pdf"); + return request.get(log.url, function(error, res, body) { + res.statusCode.should.equal(200); + return done(); + }); + }); +}); diff --git a/services/clsi/test/acceptance/coffee/SynctexTests.js b/services/clsi/test/acceptance/coffee/SynctexTests.js index 685d292877..b0ac688184 100644 --- a/services/clsi/test/acceptance/coffee/SynctexTests.js +++ b/services/clsi/test/acceptance/coffee/SynctexTests.js @@ -1,41 +1,58 @@ -Client = require "./helpers/Client" -request = require "request" -require("chai").should() -expect = require("chai").expect -ClsiApp = require "./helpers/ClsiApp" -crypto = require("crypto") +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const Client = require("./helpers/Client"); +const request = require("request"); +require("chai").should(); +const { expect } = require("chai"); +const ClsiApp = require("./helpers/ClsiApp"); +const crypto = require("crypto"); -describe "Syncing", -> - before (done) -> - content = ''' - \\documentclass{article} - \\begin{document} - Hello world - \\end{document} - ''' - @request = - resources: [ - path: "main.tex" - content: content +describe("Syncing", function() { + before(function(done) { + const content = `\ +\\documentclass{article} +\\begin{document} +Hello world +\\end{document}\ +`; + this.request = { + resources: [{ + path: "main.tex", + content + } ] - @project_id = Client.randomId() - ClsiApp.ensureRunning => - Client.compile @project_id, @request, (@error, @res, @body) => done() + }; + this.project_id = Client.randomId(); + return ClsiApp.ensureRunning(() => { + return Client.compile(this.project_id, this.request, (error, res, body) => { this.error = error; this.res = res; this.body = body; return done(); }); + }); + }); - describe "from code to pdf", -> - it "should return the correct location", (done) -> - Client.syncFromCode @project_id, "main.tex", 3, 5, (error, pdfPositions) -> - throw error if error? - expect(pdfPositions).to.deep.equal( + describe("from code to pdf", () => + it("should return the correct location", function(done) { + return Client.syncFromCode(this.project_id, "main.tex", 3, 5, function(error, pdfPositions) { + if (error != null) { throw error; } + expect(pdfPositions).to.deep.equal({ pdf: [ { page: 1, h: 133.77, v: 134.76, height: 6.92, width: 343.71 } ] - ) - done() + }); + return done(); + }); + }) + ); - describe "from pdf to code", -> - it "should return the correct location", (done) -> - Client.syncFromPdf @project_id, 1, 100, 200, (error, codePositions) => - throw error if error? - expect(codePositions).to.deep.equal( + return describe("from pdf to code", () => + it("should return the correct location", function(done) { + return Client.syncFromPdf(this.project_id, 1, 100, 200, (error, codePositions) => { + if (error != null) { throw error; } + expect(codePositions).to.deep.equal({ code: [ { file: 'main.tex', line: 3, column: -1 } ] - ) - done() + }); + return done(); + }); + }) + ); +}); diff --git a/services/clsi/test/acceptance/coffee/TimeoutTests.js b/services/clsi/test/acceptance/coffee/TimeoutTests.js index b274dd54ff..39d18ed7fe 100644 --- a/services/clsi/test/acceptance/coffee/TimeoutTests.js +++ b/services/clsi/test/acceptance/coffee/TimeoutTests.js @@ -1,34 +1,48 @@ -Client = require "./helpers/Client" -request = require "request" -require("chai").should() -ClsiApp = require "./helpers/ClsiApp" +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const Client = require("./helpers/Client"); +const request = require("request"); +require("chai").should(); +const ClsiApp = require("./helpers/ClsiApp"); -describe "Timed out compile", -> - before (done) -> - @request = - options: - timeout: 10 #seconds - resources: [ - path: "main.tex" - content: ''' - \\documentclass{article} - \\begin{document} - \\def\\x{Hello!\\par\\x} - \\x - \\end{document} - ''' +describe("Timed out compile", function() { + before(function(done) { + this.request = { + options: { + timeout: 10 + }, //seconds + resources: [{ + path: "main.tex", + content: `\ +\\documentclass{article} +\\begin{document} +\\def\\x{Hello!\\par\\x} +\\x +\\end{document}\ +` + } ] - @project_id = Client.randomId() - ClsiApp.ensureRunning => - Client.compile @project_id, @request, (@error, @res, @body) => done() + }; + this.project_id = Client.randomId(); + return ClsiApp.ensureRunning(() => { + return Client.compile(this.project_id, this.request, (error, res, body) => { this.error = error; this.res = res; this.body = body; return done(); }); + }); + }); - it "should return a timeout error", -> - @body.compile.error.should.equal "container timed out" + it("should return a timeout error", function() { + return this.body.compile.error.should.equal("container timed out"); + }); - it "should return a timedout status", -> - @body.compile.status.should.equal "timedout" + it("should return a timedout status", function() { + return this.body.compile.status.should.equal("timedout"); + }); - it "should return the log output file name", -> - outputFilePaths = @body.compile.outputFiles.map((x) => x.path) - outputFilePaths.should.include('output.log') + return it("should return the log output file name", function() { + const outputFilePaths = this.body.compile.outputFiles.map(x => x.path); + return outputFilePaths.should.include('output.log'); + }); +}); diff --git a/services/clsi/test/acceptance/coffee/UrlCachingTests.js b/services/clsi/test/acceptance/coffee/UrlCachingTests.js index cef744672e..3fe947ff41 100644 --- a/services/clsi/test/acceptance/coffee/UrlCachingTests.js +++ b/services/clsi/test/acceptance/coffee/UrlCachingTests.js @@ -1,222 +1,280 @@ -Client = require "./helpers/Client" -request = require "request" -require("chai").should() -sinon = require "sinon" -ClsiApp = require "./helpers/ClsiApp" +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const Client = require("./helpers/Client"); +const request = require("request"); +require("chai").should(); +const sinon = require("sinon"); +const ClsiApp = require("./helpers/ClsiApp"); -host = "localhost" +const host = "localhost"; -Server = - run: () -> - express = require "express" - app = express() +const Server = { + run() { + const express = require("express"); + const app = express(); - staticServer = express.static __dirname + "/../fixtures/" - app.get "/:random_id/*", (req, res, next) => - @getFile(req.url) - req.url = "/" + req.params[0] - staticServer(req, res, next) + const staticServer = express.static(__dirname + "/../fixtures/"); + app.get("/:random_id/*", (req, res, next) => { + this.getFile(req.url); + req.url = `/${req.params[0]}`; + return staticServer(req, res, next); + }); - app.listen 31415, host + return app.listen(31415, host); + }, - getFile: () -> + getFile() {}, - randomId: () -> - Math.random().toString(16).slice(2) + randomId() { + return Math.random().toString(16).slice(2); + } +}; -Server.run() +Server.run(); -describe "Url Caching", -> - describe "Downloading an image for the first time", -> - before (done) -> - @project_id = Client.randomId() - @file = "#{Server.randomId()}/lion.png" - @request = +describe("Url Caching", function() { + describe("Downloading an image for the first time", function() { + before(function(done) { + this.project_id = Client.randomId(); + this.file = `${Server.randomId()}/lion.png`; + this.request = { resources: [{ - path: "main.tex" - content: ''' - \\documentclass{article} - \\usepackage{graphicx} - \\begin{document} - \\includegraphics{lion.png} - \\end{document} - ''' + path: "main.tex", + content: `\ +\\documentclass{article} +\\usepackage{graphicx} +\\begin{document} +\\includegraphics{lion.png} +\\end{document}\ +` }, { - path: "lion.png" - url: "http://#{host}:31415/#{@file}" + path: "lion.png", + url: `http://${host}:31415/${this.file}` }] + }; - sinon.spy Server, "getFile" - ClsiApp.ensureRunning => - Client.compile @project_id, @request, (@error, @res, @body) => done() + sinon.spy(Server, "getFile"); + return ClsiApp.ensureRunning(() => { + return Client.compile(this.project_id, this.request, (error, res, body) => { this.error = error; this.res = res; this.body = body; return done(); }); + }); + }); - afterEach -> - Server.getFile.restore() + afterEach(() => Server.getFile.restore()); - it "should download the image", -> - Server.getFile - .calledWith("/" + @file) - .should.equal true + return it("should download the image", function() { + return Server.getFile + .calledWith(`/${this.file}`) + .should.equal(true); + }); + }); - describe "When an image is in the cache and the last modified date is unchanged", -> - before (done) -> - @project_id = Client.randomId() - @file = "#{Server.randomId()}/lion.png" - @request = + describe("When an image is in the cache and the last modified date is unchanged", function() { + before(function(done) { + this.project_id = Client.randomId(); + this.file = `${Server.randomId()}/lion.png`; + this.request = { resources: [{ - path: "main.tex" - content: ''' - \\documentclass{article} - \\usepackage{graphicx} - \\begin{document} - \\includegraphics{lion.png} - \\end{document} - ''' - }, @image_resource = { - path: "lion.png" - url: "http://#{host}:31415/#{@file}" + path: "main.tex", + content: `\ +\\documentclass{article} +\\usepackage{graphicx} +\\begin{document} +\\includegraphics{lion.png} +\\end{document}\ +` + }, (this.image_resource = { + path: "lion.png", + url: `http://${host}:31415/${this.file}`, modified: Date.now() - }] + })] + }; - Client.compile @project_id, @request, (@error, @res, @body) => - sinon.spy Server, "getFile" - Client.compile @project_id, @request, (@error, @res, @body) => - done() + return Client.compile(this.project_id, this.request, (error, res, body) => { + this.error = error; + this.res = res; + this.body = body; + sinon.spy(Server, "getFile"); + return Client.compile(this.project_id, this.request, (error1, res1, body1) => { + this.error = error1; + this.res = res1; + this.body = body1; + return done(); + }); + }); + }); - after -> - Server.getFile.restore() + after(() => Server.getFile.restore()); - it "should not download the image again", -> - Server.getFile.called.should.equal false + return it("should not download the image again", () => Server.getFile.called.should.equal(false)); + }); - describe "When an image is in the cache and the last modified date is advanced", -> - before (done) -> - @project_id = Client.randomId() - @file = "#{Server.randomId()}/lion.png" - @request = + describe("When an image is in the cache and the last modified date is advanced", function() { + before(function(done) { + this.project_id = Client.randomId(); + this.file = `${Server.randomId()}/lion.png`; + this.request = { resources: [{ - path: "main.tex" - content: ''' - \\documentclass{article} - \\usepackage{graphicx} - \\begin{document} - \\includegraphics{lion.png} - \\end{document} - ''' - }, @image_resource = { - path: "lion.png" - url: "http://#{host}:31415/#{@file}" - modified: @last_modified = Date.now() - }] + path: "main.tex", + content: `\ +\\documentclass{article} +\\usepackage{graphicx} +\\begin{document} +\\includegraphics{lion.png} +\\end{document}\ +` + }, (this.image_resource = { + path: "lion.png", + url: `http://${host}:31415/${this.file}`, + modified: (this.last_modified = Date.now()) + })] + }; - Client.compile @project_id, @request, (@error, @res, @body) => - sinon.spy Server, "getFile" - @image_resource.modified = new Date(@last_modified + 3000) - Client.compile @project_id, @request, (@error, @res, @body) => - done() + return Client.compile(this.project_id, this.request, (error, res, body) => { + this.error = error; + this.res = res; + this.body = body; + sinon.spy(Server, "getFile"); + this.image_resource.modified = new Date(this.last_modified + 3000); + return Client.compile(this.project_id, this.request, (error1, res1, body1) => { + this.error = error1; + this.res = res1; + this.body = body1; + return done(); + }); + }); + }); - afterEach -> - Server.getFile.restore() + afterEach(() => Server.getFile.restore()); - it "should download the image again", -> - Server.getFile.called.should.equal true + return it("should download the image again", () => Server.getFile.called.should.equal(true)); + }); - describe "When an image is in the cache and the last modified date is further in the past", -> - before (done) -> - @project_id = Client.randomId() - @file = "#{Server.randomId()}/lion.png" - @request = + describe("When an image is in the cache and the last modified date is further in the past", function() { + before(function(done) { + this.project_id = Client.randomId(); + this.file = `${Server.randomId()}/lion.png`; + this.request = { resources: [{ - path: "main.tex" - content: ''' - \\documentclass{article} - \\usepackage{graphicx} - \\begin{document} - \\includegraphics{lion.png} - \\end{document} - ''' - }, @image_resource = { - path: "lion.png" - url: "http://#{host}:31415/#{@file}" - modified: @last_modified = Date.now() - }] + path: "main.tex", + content: `\ +\\documentclass{article} +\\usepackage{graphicx} +\\begin{document} +\\includegraphics{lion.png} +\\end{document}\ +` + }, (this.image_resource = { + path: "lion.png", + url: `http://${host}:31415/${this.file}`, + modified: (this.last_modified = Date.now()) + })] + }; - Client.compile @project_id, @request, (@error, @res, @body) => - sinon.spy Server, "getFile" - @image_resource.modified = new Date(@last_modified - 3000) - Client.compile @project_id, @request, (@error, @res, @body) => - done() + return Client.compile(this.project_id, this.request, (error, res, body) => { + this.error = error; + this.res = res; + this.body = body; + sinon.spy(Server, "getFile"); + this.image_resource.modified = new Date(this.last_modified - 3000); + return Client.compile(this.project_id, this.request, (error1, res1, body1) => { + this.error = error1; + this.res = res1; + this.body = body1; + return done(); + }); + }); + }); - afterEach -> - Server.getFile.restore() + afterEach(() => Server.getFile.restore()); - it "should not download the image again", -> - Server.getFile.called.should.equal false + return it("should not download the image again", () => Server.getFile.called.should.equal(false)); + }); - describe "When an image is in the cache and the last modified date is not specified", -> - before (done) -> - @project_id = Client.randomId() - @file = "#{Server.randomId()}/lion.png" - @request = + describe("When an image is in the cache and the last modified date is not specified", function() { + before(function(done) { + this.project_id = Client.randomId(); + this.file = `${Server.randomId()}/lion.png`; + this.request = { resources: [{ - path: "main.tex" - content: ''' - \\documentclass{article} - \\usepackage{graphicx} - \\begin{document} - \\includegraphics{lion.png} - \\end{document} - ''' - }, @image_resource = { - path: "lion.png" - url: "http://#{host}:31415/#{@file}" - modified: @last_modified = Date.now() - }] + path: "main.tex", + content: `\ +\\documentclass{article} +\\usepackage{graphicx} +\\begin{document} +\\includegraphics{lion.png} +\\end{document}\ +` + }, (this.image_resource = { + path: "lion.png", + url: `http://${host}:31415/${this.file}`, + modified: (this.last_modified = Date.now()) + })] + }; - Client.compile @project_id, @request, (@error, @res, @body) => - sinon.spy Server, "getFile" - delete @image_resource.modified - Client.compile @project_id, @request, (@error, @res, @body) => - done() + return Client.compile(this.project_id, this.request, (error, res, body) => { + this.error = error; + this.res = res; + this.body = body; + sinon.spy(Server, "getFile"); + delete this.image_resource.modified; + return Client.compile(this.project_id, this.request, (error1, res1, body1) => { + this.error = error1; + this.res = res1; + this.body = body1; + return done(); + }); + }); + }); - afterEach -> - Server.getFile.restore() + afterEach(() => Server.getFile.restore()); - it "should download the image again", -> - Server.getFile.called.should.equal true + return it("should download the image again", () => Server.getFile.called.should.equal(true)); + }); - describe "After clearing the cache", -> - before (done) -> - @project_id = Client.randomId() - @file = "#{Server.randomId()}/lion.png" - @request = + return describe("After clearing the cache", function() { + before(function(done) { + this.project_id = Client.randomId(); + this.file = `${Server.randomId()}/lion.png`; + this.request = { resources: [{ - path: "main.tex" - content: ''' - \\documentclass{article} - \\usepackage{graphicx} - \\begin{document} - \\includegraphics{lion.png} - \\end{document} - ''' - }, @image_resource = { - path: "lion.png" - url: "http://#{host}:31415/#{@file}" - modified: @last_modified = Date.now() - }] + path: "main.tex", + content: `\ +\\documentclass{article} +\\usepackage{graphicx} +\\begin{document} +\\includegraphics{lion.png} +\\end{document}\ +` + }, (this.image_resource = { + path: "lion.png", + url: `http://${host}:31415/${this.file}`, + modified: (this.last_modified = Date.now()) + })] + }; - Client.compile @project_id, @request, (error) => - throw error if error? - Client.clearCache @project_id, (error, res, body) => - throw error if error? - sinon.spy Server, "getFile" - Client.compile @project_id, @request, (@error, @res, @body) => - done() + return Client.compile(this.project_id, this.request, error => { + if (error != null) { throw error; } + return Client.clearCache(this.project_id, (error, res, body) => { + if (error != null) { throw error; } + sinon.spy(Server, "getFile"); + return Client.compile(this.project_id, this.request, (error1, res1, body1) => { + this.error = error1; + this.res = res1; + this.body = body1; + return done(); + }); + }); + }); + }); - afterEach -> - Server.getFile.restore() + afterEach(() => Server.getFile.restore()); - it "should download the image again", -> - Server.getFile.called.should.equal true + return it("should download the image again", () => Server.getFile.called.should.equal(true)); + }); +}); diff --git a/services/clsi/test/acceptance/coffee/WordcountTests.js b/services/clsi/test/acceptance/coffee/WordcountTests.js index abace066c9..8c87a7cd21 100644 --- a/services/clsi/test/acceptance/coffee/WordcountTests.js +++ b/services/clsi/test/acceptance/coffee/WordcountTests.js @@ -1,38 +1,52 @@ -Client = require "./helpers/Client" -request = require "request" -require("chai").should() -expect = require("chai").expect -path = require("path") -fs = require("fs") -ClsiApp = require "./helpers/ClsiApp" +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const Client = require("./helpers/Client"); +const request = require("request"); +require("chai").should(); +const { expect } = require("chai"); +const path = require("path"); +const fs = require("fs"); +const ClsiApp = require("./helpers/ClsiApp"); -describe "Syncing", -> - before (done) -> - @request = - resources: [ - path: "main.tex" +describe("Syncing", function() { + before(function(done) { + this.request = { + resources: [{ + path: "main.tex", content: fs.readFileSync(path.join(__dirname,"../fixtures/naugty_strings.txt"),"utf-8") + } ] - @project_id = Client.randomId() - ClsiApp.ensureRunning => - Client.compile @project_id, @request, (@error, @res, @body) => done() + }; + this.project_id = Client.randomId(); + return ClsiApp.ensureRunning(() => { + return Client.compile(this.project_id, this.request, (error, res, body) => { this.error = error; this.res = res; this.body = body; return done(); }); + }); + }); - describe "wordcount file", -> - it "should return wordcount info", (done) -> - Client.wordcount @project_id, "main.tex", (error, result) -> - throw error if error? - expect(result).to.deep.equal( + return describe("wordcount file", () => + it("should return wordcount info", function(done) { + return Client.wordcount(this.project_id, "main.tex", function(error, result) { + if (error != null) { throw error; } + expect(result).to.deep.equal({ texcount: { - encode: "utf8" - textWords: 2281 - headWords: 2 - outside: 0 - headers: 2 - elements: 0 - mathInline: 6 - mathDisplay: 0 - errors: 0 + encode: "utf8", + textWords: 2281, + headWords: 2, + outside: 0, + headers: 2, + elements: 0, + mathInline: 6, + mathDisplay: 0, + errors: 0, messages: "" } - ) - done() + }); + return done(); + }); + }) + ); +}); diff --git a/services/clsi/test/acceptance/coffee/helpers/Client.js b/services/clsi/test/acceptance/coffee/helpers/Client.js index 391317032f..4b85413a8b 100644 --- a/services/clsi/test/acceptance/coffee/helpers/Client.js +++ b/services/clsi/test/acceptance/coffee/helpers/Client.js @@ -1,105 +1,147 @@ -request = require "request" -fs = require "fs" -Settings = require "settings-sharelatex" +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let Client; +const request = require("request"); +const fs = require("fs"); +const Settings = require("settings-sharelatex"); -host = "localhost" +const host = "localhost"; -module.exports = Client = - host: Settings.apis.clsi.url +module.exports = (Client = { + host: Settings.apis.clsi.url, - randomId: () -> - Math.random().toString(16).slice(2) + randomId() { + return Math.random().toString(16).slice(2); + }, - compile: (project_id, data, callback = (error, res, body) ->) -> - request.post { - url: "#{@host}/project/#{project_id}/compile" - json: + compile(project_id, data, callback) { + if (callback == null) { callback = function(error, res, body) {}; } + return request.post({ + url: `${this.host}/project/${project_id}/compile`, + json: { compile: data - }, callback - - clearCache: (project_id, callback = (error, res, body) ->) -> - request.del "#{@host}/project/#{project_id}", callback - - getOutputFile: (response, type) -> - for file in response.compile.outputFiles - if file.type == type and file.url.match("output.#{type}") - return file - return null - - runServer: (port, directory) -> - express = require("express") - app = express() - app.use express.static(directory) - console.log("starting test server on", port, host) - app.listen(port, host).on "error", (error) -> - console.error "error starting server:", error.message - process.exit(1) - - - syncFromCode: (project_id, file, line, column, callback = (error, pdfPositions) ->) -> - request.get { - url: "#{@host}/project/#{project_id}/sync/code" - qs: { - file: file - line: line - column: column } - }, (error, response, body) -> - return callback(error) if error? - callback null, JSON.parse(body) + }, callback); + }, - syncFromPdf: (project_id, page, h, v, callback = (error, pdfPositions) ->) -> - request.get { - url: "#{@host}/project/#{project_id}/sync/pdf" - qs: { - page: page, - h: h, v: v + clearCache(project_id, callback) { + if (callback == null) { callback = function(error, res, body) {}; } + return request.del(`${this.host}/project/${project_id}`, callback); + }, + + getOutputFile(response, type) { + for (let file of Array.from(response.compile.outputFiles)) { + if ((file.type === type) && file.url.match(`output.${type}`)) { + return file; } - }, (error, response, body) -> - return callback(error) if error? - callback null, JSON.parse(body) + } + return null; + }, - compileDirectory: (project_id, baseDirectory, directory, serverPort, callback = (error, res, body) ->) -> - resources = [] - entities = fs.readdirSync("#{baseDirectory}/#{directory}") - rootResourcePath = "main.tex" - while (entities.length > 0) - entity = entities.pop() - stat = fs.statSync("#{baseDirectory}/#{directory}/#{entity}") - if stat.isDirectory() - entities = entities.concat fs.readdirSync("#{baseDirectory}/#{directory}/#{entity}").map (subEntity) -> - if subEntity == "main.tex" - rootResourcePath = "#{entity}/#{subEntity}" - return "#{entity}/#{subEntity}" - else if stat.isFile() and entity != "output.pdf" - extension = entity.split(".").pop() - if ["tex", "bib", "cls", "sty", "pdf_tex", "Rtex", "ist", "md", "Rmd"].indexOf(extension) > -1 - resources.push - path: entity - content: fs.readFileSync("#{baseDirectory}/#{directory}/#{entity}").toString() - else if ["eps", "ttf", "png", "jpg", "pdf", "jpeg"].indexOf(extension) > -1 - resources.push - path: entity - url: "http://#{host}:#{serverPort}/#{directory}/#{entity}" + runServer(port, directory) { + const express = require("express"); + const app = express(); + app.use(express.static(directory)); + console.log("starting test server on", port, host); + return app.listen(port, host).on("error", function(error) { + console.error("error starting server:", error.message); + return process.exit(1); + }); + }, + + + syncFromCode(project_id, file, line, column, callback) { + if (callback == null) { callback = function(error, pdfPositions) {}; } + return request.get({ + url: `${this.host}/project/${project_id}/sync/code`, + qs: { + file, + line, + column + } + }, function(error, response, body) { + if (error != null) { return callback(error); } + return callback(null, JSON.parse(body)); + }); + }, + + syncFromPdf(project_id, page, h, v, callback) { + if (callback == null) { callback = function(error, pdfPositions) {}; } + return request.get({ + url: `${this.host}/project/${project_id}/sync/pdf`, + qs: { + page, + h, v + } + }, function(error, response, body) { + if (error != null) { return callback(error); } + return callback(null, JSON.parse(body)); + }); + }, + + compileDirectory(project_id, baseDirectory, directory, serverPort, callback) { + if (callback == null) { callback = function(error, res, body) {}; } + const resources = []; + let entities = fs.readdirSync(`${baseDirectory}/${directory}`); + let rootResourcePath = "main.tex"; + while (entities.length > 0) { + var entity = entities.pop(); + const stat = fs.statSync(`${baseDirectory}/${directory}/${entity}`); + if (stat.isDirectory()) { + entities = entities.concat(fs.readdirSync(`${baseDirectory}/${directory}/${entity}`).map(function(subEntity) { + if (subEntity === "main.tex") { + rootResourcePath = `${entity}/${subEntity}`; + } + return `${entity}/${subEntity}`; + }) + ); + } else if (stat.isFile() && (entity !== "output.pdf")) { + const extension = entity.split(".").pop(); + if (["tex", "bib", "cls", "sty", "pdf_tex", "Rtex", "ist", "md", "Rmd"].indexOf(extension) > -1) { + resources.push({ + path: entity, + content: fs.readFileSync(`${baseDirectory}/${directory}/${entity}`).toString() + }); + } else if (["eps", "ttf", "png", "jpg", "pdf", "jpeg"].indexOf(extension) > -1) { + resources.push({ + path: entity, + url: `http://${host}:${serverPort}/${directory}/${entity}`, modified: stat.mtime - - fs.readFile "#{baseDirectory}/#{directory}/options.json", (error, body) => - req = - resources: resources - rootResourcePath: rootResourcePath - - if !error? - body = JSON.parse body - req.options = body - - @compile project_id, req, callback - - wordcount: (project_id, file, callback = (error, pdfPositions) ->) -> - request.get { - url: "#{@host}/project/#{project_id}/wordcount" - qs: { - file: file + }); + } } - }, (error, response, body) -> - return callback(error) if error? - callback null, JSON.parse(body) + } + + return fs.readFile(`${baseDirectory}/${directory}/options.json`, (error, body) => { + const req = { + resources, + rootResourcePath + }; + + if ((error == null)) { + body = JSON.parse(body); + req.options = body; + } + + return this.compile(project_id, req, callback); + }); + }, + + wordcount(project_id, file, callback) { + if (callback == null) { callback = function(error, pdfPositions) {}; } + return request.get({ + url: `${this.host}/project/${project_id}/wordcount`, + qs: { + file + } + }, function(error, response, body) { + if (error != null) { return callback(error); } + return callback(null, JSON.parse(body)); + }); + } +}); diff --git a/services/clsi/test/acceptance/coffee/helpers/ClsiApp.js b/services/clsi/test/acceptance/coffee/helpers/ClsiApp.js index d9cd534ba4..cad63ecdda 100644 --- a/services/clsi/test/acceptance/coffee/helpers/ClsiApp.js +++ b/services/clsi/test/acceptance/coffee/helpers/ClsiApp.js @@ -1,24 +1,46 @@ -app = require('../../../../app') -require("logger-sharelatex").logger.level("info") -logger = require("logger-sharelatex") -Settings = require("settings-sharelatex") +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__ + * DS205: Consider reworking code to avoid use of IIFEs + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const app = require('../../../../app'); +require("logger-sharelatex").logger.level("info"); +const logger = require("logger-sharelatex"); +const Settings = require("settings-sharelatex"); -module.exports = - running: false - initing: false - callbacks: [] - ensureRunning: (callback = (error) ->) -> - if @running - return callback() - else if @initing - @callbacks.push callback - else - @initing = true - @callbacks.push callback - app.listen Settings.internal?.clsi?.port, "localhost", (error) => - throw error if error? - @running = true - logger.log("clsi running in dev mode") +module.exports = { + running: false, + initing: false, + callbacks: [], + ensureRunning(callback) { + if (callback == null) { callback = function(error) {}; } + if (this.running) { + return callback(); + } else if (this.initing) { + return this.callbacks.push(callback); + } else { + this.initing = true; + this.callbacks.push(callback); + return app.listen(__guard__(Settings.internal != null ? Settings.internal.clsi : undefined, x => x.port), "localhost", error => { + if (error != null) { throw error; } + this.running = true; + logger.log("clsi running in dev mode"); - for callback in @callbacks - callback() \ No newline at end of file + return (() => { + const result = []; + for (callback of Array.from(this.callbacks)) { + result.push(callback()); + } + return result; + })(); + }); + } + } +}; +function __guard__(value, transform) { + return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; +} \ No newline at end of file From d1da978e395ac6e8f849f2a09371fb150e15d016 Mon Sep 17 00:00:00 2001 From: decaffeinate <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:16:07 +0100 Subject: [PATCH 528/754] decaffeinate: Run post-processing cleanups on BrokenLatexFileTests.coffee and 9 other files --- .../acceptance/coffee/BrokenLatexFileTests.js | 5 ++++ .../acceptance/coffee/DeleteOldFilesTest.js | 5 ++++ .../acceptance/coffee/ExampleDocumentTests.js | 22 ++++++++++----- .../acceptance/coffee/SimpleLatexFileTests.js | 9 ++++-- .../test/acceptance/coffee/SynctexTests.js | 17 ++++++----- .../test/acceptance/coffee/TimeoutTests.js | 7 ++++- .../test/acceptance/coffee/UrlCachingTests.js | 28 +++++++++++-------- .../test/acceptance/coffee/WordcountTests.js | 12 +++++--- .../test/acceptance/coffee/helpers/Client.js | 19 +++++++++---- .../test/acceptance/coffee/helpers/ClsiApp.js | 5 ++++ 10 files changed, 91 insertions(+), 38 deletions(-) diff --git a/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.js b/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.js index 5aea625778..2db36c1414 100644 --- a/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.js +++ b/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.js @@ -1,3 +1,8 @@ +/* eslint-disable + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns diff --git a/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.js b/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.js index d6958c20bd..720b90f2ba 100644 --- a/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.js +++ b/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.js @@ -1,3 +1,8 @@ +/* eslint-disable + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns diff --git a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.js b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.js index fe899707a8..4c3080f3a5 100644 --- a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.js +++ b/services/clsi/test/acceptance/coffee/ExampleDocumentTests.js @@ -1,3 +1,12 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-path-concat, + 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 @@ -45,10 +54,10 @@ const compare = function(originalPath, generatedPath, callback) { const proc = ChildProcess.exec(`compare -metric mae ${fixturePath(originalPath)} ${fixturePath(generatedPath)} ${diff_file}`); let stderr = ""; proc.stderr.on("data", chunk => stderr += chunk); - return proc.on("exit", function() { + return proc.on("exit", () => { if (stderr.trim() === "0 (0)") { // remove output diff if test matches expected image - fs.unlink(diff_file, function(err) { + fs.unlink(diff_file, (err) => { if (err) { throw err; } @@ -67,7 +76,7 @@ const checkPdfInfo = function(pdfPath, callback) { let stdout = ""; proc.stdout.on("data", chunk => stdout += chunk); proc.stderr.on("data", chunk => console.log("STDERR", chunk.toString())); - return proc.on("exit", function() { + return proc.on("exit", () => { if (stdout.match(/Optimized:\s+yes/)) { return callback(null, true); } else { @@ -80,7 +89,7 @@ const compareMultiplePages = function(project_id, callback) { if (callback == null) { callback = function(error) {}; } var compareNext = function(page_no, callback) { const path = `tmp/${project_id}-source-${page_no}.png`; - return fs.stat(fixturePath(path), function(error, stat) { + return fs.stat(fixturePath(path), (error, stat) => { if (error != null) { return callback(); } else { @@ -111,7 +120,7 @@ const comparePdf = function(project_id, example_dir, callback) { return callback(); }); } else { - return compareMultiplePages(project_id, function(error) { + return compareMultiplePages(project_id, (error) => { if (error != null) { throw error; } return callback(); }); @@ -138,8 +147,7 @@ const downloadAndComparePdf = function(project_id, example_dir, url, callback) { Client.runServer(4242, fixturePath("examples")); describe("Example Documents", function() { - before(done => - ChildProcess.exec("rm test/acceptance/fixtures/tmp/*").on("exit", () => ClsiApp.ensureRunning(done)) + before(function(done) { return ChildProcess.exec("rm test/acceptance/fixtures/tmp/*").on("exit", () => ClsiApp.ensureRunning(done)); } ); diff --git a/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.js b/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.js index 79789e80f7..d774301dd0 100644 --- a/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.js +++ b/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.js @@ -1,3 +1,8 @@ +/* eslint-disable + handle-callback-err, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns @@ -40,7 +45,7 @@ Hello world it("should provide the pdf for download", function(done) { const pdf = Client.getOutputFile(this.body, "pdf"); - return request.get(pdf.url, function(error, res, body) { + return request.get(pdf.url, (error, res, body) => { res.statusCode.should.equal(200); return done(); }); @@ -48,7 +53,7 @@ Hello world return it("should provide the log for download", function(done) { const log = Client.getOutputFile(this.body, "pdf"); - return request.get(log.url, function(error, res, body) { + return request.get(log.url, (error, res, body) => { res.statusCode.should.equal(200); return done(); }); diff --git a/services/clsi/test/acceptance/coffee/SynctexTests.js b/services/clsi/test/acceptance/coffee/SynctexTests.js index b0ac688184..d8879ebc0f 100644 --- a/services/clsi/test/acceptance/coffee/SynctexTests.js +++ b/services/clsi/test/acceptance/coffee/SynctexTests.js @@ -1,3 +1,8 @@ +/* eslint-disable + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns @@ -32,20 +37,18 @@ Hello world }); }); - describe("from code to pdf", () => - it("should return the correct location", function(done) { - return Client.syncFromCode(this.project_id, "main.tex", 3, 5, function(error, pdfPositions) { + describe("from code to pdf", function() { return it("should return the correct location", function(done) { + return Client.syncFromCode(this.project_id, "main.tex", 3, 5, (error, pdfPositions) => { if (error != null) { throw error; } expect(pdfPositions).to.deep.equal({ pdf: [ { page: 1, h: 133.77, v: 134.76, height: 6.92, width: 343.71 } ] }); return done(); }); - }) + }); } ); - return describe("from pdf to code", () => - it("should return the correct location", function(done) { + return describe("from pdf to code", function() { return it("should return the correct location", function(done) { return Client.syncFromPdf(this.project_id, 1, 100, 200, (error, codePositions) => { if (error != null) { throw error; } expect(codePositions).to.deep.equal({ @@ -53,6 +56,6 @@ Hello world }); return done(); }); - }) + }); } ); }); diff --git a/services/clsi/test/acceptance/coffee/TimeoutTests.js b/services/clsi/test/acceptance/coffee/TimeoutTests.js index 39d18ed7fe..7f8f84835a 100644 --- a/services/clsi/test/acceptance/coffee/TimeoutTests.js +++ b/services/clsi/test/acceptance/coffee/TimeoutTests.js @@ -1,3 +1,8 @@ +/* eslint-disable + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns @@ -14,7 +19,7 @@ describe("Timed out compile", function() { this.request = { options: { timeout: 10 - }, //seconds + }, // seconds resources: [{ path: "main.tex", content: `\ diff --git a/services/clsi/test/acceptance/coffee/UrlCachingTests.js b/services/clsi/test/acceptance/coffee/UrlCachingTests.js index 3fe947ff41..7bb0a2055e 100644 --- a/services/clsi/test/acceptance/coffee/UrlCachingTests.js +++ b/services/clsi/test/acceptance/coffee/UrlCachingTests.js @@ -1,3 +1,9 @@ +/* eslint-disable + no-path-concat, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns @@ -63,7 +69,7 @@ describe("Url Caching", function() { }); }); - afterEach(() => Server.getFile.restore()); + afterEach(function() { return Server.getFile.restore(); }); return it("should download the image", function() { return Server.getFile @@ -107,9 +113,9 @@ describe("Url Caching", function() { }); }); - after(() => Server.getFile.restore()); + after(function() { return Server.getFile.restore(); }); - return it("should not download the image again", () => Server.getFile.called.should.equal(false)); + return it("should not download the image again", function() { return Server.getFile.called.should.equal(false); }); }); describe("When an image is in the cache and the last modified date is advanced", function() { @@ -148,9 +154,9 @@ describe("Url Caching", function() { }); }); - afterEach(() => Server.getFile.restore()); + afterEach(function() { return Server.getFile.restore(); }); - return it("should download the image again", () => Server.getFile.called.should.equal(true)); + return it("should download the image again", function() { return Server.getFile.called.should.equal(true); }); }); describe("When an image is in the cache and the last modified date is further in the past", function() { @@ -189,9 +195,9 @@ describe("Url Caching", function() { }); }); - afterEach(() => Server.getFile.restore()); + afterEach(function() { return Server.getFile.restore(); }); - return it("should not download the image again", () => Server.getFile.called.should.equal(false)); + return it("should not download the image again", function() { return Server.getFile.called.should.equal(false); }); }); describe("When an image is in the cache and the last modified date is not specified", function() { @@ -230,9 +236,9 @@ describe("Url Caching", function() { }); }); - afterEach(() => Server.getFile.restore()); + afterEach(function() { return Server.getFile.restore(); }); - return it("should download the image again", () => Server.getFile.called.should.equal(true)); + return it("should download the image again", function() { return Server.getFile.called.should.equal(true); }); }); return describe("After clearing the cache", function() { @@ -271,9 +277,9 @@ describe("Url Caching", function() { }); }); - afterEach(() => Server.getFile.restore()); + afterEach(function() { return Server.getFile.restore(); }); - return it("should download the image again", () => Server.getFile.called.should.equal(true)); + return it("should download the image again", function() { return Server.getFile.called.should.equal(true); }); }); }); diff --git a/services/clsi/test/acceptance/coffee/WordcountTests.js b/services/clsi/test/acceptance/coffee/WordcountTests.js index 8c87a7cd21..2f81e1398d 100644 --- a/services/clsi/test/acceptance/coffee/WordcountTests.js +++ b/services/clsi/test/acceptance/coffee/WordcountTests.js @@ -1,3 +1,8 @@ +/* eslint-disable + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns @@ -27,9 +32,8 @@ describe("Syncing", function() { }); }); - return describe("wordcount file", () => - it("should return wordcount info", function(done) { - return Client.wordcount(this.project_id, "main.tex", function(error, result) { + return describe("wordcount file", function() { return it("should return wordcount info", function(done) { + return Client.wordcount(this.project_id, "main.tex", (error, result) => { if (error != null) { throw error; } expect(result).to.deep.equal({ texcount: { @@ -47,6 +51,6 @@ describe("Syncing", function() { }); return done(); }); - }) + }); } ); }); diff --git a/services/clsi/test/acceptance/coffee/helpers/Client.js b/services/clsi/test/acceptance/coffee/helpers/Client.js index 4b85413a8b..50e75d6148 100644 --- a/services/clsi/test/acceptance/coffee/helpers/Client.js +++ b/services/clsi/test/acceptance/coffee/helpers/Client.js @@ -1,3 +1,10 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from @@ -35,7 +42,7 @@ module.exports = (Client = { }, getOutputFile(response, type) { - for (let file of Array.from(response.compile.outputFiles)) { + for (const file of Array.from(response.compile.outputFiles)) { if ((file.type === type) && file.url.match(`output.${type}`)) { return file; } @@ -48,7 +55,7 @@ module.exports = (Client = { const app = express(); app.use(express.static(directory)); console.log("starting test server on", port, host); - return app.listen(port, host).on("error", function(error) { + return app.listen(port, host).on("error", (error) => { console.error("error starting server:", error.message); return process.exit(1); }); @@ -64,7 +71,7 @@ module.exports = (Client = { line, column } - }, function(error, response, body) { + }, (error, response, body) => { if (error != null) { return callback(error); } return callback(null, JSON.parse(body)); }); @@ -78,7 +85,7 @@ module.exports = (Client = { page, h, v } - }, function(error, response, body) { + }, (error, response, body) => { if (error != null) { return callback(error); } return callback(null, JSON.parse(body)); }); @@ -93,7 +100,7 @@ module.exports = (Client = { var entity = entities.pop(); const stat = fs.statSync(`${baseDirectory}/${directory}/${entity}`); if (stat.isDirectory()) { - entities = entities.concat(fs.readdirSync(`${baseDirectory}/${directory}/${entity}`).map(function(subEntity) { + entities = entities.concat(fs.readdirSync(`${baseDirectory}/${directory}/${entity}`).map((subEntity) => { if (subEntity === "main.tex") { rootResourcePath = `${entity}/${subEntity}`; } @@ -139,7 +146,7 @@ module.exports = (Client = { qs: { file } - }, function(error, response, body) { + }, (error, response, body) => { if (error != null) { return callback(error); } return callback(null, JSON.parse(body)); }); diff --git a/services/clsi/test/acceptance/coffee/helpers/ClsiApp.js b/services/clsi/test/acceptance/coffee/helpers/ClsiApp.js index cad63ecdda..bd3222df6c 100644 --- a/services/clsi/test/acceptance/coffee/helpers/ClsiApp.js +++ b/services/clsi/test/acceptance/coffee/helpers/ClsiApp.js @@ -1,3 +1,8 @@ +/* eslint-disable + handle-callback-err, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from From c5a6496b7159b29ac41f65b7a8778bc22e27363a Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:16:11 +0100 Subject: [PATCH 529/754] decaffeinate: rename test/acceptance/coffee to test/acceptance/js --- .../clsi/test/acceptance/{coffee => js}/BrokenLatexFileTests.js | 0 .../clsi/test/acceptance/{coffee => js}/DeleteOldFilesTest.js | 0 .../clsi/test/acceptance/{coffee => js}/ExampleDocumentTests.js | 0 .../clsi/test/acceptance/{coffee => js}/SimpleLatexFileTests.js | 0 services/clsi/test/acceptance/{coffee => js}/SynctexTests.js | 0 services/clsi/test/acceptance/{coffee => js}/TimeoutTests.js | 0 services/clsi/test/acceptance/{coffee => js}/UrlCachingTests.js | 0 services/clsi/test/acceptance/{coffee => js}/WordcountTests.js | 0 services/clsi/test/acceptance/{coffee => js}/helpers/Client.js | 0 services/clsi/test/acceptance/{coffee => js}/helpers/ClsiApp.js | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename services/clsi/test/acceptance/{coffee => js}/BrokenLatexFileTests.js (100%) rename services/clsi/test/acceptance/{coffee => js}/DeleteOldFilesTest.js (100%) rename services/clsi/test/acceptance/{coffee => js}/ExampleDocumentTests.js (100%) rename services/clsi/test/acceptance/{coffee => js}/SimpleLatexFileTests.js (100%) rename services/clsi/test/acceptance/{coffee => js}/SynctexTests.js (100%) rename services/clsi/test/acceptance/{coffee => js}/TimeoutTests.js (100%) rename services/clsi/test/acceptance/{coffee => js}/UrlCachingTests.js (100%) rename services/clsi/test/acceptance/{coffee => js}/WordcountTests.js (100%) rename services/clsi/test/acceptance/{coffee => js}/helpers/Client.js (100%) rename services/clsi/test/acceptance/{coffee => js}/helpers/ClsiApp.js (100%) diff --git a/services/clsi/test/acceptance/coffee/BrokenLatexFileTests.js b/services/clsi/test/acceptance/js/BrokenLatexFileTests.js similarity index 100% rename from services/clsi/test/acceptance/coffee/BrokenLatexFileTests.js rename to services/clsi/test/acceptance/js/BrokenLatexFileTests.js diff --git a/services/clsi/test/acceptance/coffee/DeleteOldFilesTest.js b/services/clsi/test/acceptance/js/DeleteOldFilesTest.js similarity index 100% rename from services/clsi/test/acceptance/coffee/DeleteOldFilesTest.js rename to services/clsi/test/acceptance/js/DeleteOldFilesTest.js diff --git a/services/clsi/test/acceptance/coffee/ExampleDocumentTests.js b/services/clsi/test/acceptance/js/ExampleDocumentTests.js similarity index 100% rename from services/clsi/test/acceptance/coffee/ExampleDocumentTests.js rename to services/clsi/test/acceptance/js/ExampleDocumentTests.js diff --git a/services/clsi/test/acceptance/coffee/SimpleLatexFileTests.js b/services/clsi/test/acceptance/js/SimpleLatexFileTests.js similarity index 100% rename from services/clsi/test/acceptance/coffee/SimpleLatexFileTests.js rename to services/clsi/test/acceptance/js/SimpleLatexFileTests.js diff --git a/services/clsi/test/acceptance/coffee/SynctexTests.js b/services/clsi/test/acceptance/js/SynctexTests.js similarity index 100% rename from services/clsi/test/acceptance/coffee/SynctexTests.js rename to services/clsi/test/acceptance/js/SynctexTests.js diff --git a/services/clsi/test/acceptance/coffee/TimeoutTests.js b/services/clsi/test/acceptance/js/TimeoutTests.js similarity index 100% rename from services/clsi/test/acceptance/coffee/TimeoutTests.js rename to services/clsi/test/acceptance/js/TimeoutTests.js diff --git a/services/clsi/test/acceptance/coffee/UrlCachingTests.js b/services/clsi/test/acceptance/js/UrlCachingTests.js similarity index 100% rename from services/clsi/test/acceptance/coffee/UrlCachingTests.js rename to services/clsi/test/acceptance/js/UrlCachingTests.js diff --git a/services/clsi/test/acceptance/coffee/WordcountTests.js b/services/clsi/test/acceptance/js/WordcountTests.js similarity index 100% rename from services/clsi/test/acceptance/coffee/WordcountTests.js rename to services/clsi/test/acceptance/js/WordcountTests.js diff --git a/services/clsi/test/acceptance/coffee/helpers/Client.js b/services/clsi/test/acceptance/js/helpers/Client.js similarity index 100% rename from services/clsi/test/acceptance/coffee/helpers/Client.js rename to services/clsi/test/acceptance/js/helpers/Client.js diff --git a/services/clsi/test/acceptance/coffee/helpers/ClsiApp.js b/services/clsi/test/acceptance/js/helpers/ClsiApp.js similarity index 100% rename from services/clsi/test/acceptance/coffee/helpers/ClsiApp.js rename to services/clsi/test/acceptance/js/helpers/ClsiApp.js From 778f2a84201d36781bd883180cb528f05bd1dc17 Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:16:14 +0100 Subject: [PATCH 530/754] prettier: convert test/acceptance decaffeinated files to Prettier format --- .../acceptance/js/BrokenLatexFileTests.js | 115 ++-- .../test/acceptance/js/DeleteOldFilesTest.js | 93 ++-- .../acceptance/js/ExampleDocumentTests.js | 390 ++++++++------ .../acceptance/js/SimpleLatexFileTests.js | 97 ++-- .../clsi/test/acceptance/js/SynctexTests.js | 116 ++-- .../clsi/test/acceptance/js/TimeoutTests.js | 75 +-- .../test/acceptance/js/UrlCachingTests.js | 503 ++++++++++-------- .../clsi/test/acceptance/js/WordcountTests.js | 102 ++-- .../clsi/test/acceptance/js/helpers/Client.js | 306 ++++++----- .../test/acceptance/js/helpers/ClsiApp.js | 79 +-- 10 files changed, 1105 insertions(+), 771 deletions(-) diff --git a/services/clsi/test/acceptance/js/BrokenLatexFileTests.js b/services/clsi/test/acceptance/js/BrokenLatexFileTests.js index 2db36c1414..b34d23cad5 100644 --- a/services/clsi/test/acceptance/js/BrokenLatexFileTests.js +++ b/services/clsi/test/acceptance/js/BrokenLatexFileTests.js @@ -8,68 +8,81 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const Client = require("./helpers/Client"); -const request = require("request"); -require("chai").should(); -const ClsiApp = require("./helpers/ClsiApp"); +const Client = require('./helpers/Client') +const request = require('request') +require('chai').should() +const ClsiApp = require('./helpers/ClsiApp') -describe("Broken LaTeX file", function() { - before(function(done){ - this.broken_request = { - resources: [{ - path: "main.tex", - content: `\ +describe('Broken LaTeX file', function() { + before(function(done) { + this.broken_request = { + resources: [ + { + path: 'main.tex', + content: `\ \\documentclass{articl % :( \\begin{documen % :( Broken \\end{documen % :(\ ` - } - ] - }; - this.correct_request = { - resources: [{ - path: "main.tex", - content: `\ + } + ] + } + this.correct_request = { + resources: [ + { + path: 'main.tex', + content: `\ \\documentclass{article} \\begin{document} Hello world \\end{document}\ ` - } - ] - }; - return ClsiApp.ensureRunning(done); - }); - - describe("on first run", function() { - before(function(done) { - this.project_id = Client.randomId(); - return Client.compile(this.project_id, this.broken_request, (error, res, body) => { this.error = error; this.res = res; this.body = body; return done(); }); - }); + } + ] + } + return ClsiApp.ensureRunning(done) + }) - return it("should return a failure status", function() { - return this.body.compile.status.should.equal("failure"); - }); - }); + describe('on first run', function() { + before(function(done) { + this.project_id = Client.randomId() + return Client.compile( + this.project_id, + this.broken_request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + return done() + } + ) + }) - return describe("on second run", function() { - before(function(done) { - this.project_id = Client.randomId(); - return Client.compile(this.project_id, this.correct_request, () => { - return Client.compile(this.project_id, this.broken_request, (error, res, body) => { - this.error = error; - this.res = res; - this.body = body; - return done(); - }); - }); - }); + return it('should return a failure status', function() { + return this.body.compile.status.should.equal('failure') + }) + }) - return it("should return a failure status", function() { - return this.body.compile.status.should.equal("failure"); - }); - }); -}); - - + return describe('on second run', function() { + before(function(done) { + this.project_id = Client.randomId() + return Client.compile(this.project_id, this.correct_request, () => { + return Client.compile( + this.project_id, + this.broken_request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + return done() + } + ) + }) + }) + + return it('should return a failure status', function() { + return this.body.compile.status.should.equal('failure') + }) + }) +}) diff --git a/services/clsi/test/acceptance/js/DeleteOldFilesTest.js b/services/clsi/test/acceptance/js/DeleteOldFilesTest.js index 720b90f2ba..83d7c96d0d 100644 --- a/services/clsi/test/acceptance/js/DeleteOldFilesTest.js +++ b/services/clsi/test/acceptance/js/DeleteOldFilesTest.js @@ -8,53 +8,66 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const Client = require("./helpers/Client"); -const request = require("request"); -require("chai").should(); -const ClsiApp = require("./helpers/ClsiApp"); +const Client = require('./helpers/Client') +const request = require('request') +require('chai').should() +const ClsiApp = require('./helpers/ClsiApp') -describe("Deleting Old Files", function() { - before(function(done){ - this.request = { - resources: [{ - path: "main.tex", - content: `\ +describe('Deleting Old Files', function() { + before(function(done) { + this.request = { + resources: [ + { + path: 'main.tex', + content: `\ \\documentclass{article} \\begin{document} Hello world \\end{document}\ ` - } - ] - }; - return ClsiApp.ensureRunning(done); - }); + } + ] + } + return ClsiApp.ensureRunning(done) + }) - return describe("on first run", function() { - before(function(done) { - this.project_id = Client.randomId(); - return Client.compile(this.project_id, this.request, (error, res, body) => { this.error = error; this.res = res; this.body = body; return done(); }); - }); + return describe('on first run', function() { + before(function(done) { + this.project_id = Client.randomId() + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + return done() + } + ) + }) - it("should return a success status", function() { - return this.body.compile.status.should.equal("success"); - }); + it('should return a success status', function() { + return this.body.compile.status.should.equal('success') + }) - return describe("after file has been deleted", function() { - before(function(done) { - this.request.resources = []; - return Client.compile(this.project_id, this.request, (error, res, body) => { - this.error = error; - this.res = res; - this.body = body; - return done(); - }); - }); - - return it("should return a failure status", function() { - return this.body.compile.status.should.equal("failure"); - }); - }); - }); -}); + return describe('after file has been deleted', function() { + before(function(done) { + this.request.resources = [] + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + return done() + } + ) + }) + return it('should return a failure status', function() { + return this.body.compile.status.should.equal('failure') + }) + }) + }) +}) diff --git a/services/clsi/test/acceptance/js/ExampleDocumentTests.js b/services/clsi/test/acceptance/js/ExampleDocumentTests.js index 4c3080f3a5..110b5d6f9a 100644 --- a/services/clsi/test/acceptance/js/ExampleDocumentTests.js +++ b/services/clsi/test/acceptance/js/ExampleDocumentTests.js @@ -15,176 +15,266 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const Client = require("./helpers/Client"); -const request = require("request"); -require("chai").should(); -const fs = require("fs"); -const ChildProcess = require("child_process"); -const ClsiApp = require("./helpers/ClsiApp"); -const logger = require("logger-sharelatex"); -const Path = require("path"); -const fixturePath = path => Path.normalize(__dirname + "/../fixtures/" + path); -const process = require("process"); -console.log(process.pid, process.ppid, process.getuid(),process.getgroups(), "PID"); +const Client = require('./helpers/Client') +const request = require('request') +require('chai').should() +const fs = require('fs') +const ChildProcess = require('child_process') +const ClsiApp = require('./helpers/ClsiApp') +const logger = require('logger-sharelatex') +const Path = require('path') +const fixturePath = path => Path.normalize(__dirname + '/../fixtures/' + path) +const process = require('process') +console.log( + process.pid, + process.ppid, + process.getuid(), + process.getgroups(), + 'PID' +) try { - console.log("creating tmp directory", fixturePath("tmp")); - fs.mkdirSync(fixturePath("tmp")); + console.log('creating tmp directory', fixturePath('tmp')) + fs.mkdirSync(fixturePath('tmp')) } catch (error) { - const err = error; - console.log(err, fixturePath("tmp"), "unable to create fixture tmp path"); + const err = error + console.log(err, fixturePath('tmp'), 'unable to create fixture tmp path') } -const MOCHA_LATEX_TIMEOUT = 60 * 1000; +const MOCHA_LATEX_TIMEOUT = 60 * 1000 const convertToPng = function(pdfPath, pngPath, callback) { - if (callback == null) { callback = function(error) {}; } - const command = `convert ${fixturePath(pdfPath)} ${fixturePath(pngPath)}`; - console.log("COMMAND"); - console.log(command); - const convert = ChildProcess.exec(command); - const stdout = ""; - convert.stdout.on("data", chunk => console.log("STDOUT", chunk.toString())); - convert.stderr.on("data", chunk => console.log("STDERR", chunk.toString())); - return convert.on("exit", () => callback()); -}; + if (callback == null) { + callback = function(error) {} + } + const command = `convert ${fixturePath(pdfPath)} ${fixturePath(pngPath)}` + console.log('COMMAND') + console.log(command) + const convert = ChildProcess.exec(command) + const stdout = '' + convert.stdout.on('data', chunk => console.log('STDOUT', chunk.toString())) + convert.stderr.on('data', chunk => console.log('STDERR', chunk.toString())) + return convert.on('exit', () => callback()) +} const compare = function(originalPath, generatedPath, callback) { - if (callback == null) { callback = function(error, same) {}; } - const diff_file = `${fixturePath(generatedPath)}-diff.png`; - const proc = ChildProcess.exec(`compare -metric mae ${fixturePath(originalPath)} ${fixturePath(generatedPath)} ${diff_file}`); - let stderr = ""; - proc.stderr.on("data", chunk => stderr += chunk); - return proc.on("exit", () => { - if (stderr.trim() === "0 (0)") { - // remove output diff if test matches expected image - fs.unlink(diff_file, (err) => { - if (err) { - throw err; - } - }); - return callback(null, true); - } else { - console.log("compare result", stderr); - return callback(null, false); - } - }); -}; + if (callback == null) { + callback = function(error, same) {} + } + const diff_file = `${fixturePath(generatedPath)}-diff.png` + const proc = ChildProcess.exec( + `compare -metric mae ${fixturePath(originalPath)} ${fixturePath( + generatedPath + )} ${diff_file}` + ) + let stderr = '' + proc.stderr.on('data', chunk => (stderr += chunk)) + return proc.on('exit', () => { + if (stderr.trim() === '0 (0)') { + // remove output diff if test matches expected image + fs.unlink(diff_file, err => { + if (err) { + throw err + } + }) + return callback(null, true) + } else { + console.log('compare result', stderr) + return callback(null, false) + } + }) +} const checkPdfInfo = function(pdfPath, callback) { - if (callback == null) { callback = function(error, output) {}; } - const proc = ChildProcess.exec(`pdfinfo ${fixturePath(pdfPath)}`); - let stdout = ""; - proc.stdout.on("data", chunk => stdout += chunk); - proc.stderr.on("data", chunk => console.log("STDERR", chunk.toString())); - return proc.on("exit", () => { - if (stdout.match(/Optimized:\s+yes/)) { - return callback(null, true); - } else { - return callback(null, false); - } - }); -}; + if (callback == null) { + callback = function(error, output) {} + } + const proc = ChildProcess.exec(`pdfinfo ${fixturePath(pdfPath)}`) + let stdout = '' + proc.stdout.on('data', chunk => (stdout += chunk)) + proc.stderr.on('data', chunk => console.log('STDERR', chunk.toString())) + return proc.on('exit', () => { + if (stdout.match(/Optimized:\s+yes/)) { + return callback(null, true) + } else { + return callback(null, false) + } + }) +} const compareMultiplePages = function(project_id, callback) { - if (callback == null) { callback = function(error) {}; } - var compareNext = function(page_no, callback) { - const path = `tmp/${project_id}-source-${page_no}.png`; - return fs.stat(fixturePath(path), (error, stat) => { - if (error != null) { - return callback(); - } else { - return compare(`tmp/${project_id}-source-${page_no}.png`, `tmp/${project_id}-generated-${page_no}.png`, (error, same) => { - if (error != null) { throw error; } - same.should.equal(true); - return compareNext(page_no + 1, callback); - }); - } - }); - }; - return compareNext(0, callback); -}; + if (callback == null) { + callback = function(error) {} + } + var compareNext = function(page_no, callback) { + const path = `tmp/${project_id}-source-${page_no}.png` + return fs.stat(fixturePath(path), (error, stat) => { + if (error != null) { + return callback() + } else { + return compare( + `tmp/${project_id}-source-${page_no}.png`, + `tmp/${project_id}-generated-${page_no}.png`, + (error, same) => { + if (error != null) { + throw error + } + same.should.equal(true) + return compareNext(page_no + 1, callback) + } + ) + } + }) + } + return compareNext(0, callback) +} const comparePdf = function(project_id, example_dir, callback) { - if (callback == null) { callback = function(error) {}; } - console.log("CONVERT"); - console.log(`tmp/${project_id}.pdf`, `tmp/${project_id}-generated.png`); - return convertToPng(`tmp/${project_id}.pdf`, `tmp/${project_id}-generated.png`, error => { - if (error != null) { throw error; } - return convertToPng(`examples/${example_dir}/output.pdf`, `tmp/${project_id}-source.png`, error => { - if (error != null) { throw error; } - return fs.stat(fixturePath(`tmp/${project_id}-source-0.png`), (error, stat) => { - if (error != null) { - return compare(`tmp/${project_id}-source.png`, `tmp/${project_id}-generated.png`, (error, same) => { - if (error != null) { throw error; } - same.should.equal(true); - return callback(); - }); - } else { - return compareMultiplePages(project_id, (error) => { - if (error != null) { throw error; } - return callback(); - }); - } - }); - }); - }); -}; + if (callback == null) { + callback = function(error) {} + } + console.log('CONVERT') + console.log(`tmp/${project_id}.pdf`, `tmp/${project_id}-generated.png`) + return convertToPng( + `tmp/${project_id}.pdf`, + `tmp/${project_id}-generated.png`, + error => { + if (error != null) { + throw error + } + return convertToPng( + `examples/${example_dir}/output.pdf`, + `tmp/${project_id}-source.png`, + error => { + if (error != null) { + throw error + } + return fs.stat( + fixturePath(`tmp/${project_id}-source-0.png`), + (error, stat) => { + if (error != null) { + return compare( + `tmp/${project_id}-source.png`, + `tmp/${project_id}-generated.png`, + (error, same) => { + if (error != null) { + throw error + } + same.should.equal(true) + return callback() + } + ) + } else { + return compareMultiplePages(project_id, error => { + if (error != null) { + throw error + } + return callback() + }) + } + } + ) + } + ) + } + ) +} const downloadAndComparePdf = function(project_id, example_dir, url, callback) { - if (callback == null) { callback = function(error) {}; } - const writeStream = fs.createWriteStream(fixturePath(`tmp/${project_id}.pdf`)); - request.get(url).pipe(writeStream); - console.log("writing file out", fixturePath(`tmp/${project_id}.pdf`)); - return writeStream.on("close", () => { - return checkPdfInfo(`tmp/${project_id}.pdf`, (error, optimised) => { - if (error != null) { throw error; } - optimised.should.equal(true); - return comparePdf(project_id, example_dir, callback); - }); - }); -}; + if (callback == null) { + callback = function(error) {} + } + const writeStream = fs.createWriteStream(fixturePath(`tmp/${project_id}.pdf`)) + request.get(url).pipe(writeStream) + console.log('writing file out', fixturePath(`tmp/${project_id}.pdf`)) + return writeStream.on('close', () => { + return checkPdfInfo(`tmp/${project_id}.pdf`, (error, optimised) => { + if (error != null) { + throw error + } + optimised.should.equal(true) + return comparePdf(project_id, example_dir, callback) + }) + }) +} -Client.runServer(4242, fixturePath("examples")); +Client.runServer(4242, fixturePath('examples')) -describe("Example Documents", function() { - before(function(done) { return ChildProcess.exec("rm test/acceptance/fixtures/tmp/*").on("exit", () => ClsiApp.ensureRunning(done)); } - ); +describe('Example Documents', function() { + before(function(done) { + return ChildProcess.exec('rm test/acceptance/fixtures/tmp/*').on( + 'exit', + () => ClsiApp.ensureRunning(done) + ) + }) + return Array.from(fs.readdirSync(fixturePath('examples'))).map(example_dir => + (example_dir => + describe(example_dir, function() { + before(function() { + return (this.project_id = Client.randomId() + '_' + example_dir) + }) - return Array.from(fs.readdirSync(fixturePath("examples"))).map((example_dir) => - (example_dir => - describe(example_dir, function() { - before(function() { - return this.project_id = Client.randomId() + "_" + example_dir; - }); - - it("should generate the correct pdf", function(done) { - this.timeout(MOCHA_LATEX_TIMEOUT); - return Client.compileDirectory(this.project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => { - if (error || (__guard__(body != null ? body.compile : undefined, x => x.status) === "failure")) { - console.log("DEBUG: error", error, "body", JSON.stringify(body)); - } - const pdf = Client.getOutputFile(body, "pdf"); - return downloadAndComparePdf(this.project_id, example_dir, pdf.url, done); - }); - }); - - return it("should generate the correct pdf on the second run as well", function(done) { - this.timeout(MOCHA_LATEX_TIMEOUT); - return Client.compileDirectory(this.project_id, fixturePath("examples"), example_dir, 4242, (error, res, body) => { - if (error || (__guard__(body != null ? body.compile : undefined, x => x.status) === "failure")) { - console.log("DEBUG: error", error, "body", JSON.stringify(body)); - } - const pdf = Client.getOutputFile(body, "pdf"); - return downloadAndComparePdf(this.project_id, example_dir, pdf.url, done); - }); - }); - }) - )(example_dir)); -}); - + it('should generate the correct pdf', function(done) { + this.timeout(MOCHA_LATEX_TIMEOUT) + return Client.compileDirectory( + this.project_id, + fixturePath('examples'), + example_dir, + 4242, + (error, res, body) => { + if ( + error || + __guard__( + body != null ? body.compile : undefined, + x => x.status + ) === 'failure' + ) { + console.log('DEBUG: error', error, 'body', JSON.stringify(body)) + } + const pdf = Client.getOutputFile(body, 'pdf') + return downloadAndComparePdf( + this.project_id, + example_dir, + pdf.url, + done + ) + } + ) + }) + return it('should generate the correct pdf on the second run as well', function(done) { + this.timeout(MOCHA_LATEX_TIMEOUT) + return Client.compileDirectory( + this.project_id, + fixturePath('examples'), + example_dir, + 4242, + (error, res, body) => { + if ( + error || + __guard__( + body != null ? body.compile : undefined, + x => x.status + ) === 'failure' + ) { + console.log('DEBUG: error', error, 'body', JSON.stringify(body)) + } + const pdf = Client.getOutputFile(body, 'pdf') + return downloadAndComparePdf( + this.project_id, + example_dir, + pdf.url, + done + ) + } + ) + }) + }))(example_dir) + ) +}) function __guard__(value, transform) { - return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; -} \ No newline at end of file + return typeof value !== 'undefined' && value !== null + ? transform(value) + : undefined +} diff --git a/services/clsi/test/acceptance/js/SimpleLatexFileTests.js b/services/clsi/test/acceptance/js/SimpleLatexFileTests.js index d774301dd0..447e1b635a 100644 --- a/services/clsi/test/acceptance/js/SimpleLatexFileTests.js +++ b/services/clsi/test/acceptance/js/SimpleLatexFileTests.js @@ -8,55 +8,64 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const Client = require("./helpers/Client"); -const request = require("request"); -require("chai").should(); -const ClsiApp = require("./helpers/ClsiApp"); +const Client = require('./helpers/Client') +const request = require('request') +require('chai').should() +const ClsiApp = require('./helpers/ClsiApp') -describe("Simple LaTeX file", function() { - before(function(done) { - this.project_id = Client.randomId(); - this.request = { - resources: [{ - path: "main.tex", - content: `\ +describe('Simple LaTeX file', function() { + before(function(done) { + this.project_id = Client.randomId() + this.request = { + resources: [ + { + path: 'main.tex', + content: `\ \\documentclass{article} \\begin{document} Hello world \\end{document}\ ` - } - ] - }; - return ClsiApp.ensureRunning(() => { - return Client.compile(this.project_id, this.request, (error, res, body) => { this.error = error; this.res = res; this.body = body; return done(); }); - }); - }); + } + ] + } + return ClsiApp.ensureRunning(() => { + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + return done() + } + ) + }) + }) - it("should return the PDF", function() { - const pdf = Client.getOutputFile(this.body, "pdf"); - return pdf.type.should.equal("pdf"); - }); - - it("should return the log", function() { - const log = Client.getOutputFile(this.body, "log"); - return log.type.should.equal("log"); - }); + it('should return the PDF', function() { + const pdf = Client.getOutputFile(this.body, 'pdf') + return pdf.type.should.equal('pdf') + }) - it("should provide the pdf for download", function(done) { - const pdf = Client.getOutputFile(this.body, "pdf"); - return request.get(pdf.url, (error, res, body) => { - res.statusCode.should.equal(200); - return done(); - }); - }); - - return it("should provide the log for download", function(done) { - const log = Client.getOutputFile(this.body, "pdf"); - return request.get(log.url, (error, res, body) => { - res.statusCode.should.equal(200); - return done(); - }); - }); -}); - + it('should return the log', function() { + const log = Client.getOutputFile(this.body, 'log') + return log.type.should.equal('log') + }) + + it('should provide the pdf for download', function(done) { + const pdf = Client.getOutputFile(this.body, 'pdf') + return request.get(pdf.url, (error, res, body) => { + res.statusCode.should.equal(200) + return done() + }) + }) + + return it('should provide the log for download', function(done) { + const log = Client.getOutputFile(this.body, 'pdf') + return request.get(log.url, (error, res, body) => { + res.statusCode.should.equal(200) + return done() + }) + }) +}) diff --git a/services/clsi/test/acceptance/js/SynctexTests.js b/services/clsi/test/acceptance/js/SynctexTests.js index d8879ebc0f..4860c6040c 100644 --- a/services/clsi/test/acceptance/js/SynctexTests.js +++ b/services/clsi/test/acceptance/js/SynctexTests.js @@ -9,53 +9,83 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const Client = require("./helpers/Client"); -const request = require("request"); -require("chai").should(); -const { expect } = require("chai"); -const ClsiApp = require("./helpers/ClsiApp"); -const crypto = require("crypto"); +const Client = require('./helpers/Client') +const request = require('request') +require('chai').should() +const { expect } = require('chai') +const ClsiApp = require('./helpers/ClsiApp') +const crypto = require('crypto') -describe("Syncing", function() { - before(function(done) { - const content = `\ +describe('Syncing', function() { + before(function(done) { + const content = `\ \\documentclass{article} \\begin{document} Hello world \\end{document}\ -`; - this.request = { - resources: [{ - path: "main.tex", - content - } - ] - }; - this.project_id = Client.randomId(); - return ClsiApp.ensureRunning(() => { - return Client.compile(this.project_id, this.request, (error, res, body) => { this.error = error; this.res = res; this.body = body; return done(); }); - }); - }); +` + this.request = { + resources: [ + { + path: 'main.tex', + content + } + ] + } + this.project_id = Client.randomId() + return ClsiApp.ensureRunning(() => { + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + return done() + } + ) + }) + }) - describe("from code to pdf", function() { return it("should return the correct location", function(done) { - return Client.syncFromCode(this.project_id, "main.tex", 3, 5, (error, pdfPositions) => { - if (error != null) { throw error; } - expect(pdfPositions).to.deep.equal({ - pdf: [ { page: 1, h: 133.77, v: 134.76, height: 6.92, width: 343.71 } ] - }); - return done(); - }); - }); } - ); + describe('from code to pdf', function() { + return it('should return the correct location', function(done) { + return Client.syncFromCode( + this.project_id, + 'main.tex', + 3, + 5, + (error, pdfPositions) => { + if (error != null) { + throw error + } + expect(pdfPositions).to.deep.equal({ + pdf: [ + { page: 1, h: 133.77, v: 134.76, height: 6.92, width: 343.71 } + ] + }) + return done() + } + ) + }) + }) - return describe("from pdf to code", function() { return it("should return the correct location", function(done) { - return Client.syncFromPdf(this.project_id, 1, 100, 200, (error, codePositions) => { - if (error != null) { throw error; } - expect(codePositions).to.deep.equal({ - code: [ { file: 'main.tex', line: 3, column: -1 } ] - }); - return done(); - }); - }); } - ); -}); + return describe('from pdf to code', function() { + return it('should return the correct location', function(done) { + return Client.syncFromPdf( + this.project_id, + 1, + 100, + 200, + (error, codePositions) => { + if (error != null) { + throw error + } + expect(codePositions).to.deep.equal({ + code: [{ file: 'main.tex', line: 3, column: -1 }] + }) + return done() + } + ) + }) + }) +}) diff --git a/services/clsi/test/acceptance/js/TimeoutTests.js b/services/clsi/test/acceptance/js/TimeoutTests.js index 7f8f84835a..f6812e8c6e 100644 --- a/services/clsi/test/acceptance/js/TimeoutTests.js +++ b/services/clsi/test/acceptance/js/TimeoutTests.js @@ -8,46 +8,55 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const Client = require("./helpers/Client"); -const request = require("request"); -require("chai").should(); -const ClsiApp = require("./helpers/ClsiApp"); +const Client = require('./helpers/Client') +const request = require('request') +require('chai').should() +const ClsiApp = require('./helpers/ClsiApp') - -describe("Timed out compile", function() { - before(function(done) { - this.request = { - options: { - timeout: 10 - }, // seconds - resources: [{ - path: "main.tex", - content: `\ +describe('Timed out compile', function() { + before(function(done) { + this.request = { + options: { + timeout: 10 + }, // seconds + resources: [ + { + path: 'main.tex', + content: `\ \\documentclass{article} \\begin{document} \\def\\x{Hello!\\par\\x} \\x \\end{document}\ ` - } - ] - }; - this.project_id = Client.randomId(); - return ClsiApp.ensureRunning(() => { - return Client.compile(this.project_id, this.request, (error, res, body) => { this.error = error; this.res = res; this.body = body; return done(); }); - }); - }); + } + ] + } + this.project_id = Client.randomId() + return ClsiApp.ensureRunning(() => { + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + return done() + } + ) + }) + }) - it("should return a timeout error", function() { - return this.body.compile.error.should.equal("container timed out"); - }); + it('should return a timeout error', function() { + return this.body.compile.error.should.equal('container timed out') + }) - it("should return a timedout status", function() { - return this.body.compile.status.should.equal("timedout"); - }); + it('should return a timedout status', function() { + return this.body.compile.status.should.equal('timedout') + }) - return it("should return the log output file name", function() { - const outputFilePaths = this.body.compile.outputFiles.map(x => x.path); - return outputFilePaths.should.include('output.log'); - }); -}); + return it('should return the log output file name', function() { + const outputFilePaths = this.body.compile.outputFiles.map(x => x.path) + return outputFilePaths.should.include('output.log') + }) +}) diff --git a/services/clsi/test/acceptance/js/UrlCachingTests.js b/services/clsi/test/acceptance/js/UrlCachingTests.js index 7bb0a2055e..4d6249784c 100644 --- a/services/clsi/test/acceptance/js/UrlCachingTests.js +++ b/services/clsi/test/acceptance/js/UrlCachingTests.js @@ -10,277 +10,364 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const Client = require("./helpers/Client"); -const request = require("request"); -require("chai").should(); -const sinon = require("sinon"); -const ClsiApp = require("./helpers/ClsiApp"); +const Client = require('./helpers/Client') +const request = require('request') +require('chai').should() +const sinon = require('sinon') +const ClsiApp = require('./helpers/ClsiApp') -const host = "localhost"; +const host = 'localhost' const Server = { - run() { - const express = require("express"); - const app = express(); + run() { + const express = require('express') + const app = express() - const staticServer = express.static(__dirname + "/../fixtures/"); - app.get("/:random_id/*", (req, res, next) => { - this.getFile(req.url); - req.url = `/${req.params[0]}`; - return staticServer(req, res, next); - }); + const staticServer = express.static(__dirname + '/../fixtures/') + app.get('/:random_id/*', (req, res, next) => { + this.getFile(req.url) + req.url = `/${req.params[0]}` + return staticServer(req, res, next) + }) - return app.listen(31415, host); - }, + return app.listen(31415, host) + }, - getFile() {}, + getFile() {}, - randomId() { - return Math.random().toString(16).slice(2); - } -}; + randomId() { + return Math.random() + .toString(16) + .slice(2) + } +} -Server.run(); +Server.run() -describe("Url Caching", function() { - describe("Downloading an image for the first time", function() { - before(function(done) { - this.project_id = Client.randomId(); - this.file = `${Server.randomId()}/lion.png`; - this.request = { - resources: [{ - path: "main.tex", - content: `\ +describe('Url Caching', function() { + describe('Downloading an image for the first time', function() { + before(function(done) { + this.project_id = Client.randomId() + this.file = `${Server.randomId()}/lion.png` + this.request = { + resources: [ + { + path: 'main.tex', + content: `\ \\documentclass{article} \\usepackage{graphicx} \\begin{document} \\includegraphics{lion.png} \\end{document}\ ` - }, { - path: "lion.png", - url: `http://${host}:31415/${this.file}` - }] - }; + }, + { + path: 'lion.png', + url: `http://${host}:31415/${this.file}` + } + ] + } - sinon.spy(Server, "getFile"); - return ClsiApp.ensureRunning(() => { - return Client.compile(this.project_id, this.request, (error, res, body) => { this.error = error; this.res = res; this.body = body; return done(); }); - }); - }); + sinon.spy(Server, 'getFile') + return ClsiApp.ensureRunning(() => { + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + return done() + } + ) + }) + }) - afterEach(function() { return Server.getFile.restore(); }); + afterEach(function() { + return Server.getFile.restore() + }) - return it("should download the image", function() { - return Server.getFile - .calledWith(`/${this.file}`) - .should.equal(true); - }); - }); - - describe("When an image is in the cache and the last modified date is unchanged", function() { - before(function(done) { - this.project_id = Client.randomId(); - this.file = `${Server.randomId()}/lion.png`; - this.request = { - resources: [{ - path: "main.tex", - content: `\ + return it('should download the image', function() { + return Server.getFile.calledWith(`/${this.file}`).should.equal(true) + }) + }) + + describe('When an image is in the cache and the last modified date is unchanged', function() { + before(function(done) { + this.project_id = Client.randomId() + this.file = `${Server.randomId()}/lion.png` + this.request = { + resources: [ + { + path: 'main.tex', + content: `\ \\documentclass{article} \\usepackage{graphicx} \\begin{document} \\includegraphics{lion.png} \\end{document}\ ` - }, (this.image_resource = { - path: "lion.png", - url: `http://${host}:31415/${this.file}`, - modified: Date.now() - })] - }; + }, + (this.image_resource = { + path: 'lion.png', + url: `http://${host}:31415/${this.file}`, + modified: Date.now() + }) + ] + } - return Client.compile(this.project_id, this.request, (error, res, body) => { - this.error = error; - this.res = res; - this.body = body; - sinon.spy(Server, "getFile"); - return Client.compile(this.project_id, this.request, (error1, res1, body1) => { - this.error = error1; - this.res = res1; - this.body = body1; - return done(); - }); - }); - }); + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + sinon.spy(Server, 'getFile') + return Client.compile( + this.project_id, + this.request, + (error1, res1, body1) => { + this.error = error1 + this.res = res1 + this.body = body1 + return done() + } + ) + } + ) + }) - after(function() { return Server.getFile.restore(); }); + after(function() { + return Server.getFile.restore() + }) - return it("should not download the image again", function() { return Server.getFile.called.should.equal(false); }); - }); + return it('should not download the image again', function() { + return Server.getFile.called.should.equal(false) + }) + }) - describe("When an image is in the cache and the last modified date is advanced", function() { - before(function(done) { - this.project_id = Client.randomId(); - this.file = `${Server.randomId()}/lion.png`; - this.request = { - resources: [{ - path: "main.tex", - content: `\ + describe('When an image is in the cache and the last modified date is advanced', function() { + before(function(done) { + this.project_id = Client.randomId() + this.file = `${Server.randomId()}/lion.png` + this.request = { + resources: [ + { + path: 'main.tex', + content: `\ \\documentclass{article} \\usepackage{graphicx} \\begin{document} \\includegraphics{lion.png} \\end{document}\ ` - }, (this.image_resource = { - path: "lion.png", - url: `http://${host}:31415/${this.file}`, - modified: (this.last_modified = Date.now()) - })] - }; + }, + (this.image_resource = { + path: 'lion.png', + url: `http://${host}:31415/${this.file}`, + modified: (this.last_modified = Date.now()) + }) + ] + } - return Client.compile(this.project_id, this.request, (error, res, body) => { - this.error = error; - this.res = res; - this.body = body; - sinon.spy(Server, "getFile"); - this.image_resource.modified = new Date(this.last_modified + 3000); - return Client.compile(this.project_id, this.request, (error1, res1, body1) => { - this.error = error1; - this.res = res1; - this.body = body1; - return done(); - }); - }); - }); + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + sinon.spy(Server, 'getFile') + this.image_resource.modified = new Date(this.last_modified + 3000) + return Client.compile( + this.project_id, + this.request, + (error1, res1, body1) => { + this.error = error1 + this.res = res1 + this.body = body1 + return done() + } + ) + } + ) + }) - afterEach(function() { return Server.getFile.restore(); }); + afterEach(function() { + return Server.getFile.restore() + }) - return it("should download the image again", function() { return Server.getFile.called.should.equal(true); }); - }); + return it('should download the image again', function() { + return Server.getFile.called.should.equal(true) + }) + }) - describe("When an image is in the cache and the last modified date is further in the past", function() { - before(function(done) { - this.project_id = Client.randomId(); - this.file = `${Server.randomId()}/lion.png`; - this.request = { - resources: [{ - path: "main.tex", - content: `\ + describe('When an image is in the cache and the last modified date is further in the past', function() { + before(function(done) { + this.project_id = Client.randomId() + this.file = `${Server.randomId()}/lion.png` + this.request = { + resources: [ + { + path: 'main.tex', + content: `\ \\documentclass{article} \\usepackage{graphicx} \\begin{document} \\includegraphics{lion.png} \\end{document}\ ` - }, (this.image_resource = { - path: "lion.png", - url: `http://${host}:31415/${this.file}`, - modified: (this.last_modified = Date.now()) - })] - }; + }, + (this.image_resource = { + path: 'lion.png', + url: `http://${host}:31415/${this.file}`, + modified: (this.last_modified = Date.now()) + }) + ] + } - return Client.compile(this.project_id, this.request, (error, res, body) => { - this.error = error; - this.res = res; - this.body = body; - sinon.spy(Server, "getFile"); - this.image_resource.modified = new Date(this.last_modified - 3000); - return Client.compile(this.project_id, this.request, (error1, res1, body1) => { - this.error = error1; - this.res = res1; - this.body = body1; - return done(); - }); - }); - }); + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + sinon.spy(Server, 'getFile') + this.image_resource.modified = new Date(this.last_modified - 3000) + return Client.compile( + this.project_id, + this.request, + (error1, res1, body1) => { + this.error = error1 + this.res = res1 + this.body = body1 + return done() + } + ) + } + ) + }) - afterEach(function() { return Server.getFile.restore(); }); + afterEach(function() { + return Server.getFile.restore() + }) - return it("should not download the image again", function() { return Server.getFile.called.should.equal(false); }); - }); + return it('should not download the image again', function() { + return Server.getFile.called.should.equal(false) + }) + }) - describe("When an image is in the cache and the last modified date is not specified", function() { - before(function(done) { - this.project_id = Client.randomId(); - this.file = `${Server.randomId()}/lion.png`; - this.request = { - resources: [{ - path: "main.tex", - content: `\ + describe('When an image is in the cache and the last modified date is not specified', function() { + before(function(done) { + this.project_id = Client.randomId() + this.file = `${Server.randomId()}/lion.png` + this.request = { + resources: [ + { + path: 'main.tex', + content: `\ \\documentclass{article} \\usepackage{graphicx} \\begin{document} \\includegraphics{lion.png} \\end{document}\ ` - }, (this.image_resource = { - path: "lion.png", - url: `http://${host}:31415/${this.file}`, - modified: (this.last_modified = Date.now()) - })] - }; + }, + (this.image_resource = { + path: 'lion.png', + url: `http://${host}:31415/${this.file}`, + modified: (this.last_modified = Date.now()) + }) + ] + } - return Client.compile(this.project_id, this.request, (error, res, body) => { - this.error = error; - this.res = res; - this.body = body; - sinon.spy(Server, "getFile"); - delete this.image_resource.modified; - return Client.compile(this.project_id, this.request, (error1, res1, body1) => { - this.error = error1; - this.res = res1; - this.body = body1; - return done(); - }); - }); - }); + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + sinon.spy(Server, 'getFile') + delete this.image_resource.modified + return Client.compile( + this.project_id, + this.request, + (error1, res1, body1) => { + this.error = error1 + this.res = res1 + this.body = body1 + return done() + } + ) + } + ) + }) - afterEach(function() { return Server.getFile.restore(); }); + afterEach(function() { + return Server.getFile.restore() + }) - return it("should download the image again", function() { return Server.getFile.called.should.equal(true); }); - }); - - return describe("After clearing the cache", function() { - before(function(done) { - this.project_id = Client.randomId(); - this.file = `${Server.randomId()}/lion.png`; - this.request = { - resources: [{ - path: "main.tex", - content: `\ + return it('should download the image again', function() { + return Server.getFile.called.should.equal(true) + }) + }) + + return describe('After clearing the cache', function() { + before(function(done) { + this.project_id = Client.randomId() + this.file = `${Server.randomId()}/lion.png` + this.request = { + resources: [ + { + path: 'main.tex', + content: `\ \\documentclass{article} \\usepackage{graphicx} \\begin{document} \\includegraphics{lion.png} \\end{document}\ ` - }, (this.image_resource = { - path: "lion.png", - url: `http://${host}:31415/${this.file}`, - modified: (this.last_modified = Date.now()) - })] - }; + }, + (this.image_resource = { + path: 'lion.png', + url: `http://${host}:31415/${this.file}`, + modified: (this.last_modified = Date.now()) + }) + ] + } - return Client.compile(this.project_id, this.request, error => { - if (error != null) { throw error; } - return Client.clearCache(this.project_id, (error, res, body) => { - if (error != null) { throw error; } - sinon.spy(Server, "getFile"); - return Client.compile(this.project_id, this.request, (error1, res1, body1) => { - this.error = error1; - this.res = res1; - this.body = body1; - return done(); - }); - }); - }); - }); + return Client.compile(this.project_id, this.request, error => { + if (error != null) { + throw error + } + return Client.clearCache(this.project_id, (error, res, body) => { + if (error != null) { + throw error + } + sinon.spy(Server, 'getFile') + return Client.compile( + this.project_id, + this.request, + (error1, res1, body1) => { + this.error = error1 + this.res = res1 + this.body = body1 + return done() + } + ) + }) + }) + }) - afterEach(function() { return Server.getFile.restore(); }); + afterEach(function() { + return Server.getFile.restore() + }) - return it("should download the image again", function() { return Server.getFile.called.should.equal(true); }); - }); -}); - - + return it('should download the image again', function() { + return Server.getFile.called.should.equal(true) + }) + }) +}) diff --git a/services/clsi/test/acceptance/js/WordcountTests.js b/services/clsi/test/acceptance/js/WordcountTests.js index 2f81e1398d..8721857030 100644 --- a/services/clsi/test/acceptance/js/WordcountTests.js +++ b/services/clsi/test/acceptance/js/WordcountTests.js @@ -9,48 +9,64 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const Client = require("./helpers/Client"); -const request = require("request"); -require("chai").should(); -const { expect } = require("chai"); -const path = require("path"); -const fs = require("fs"); -const ClsiApp = require("./helpers/ClsiApp"); +const Client = require('./helpers/Client') +const request = require('request') +require('chai').should() +const { expect } = require('chai') +const path = require('path') +const fs = require('fs') +const ClsiApp = require('./helpers/ClsiApp') -describe("Syncing", function() { - before(function(done) { - this.request = { - resources: [{ - path: "main.tex", - content: fs.readFileSync(path.join(__dirname,"../fixtures/naugty_strings.txt"),"utf-8") - } - ] - }; - this.project_id = Client.randomId(); - return ClsiApp.ensureRunning(() => { - return Client.compile(this.project_id, this.request, (error, res, body) => { this.error = error; this.res = res; this.body = body; return done(); }); - }); - }); +describe('Syncing', function() { + before(function(done) { + this.request = { + resources: [ + { + path: 'main.tex', + content: fs.readFileSync( + path.join(__dirname, '../fixtures/naugty_strings.txt'), + 'utf-8' + ) + } + ] + } + this.project_id = Client.randomId() + return ClsiApp.ensureRunning(() => { + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + return done() + } + ) + }) + }) - return describe("wordcount file", function() { return it("should return wordcount info", function(done) { - return Client.wordcount(this.project_id, "main.tex", (error, result) => { - if (error != null) { throw error; } - expect(result).to.deep.equal({ - texcount: { - encode: "utf8", - textWords: 2281, - headWords: 2, - outside: 0, - headers: 2, - elements: 0, - mathInline: 6, - mathDisplay: 0, - errors: 0, - messages: "" - } - }); - return done(); - }); - }); } - ); -}); + return describe('wordcount file', function() { + return it('should return wordcount info', function(done) { + return Client.wordcount(this.project_id, 'main.tex', (error, result) => { + if (error != null) { + throw error + } + expect(result).to.deep.equal({ + texcount: { + encode: 'utf8', + textWords: 2281, + headWords: 2, + outside: 0, + headers: 2, + elements: 0, + mathInline: 6, + mathDisplay: 0, + errors: 0, + messages: '' + } + }) + return done() + }) + }) + }) +}) diff --git a/services/clsi/test/acceptance/js/helpers/Client.js b/services/clsi/test/acceptance/js/helpers/Client.js index 50e75d6148..9f430e3535 100644 --- a/services/clsi/test/acceptance/js/helpers/Client.js +++ b/services/clsi/test/acceptance/js/helpers/Client.js @@ -12,143 +12,197 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let Client; -const request = require("request"); -const fs = require("fs"); -const Settings = require("settings-sharelatex"); +let Client +const request = require('request') +const fs = require('fs') +const Settings = require('settings-sharelatex') -const host = "localhost"; +const host = 'localhost' -module.exports = (Client = { - host: Settings.apis.clsi.url, +module.exports = Client = { + host: Settings.apis.clsi.url, - randomId() { - return Math.random().toString(16).slice(2); - }, + randomId() { + return Math.random() + .toString(16) + .slice(2) + }, - compile(project_id, data, callback) { - if (callback == null) { callback = function(error, res, body) {}; } - return request.post({ - url: `${this.host}/project/${project_id}/compile`, - json: { - compile: data - } - }, callback); - }, + compile(project_id, data, callback) { + if (callback == null) { + callback = function(error, res, body) {} + } + return request.post( + { + url: `${this.host}/project/${project_id}/compile`, + json: { + compile: data + } + }, + callback + ) + }, - clearCache(project_id, callback) { - if (callback == null) { callback = function(error, res, body) {}; } - return request.del(`${this.host}/project/${project_id}`, callback); - }, + clearCache(project_id, callback) { + if (callback == null) { + callback = function(error, res, body) {} + } + return request.del(`${this.host}/project/${project_id}`, callback) + }, - getOutputFile(response, type) { - for (const file of Array.from(response.compile.outputFiles)) { - if ((file.type === type) && file.url.match(`output.${type}`)) { - return file; - } - } - return null; - }, + getOutputFile(response, type) { + for (const file of Array.from(response.compile.outputFiles)) { + if (file.type === type && file.url.match(`output.${type}`)) { + return file + } + } + return null + }, - runServer(port, directory) { - const express = require("express"); - const app = express(); - app.use(express.static(directory)); - console.log("starting test server on", port, host); - return app.listen(port, host).on("error", (error) => { - console.error("error starting server:", error.message); - return process.exit(1); - }); - }, + runServer(port, directory) { + const express = require('express') + const app = express() + app.use(express.static(directory)) + console.log('starting test server on', port, host) + return app.listen(port, host).on('error', error => { + console.error('error starting server:', error.message) + return process.exit(1) + }) + }, + syncFromCode(project_id, file, line, column, callback) { + if (callback == null) { + callback = function(error, pdfPositions) {} + } + return request.get( + { + url: `${this.host}/project/${project_id}/sync/code`, + qs: { + file, + line, + column + } + }, + (error, response, body) => { + if (error != null) { + return callback(error) + } + return callback(null, JSON.parse(body)) + } + ) + }, - syncFromCode(project_id, file, line, column, callback) { - if (callback == null) { callback = function(error, pdfPositions) {}; } - return request.get({ - url: `${this.host}/project/${project_id}/sync/code`, - qs: { - file, - line, - column - } - }, (error, response, body) => { - if (error != null) { return callback(error); } - return callback(null, JSON.parse(body)); - }); - }, + syncFromPdf(project_id, page, h, v, callback) { + if (callback == null) { + callback = function(error, pdfPositions) {} + } + return request.get( + { + url: `${this.host}/project/${project_id}/sync/pdf`, + qs: { + page, + h, + v + } + }, + (error, response, body) => { + if (error != null) { + return callback(error) + } + return callback(null, JSON.parse(body)) + } + ) + }, - syncFromPdf(project_id, page, h, v, callback) { - if (callback == null) { callback = function(error, pdfPositions) {}; } - return request.get({ - url: `${this.host}/project/${project_id}/sync/pdf`, - qs: { - page, - h, v - } - }, (error, response, body) => { - if (error != null) { return callback(error); } - return callback(null, JSON.parse(body)); - }); - }, + compileDirectory(project_id, baseDirectory, directory, serverPort, callback) { + if (callback == null) { + callback = function(error, res, body) {} + } + const resources = [] + let entities = fs.readdirSync(`${baseDirectory}/${directory}`) + let rootResourcePath = 'main.tex' + while (entities.length > 0) { + var entity = entities.pop() + const stat = fs.statSync(`${baseDirectory}/${directory}/${entity}`) + if (stat.isDirectory()) { + entities = entities.concat( + fs + .readdirSync(`${baseDirectory}/${directory}/${entity}`) + .map(subEntity => { + if (subEntity === 'main.tex') { + rootResourcePath = `${entity}/${subEntity}` + } + return `${entity}/${subEntity}` + }) + ) + } else if (stat.isFile() && entity !== 'output.pdf') { + const extension = entity.split('.').pop() + if ( + [ + 'tex', + 'bib', + 'cls', + 'sty', + 'pdf_tex', + 'Rtex', + 'ist', + 'md', + 'Rmd' + ].indexOf(extension) > -1 + ) { + resources.push({ + path: entity, + content: fs + .readFileSync(`${baseDirectory}/${directory}/${entity}`) + .toString() + }) + } else if ( + ['eps', 'ttf', 'png', 'jpg', 'pdf', 'jpeg'].indexOf(extension) > -1 + ) { + resources.push({ + path: entity, + url: `http://${host}:${serverPort}/${directory}/${entity}`, + modified: stat.mtime + }) + } + } + } - compileDirectory(project_id, baseDirectory, directory, serverPort, callback) { - if (callback == null) { callback = function(error, res, body) {}; } - const resources = []; - let entities = fs.readdirSync(`${baseDirectory}/${directory}`); - let rootResourcePath = "main.tex"; - while (entities.length > 0) { - var entity = entities.pop(); - const stat = fs.statSync(`${baseDirectory}/${directory}/${entity}`); - if (stat.isDirectory()) { - entities = entities.concat(fs.readdirSync(`${baseDirectory}/${directory}/${entity}`).map((subEntity) => { - if (subEntity === "main.tex") { - rootResourcePath = `${entity}/${subEntity}`; - } - return `${entity}/${subEntity}`; - }) - ); - } else if (stat.isFile() && (entity !== "output.pdf")) { - const extension = entity.split(".").pop(); - if (["tex", "bib", "cls", "sty", "pdf_tex", "Rtex", "ist", "md", "Rmd"].indexOf(extension) > -1) { - resources.push({ - path: entity, - content: fs.readFileSync(`${baseDirectory}/${directory}/${entity}`).toString() - }); - } else if (["eps", "ttf", "png", "jpg", "pdf", "jpeg"].indexOf(extension) > -1) { - resources.push({ - path: entity, - url: `http://${host}:${serverPort}/${directory}/${entity}`, - modified: stat.mtime - }); - } - } - } + return fs.readFile( + `${baseDirectory}/${directory}/options.json`, + (error, body) => { + const req = { + resources, + rootResourcePath + } - return fs.readFile(`${baseDirectory}/${directory}/options.json`, (error, body) => { - const req = { - resources, - rootResourcePath - }; + if (error == null) { + body = JSON.parse(body) + req.options = body + } - if ((error == null)) { - body = JSON.parse(body); - req.options = body; - } + return this.compile(project_id, req, callback) + } + ) + }, - return this.compile(project_id, req, callback); - }); - }, - - wordcount(project_id, file, callback) { - if (callback == null) { callback = function(error, pdfPositions) {}; } - return request.get({ - url: `${this.host}/project/${project_id}/wordcount`, - qs: { - file - } - }, (error, response, body) => { - if (error != null) { return callback(error); } - return callback(null, JSON.parse(body)); - }); - } -}); + wordcount(project_id, file, callback) { + if (callback == null) { + callback = function(error, pdfPositions) {} + } + return request.get( + { + url: `${this.host}/project/${project_id}/wordcount`, + qs: { + file + } + }, + (error, response, body) => { + if (error != null) { + return callback(error) + } + return callback(null, JSON.parse(body)) + } + ) + } +} diff --git a/services/clsi/test/acceptance/js/helpers/ClsiApp.js b/services/clsi/test/acceptance/js/helpers/ClsiApp.js index bd3222df6c..f8038460cf 100644 --- a/services/clsi/test/acceptance/js/helpers/ClsiApp.js +++ b/services/clsi/test/acceptance/js/helpers/ClsiApp.js @@ -12,40 +12,53 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const app = require('../../../../app'); -require("logger-sharelatex").logger.level("info"); -const logger = require("logger-sharelatex"); -const Settings = require("settings-sharelatex"); +const app = require('../../../../app') +require('logger-sharelatex').logger.level('info') +const logger = require('logger-sharelatex') +const Settings = require('settings-sharelatex') module.exports = { - running: false, - initing: false, - callbacks: [], - ensureRunning(callback) { - if (callback == null) { callback = function(error) {}; } - if (this.running) { - return callback(); - } else if (this.initing) { - return this.callbacks.push(callback); - } else { - this.initing = true; - this.callbacks.push(callback); - return app.listen(__guard__(Settings.internal != null ? Settings.internal.clsi : undefined, x => x.port), "localhost", error => { - if (error != null) { throw error; } - this.running = true; - logger.log("clsi running in dev mode"); + running: false, + initing: false, + callbacks: [], + ensureRunning(callback) { + if (callback == null) { + callback = function(error) {} + } + if (this.running) { + return callback() + } else if (this.initing) { + return this.callbacks.push(callback) + } else { + this.initing = true + this.callbacks.push(callback) + return app.listen( + __guard__( + Settings.internal != null ? Settings.internal.clsi : undefined, + x => x.port + ), + 'localhost', + error => { + if (error != null) { + throw error + } + this.running = true + logger.log('clsi running in dev mode') - return (() => { - const result = []; - for (callback of Array.from(this.callbacks)) { - result.push(callback()); - } - return result; - })(); - }); - } - } -}; + return (() => { + const result = [] + for (callback of Array.from(this.callbacks)) { + result.push(callback()) + } + return result + })() + } + ) + } + } +} function __guard__(value, transform) { - return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; -} \ No newline at end of file + return typeof value !== 'undefined' && value !== null + ? transform(value) + : undefined +} From 66ce5847a3d7be4fe8a3d3177e0dcbf9988a9c8f Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:16:15 +0100 Subject: [PATCH 531/754] decaffeinate: rename individual coffee files to js files --- services/clsi/{app.coffee => app.js} | 0 .../config/{settings.defaults.coffee => settings.defaults.js} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename services/clsi/{app.coffee => app.js} (100%) rename services/clsi/config/{settings.defaults.coffee => settings.defaults.js} (100%) diff --git a/services/clsi/app.coffee b/services/clsi/app.js similarity index 100% rename from services/clsi/app.coffee rename to services/clsi/app.js diff --git a/services/clsi/config/settings.defaults.coffee b/services/clsi/config/settings.defaults.js similarity index 100% rename from services/clsi/config/settings.defaults.coffee rename to services/clsi/config/settings.defaults.js From d23250a4bbdcc6e6e870a57668b01fb1779f098d Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:16:17 +0100 Subject: [PATCH 532/754] decaffeinate: convert individual files to js --- services/clsi/app.js | 428 ++++++++++++---------- services/clsi/config/settings.defaults.js | 122 +++--- 2 files changed, 311 insertions(+), 239 deletions(-) diff --git a/services/clsi/app.js b/services/clsi/app.js index 9bcdfebc62..99427da29e 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -1,244 +1,298 @@ -Metrics = require "metrics-sharelatex" -Metrics.initialize("clsi") +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__ + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let tenMinutes; +const Metrics = require("metrics-sharelatex"); +Metrics.initialize("clsi"); -CompileController = require "./app/js/CompileController" -Settings = require "settings-sharelatex" -logger = require "logger-sharelatex" -logger.initialize("clsi") -if Settings.sentry?.dsn? - logger.initializeErrorReporting(Settings.sentry.dsn) +const CompileController = require("./app/js/CompileController"); +const Settings = require("settings-sharelatex"); +const logger = require("logger-sharelatex"); +logger.initialize("clsi"); +if ((Settings.sentry != null ? Settings.sentry.dsn : undefined) != null) { + logger.initializeErrorReporting(Settings.sentry.dsn); +} -smokeTest = require "smoke-test-sharelatex" -ContentTypeMapper = require "./app/js/ContentTypeMapper" -Errors = require './app/js/Errors' +const smokeTest = require("smoke-test-sharelatex"); +const ContentTypeMapper = require("./app/js/ContentTypeMapper"); +const Errors = require('./app/js/Errors'); -Path = require "path" -fs = require "fs" +const Path = require("path"); +const fs = require("fs"); -Metrics.open_sockets.monitor(logger) -Metrics.memory.monitor(logger) +Metrics.open_sockets.monitor(logger); +Metrics.memory.monitor(logger); -ProjectPersistenceManager = require "./app/js/ProjectPersistenceManager" -OutputCacheManager = require "./app/js/OutputCacheManager" +const ProjectPersistenceManager = require("./app/js/ProjectPersistenceManager"); +const OutputCacheManager = require("./app/js/OutputCacheManager"); -require("./app/js/db").sync() +require("./app/js/db").sync(); -express = require "express" -bodyParser = require "body-parser" -app = express() +const express = require("express"); +const bodyParser = require("body-parser"); +const app = express(); -Metrics.injectMetricsRoute(app) -app.use Metrics.http.monitor(logger) +Metrics.injectMetricsRoute(app); +app.use(Metrics.http.monitor(logger)); -# Compile requests can take longer than the default two -# minutes (including file download time), so bump up the -# timeout a bit. -TIMEOUT = 10 * 60 * 1000 -app.use (req, res, next) -> - req.setTimeout TIMEOUT - res.setTimeout TIMEOUT - res.removeHeader("X-Powered-By") - next() +// Compile requests can take longer than the default two +// minutes (including file download time), so bump up the +// timeout a bit. +const TIMEOUT = 10 * 60 * 1000; +app.use(function(req, res, next) { + req.setTimeout(TIMEOUT); + res.setTimeout(TIMEOUT); + res.removeHeader("X-Powered-By"); + return next(); +}); -app.param 'project_id', (req, res, next, project_id) -> - if project_id?.match /^[a-zA-Z0-9_-]+$/ - next() - else - next new Error("invalid project id") +app.param('project_id', function(req, res, next, project_id) { + if ((project_id != null ? project_id.match(/^[a-zA-Z0-9_-]+$/) : undefined)) { + return next(); + } else { + return next(new Error("invalid project id")); + } +}); -app.param 'user_id', (req, res, next, user_id) -> - if user_id?.match /^[0-9a-f]{24}$/ - next() - else - next new Error("invalid user id") +app.param('user_id', function(req, res, next, user_id) { + if ((user_id != null ? user_id.match(/^[0-9a-f]{24}$/) : undefined)) { + return next(); + } else { + return next(new Error("invalid user id")); + } +}); -app.param 'build_id', (req, res, next, build_id) -> - if build_id?.match OutputCacheManager.BUILD_REGEX - next() - else - next new Error("invalid build id #{build_id}") +app.param('build_id', function(req, res, next, build_id) { + if ((build_id != null ? build_id.match(OutputCacheManager.BUILD_REGEX) : undefined)) { + return next(); + } else { + return next(new Error(`invalid build id ${build_id}`)); + } +}); -app.post "/project/:project_id/compile", bodyParser.json(limit: Settings.compileSizeLimit), CompileController.compile -app.post "/project/:project_id/compile/stop", CompileController.stopCompile -app.delete "/project/:project_id", CompileController.clearCache +app.post("/project/:project_id/compile", bodyParser.json({limit: Settings.compileSizeLimit}), CompileController.compile); +app.post("/project/:project_id/compile/stop", CompileController.stopCompile); +app.delete("/project/:project_id", CompileController.clearCache); -app.get "/project/:project_id/sync/code", CompileController.syncFromCode -app.get "/project/:project_id/sync/pdf", CompileController.syncFromPdf -app.get "/project/:project_id/wordcount", CompileController.wordcount -app.get "/project/:project_id/status", CompileController.status +app.get("/project/:project_id/sync/code", CompileController.syncFromCode); +app.get("/project/:project_id/sync/pdf", CompileController.syncFromPdf); +app.get("/project/:project_id/wordcount", CompileController.wordcount); +app.get("/project/:project_id/status", CompileController.status); -# Per-user containers -app.post "/project/:project_id/user/:user_id/compile", bodyParser.json(limit: Settings.compileSizeLimit), CompileController.compile -app.post "/project/:project_id/user/:user_id/compile/stop", CompileController.stopCompile -app.delete "/project/:project_id/user/:user_id", CompileController.clearCache +// Per-user containers +app.post("/project/:project_id/user/:user_id/compile", bodyParser.json({limit: Settings.compileSizeLimit}), CompileController.compile); +app.post("/project/:project_id/user/:user_id/compile/stop", CompileController.stopCompile); +app.delete("/project/:project_id/user/:user_id", CompileController.clearCache); -app.get "/project/:project_id/user/:user_id/sync/code", CompileController.syncFromCode -app.get "/project/:project_id/user/:user_id/sync/pdf", CompileController.syncFromPdf -app.get "/project/:project_id/user/:user_id/wordcount", CompileController.wordcount +app.get("/project/:project_id/user/:user_id/sync/code", CompileController.syncFromCode); +app.get("/project/:project_id/user/:user_id/sync/pdf", CompileController.syncFromPdf); +app.get("/project/:project_id/user/:user_id/wordcount", CompileController.wordcount); -ForbidSymlinks = require "./app/js/StaticServerForbidSymlinks" +const ForbidSymlinks = require("./app/js/StaticServerForbidSymlinks"); -# create a static server which does not allow access to any symlinks -# avoids possible mismatch of root directory between middleware check -# and serving the files -staticServer = ForbidSymlinks express.static, Settings.path.compilesDir, setHeaders: (res, path, stat) -> - if Path.basename(path) == "output.pdf" - # Calculate an etag in the same way as nginx - # https://github.com/tj/send/issues/65 - etag = (path, stat) -> - '"' + Math.ceil(+stat.mtime / 1000).toString(16) + +// create a static server which does not allow access to any symlinks +// avoids possible mismatch of root directory between middleware check +// and serving the files +const staticServer = ForbidSymlinks(express.static, Settings.path.compilesDir, { setHeaders(res, path, stat) { + if (Path.basename(path) === "output.pdf") { + // Calculate an etag in the same way as nginx + // https://github.com/tj/send/issues/65 + const etag = (path, stat) => + `"${Math.ceil(+stat.mtime / 1000).toString(16)}` + '-' + Number(stat.size).toString(16) + '"' - res.set("Etag", etag(path, stat)) - res.set("Content-Type", ContentTypeMapper.map(path)) + ; + res.set("Etag", etag(path, stat)); + } + return res.set("Content-Type", ContentTypeMapper.map(path)); +} +} +); -app.get "/project/:project_id/user/:user_id/build/:build_id/output/*", (req, res, next) -> - # for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) - req.url = "/#{req.params.project_id}-#{req.params.user_id}/" + OutputCacheManager.path(req.params.build_id, "/#{req.params[0]}") - staticServer(req, res, next) +app.get("/project/:project_id/user/:user_id/build/:build_id/output/*", function(req, res, next) { + // for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) + req.url = `/${req.params.project_id}-${req.params.user_id}/` + OutputCacheManager.path(req.params.build_id, `/${req.params[0]}`); + return staticServer(req, res, next); +}); -app.get "/project/:project_id/build/:build_id/output/*", (req, res, next) -> - # for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) - req.url = "/#{req.params.project_id}/" + OutputCacheManager.path(req.params.build_id, "/#{req.params[0]}") - staticServer(req, res, next) +app.get("/project/:project_id/build/:build_id/output/*", function(req, res, next) { + // for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) + req.url = `/${req.params.project_id}/` + OutputCacheManager.path(req.params.build_id, `/${req.params[0]}`); + return staticServer(req, res, next); +}); -app.get "/project/:project_id/user/:user_id/output/*", (req, res, next) -> - # for specific user get the path to the top level file - req.url = "/#{req.params.project_id}-#{req.params.user_id}/#{req.params[0]}" - staticServer(req, res, next) +app.get("/project/:project_id/user/:user_id/output/*", function(req, res, next) { + // for specific user get the path to the top level file + req.url = `/${req.params.project_id}-${req.params.user_id}/${req.params[0]}`; + return staticServer(req, res, next); +}); -app.get "/project/:project_id/output/*", (req, res, next) -> - if req.query?.build? && req.query.build.match(OutputCacheManager.BUILD_REGEX) - # for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) - req.url = "/#{req.params.project_id}/" + OutputCacheManager.path(req.query.build, "/#{req.params[0]}") - else - req.url = "/#{req.params.project_id}/#{req.params[0]}" - staticServer(req, res, next) +app.get("/project/:project_id/output/*", function(req, res, next) { + if (((req.query != null ? req.query.build : undefined) != null) && req.query.build.match(OutputCacheManager.BUILD_REGEX)) { + // for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) + req.url = `/${req.params.project_id}/` + OutputCacheManager.path(req.query.build, `/${req.params[0]}`); + } else { + req.url = `/${req.params.project_id}/${req.params[0]}`; + } + return staticServer(req, res, next); +}); -app.get "/oops", (req, res, next) -> - logger.error {err: "hello"}, "test error" - res.send "error\n" +app.get("/oops", function(req, res, next) { + logger.error({err: "hello"}, "test error"); + return res.send("error\n"); +}); -app.get "/status", (req, res, next) -> - res.send "CLSI is alive\n" +app.get("/status", (req, res, next) => res.send("CLSI is alive\n")); -resCacher = - contentType:(@setContentType)-> - send:(@code, @body)-> +const resCacher = { + contentType(setContentType){ + this.setContentType = setContentType; + }, + send(code, body){ + this.code = code; + this.body = body; + }, - #default the server to be down - code:500 - body:{} + //default the server to be down + code:500, + body:{}, setContentType:"application/json" +}; -if Settings.smokeTest - do runSmokeTest = -> - logger.log("running smoke tests") - smokeTest.run(require.resolve(__dirname + "/test/smoke/js/SmokeTests.js"))({}, resCacher) - setTimeout(runSmokeTest, 30 * 1000) +if (Settings.smokeTest) { + let runSmokeTest; + (runSmokeTest = function() { + logger.log("running smoke tests"); + smokeTest.run(require.resolve(__dirname + "/test/smoke/js/SmokeTests.js"))({}, resCacher); + return setTimeout(runSmokeTest, 30 * 1000); + })(); +} -app.get "/health_check", (req, res)-> - res.contentType(resCacher?.setContentType) - res.status(resCacher?.code).send(resCacher?.body) +app.get("/health_check", function(req, res){ + res.contentType(resCacher != null ? resCacher.setContentType : undefined); + return res.status(resCacher != null ? resCacher.code : undefined).send(resCacher != null ? resCacher.body : undefined); +}); -app.get "/smoke_test_force", (req, res)-> - smokeTest.run(require.resolve(__dirname + "/test/smoke/js/SmokeTests.js"))(req, res) +app.get("/smoke_test_force", (req, res)=> smokeTest.run(require.resolve(__dirname + "/test/smoke/js/SmokeTests.js"))(req, res)); -profiler = require "v8-profiler-node8" -app.get "/profile", (req, res) -> - time = parseInt(req.query.time || "1000") - profiler.startProfiling("test") - setTimeout () -> - profile = profiler.stopProfiling("test") - res.json(profile) - , time +const profiler = require("v8-profiler-node8"); +app.get("/profile", function(req, res) { + const time = parseInt(req.query.time || "1000"); + profiler.startProfiling("test"); + return setTimeout(function() { + const profile = profiler.stopProfiling("test"); + return res.json(profile); + } + , time); +}); -app.get "/heapdump", (req, res)-> - require('heapdump').writeSnapshot '/tmp/' + Date.now() + '.clsi.heapsnapshot', (err, filename)-> - res.send filename +app.get("/heapdump", (req, res)=> + require('heapdump').writeSnapshot(`/tmp/${Date.now()}.clsi.heapsnapshot`, (err, filename)=> res.send(filename)) +); -app.use (error, req, res, next) -> - if error instanceof Errors.NotFoundError - logger.warn {err: error, url: req.url}, "not found error" - return res.sendStatus(404) - else - logger.error {err: error, url: req.url}, "server error" - res.sendStatus(error?.statusCode || 500) +app.use(function(error, req, res, next) { + if (error instanceof Errors.NotFoundError) { + logger.warn({err: error, url: req.url}, "not found error"); + return res.sendStatus(404); + } else { + logger.error({err: error, url: req.url}, "server error"); + return res.sendStatus((error != null ? error.statusCode : undefined) || 500); + } +}); -net = require "net" -os = require "os" +const net = require("net"); +const os = require("os"); -STATE = "up" +let STATE = "up"; -loadTcpServer = net.createServer (socket) -> - socket.on "error", (err)-> - if err.code == "ECONNRESET" - # this always comes up, we don't know why - return - logger.err err:err, "error with socket on load check" - socket.destroy() +const loadTcpServer = net.createServer(function(socket) { + socket.on("error", function(err){ + if (err.code === "ECONNRESET") { + // this always comes up, we don't know why + return; + } + logger.err({err}, "error with socket on load check"); + return socket.destroy(); + }); - if STATE == "up" and Settings.internal.load_balancer_agent.report_load - currentLoad = os.loadavg()[0] + if ((STATE === "up") && Settings.internal.load_balancer_agent.report_load) { + let availableWorkingCpus; + const currentLoad = os.loadavg()[0]; - # staging clis's have 1 cpu core only - if os.cpus().length == 1 - availableWorkingCpus = 1 - else - availableWorkingCpus = os.cpus().length - 1 + // staging clis's have 1 cpu core only + if (os.cpus().length === 1) { + availableWorkingCpus = 1; + } else { + availableWorkingCpus = os.cpus().length - 1; + } - freeLoad = availableWorkingCpus - currentLoad - freeLoadPercentage = Math.round((freeLoad / availableWorkingCpus) * 100) - if freeLoadPercentage <= 0 - freeLoadPercentage = 1 # when its 0 the server is set to drain and will move projects to different servers - socket.write("up, #{freeLoadPercentage}%\n", "ASCII") - socket.end() - else - socket.write("#{STATE}\n", "ASCII") - socket.end() + const freeLoad = availableWorkingCpus - currentLoad; + let freeLoadPercentage = Math.round((freeLoad / availableWorkingCpus) * 100); + if (freeLoadPercentage <= 0) { + freeLoadPercentage = 1; // when its 0 the server is set to drain and will move projects to different servers + } + socket.write(`up, ${freeLoadPercentage}%\n`, "ASCII"); + return socket.end(); + } else { + socket.write(`${STATE}\n`, "ASCII"); + return socket.end(); + } +}); -loadHttpServer = express() +const loadHttpServer = express(); -loadHttpServer.post "/state/up", (req, res, next) -> - STATE = "up" - logger.info "getting message to set server to down" - res.sendStatus 204 +loadHttpServer.post("/state/up", function(req, res, next) { + STATE = "up"; + logger.info("getting message to set server to down"); + return res.sendStatus(204); +}); -loadHttpServer.post "/state/down", (req, res, next) -> - STATE = "down" - logger.info "getting message to set server to down" - res.sendStatus 204 +loadHttpServer.post("/state/down", function(req, res, next) { + STATE = "down"; + logger.info("getting message to set server to down"); + return res.sendStatus(204); +}); -loadHttpServer.post "/state/maint", (req, res, next) -> - STATE = "maint" - logger.info "getting message to set server to maint" - res.sendStatus 204 +loadHttpServer.post("/state/maint", function(req, res, next) { + STATE = "maint"; + logger.info("getting message to set server to maint"); + return res.sendStatus(204); +}); -port = (Settings.internal?.clsi?.port or 3013) -host = (Settings.internal?.clsi?.host or "localhost") +const port = (__guard__(Settings.internal != null ? Settings.internal.clsi : undefined, x => x.port) || 3013); +const host = (__guard__(Settings.internal != null ? Settings.internal.clsi : undefined, x1 => x1.host) || "localhost"); -load_tcp_port = Settings.internal.load_balancer_agent.load_port -load_http_port = Settings.internal.load_balancer_agent.local_port +const load_tcp_port = Settings.internal.load_balancer_agent.load_port; +const load_http_port = Settings.internal.load_balancer_agent.local_port; -if !module.parent # Called directly - app.listen port, host, (error) -> - logger.info "CLSI starting up, listening on #{host}:#{port}" +if (!module.parent) { // Called directly + app.listen(port, host, error => logger.info(`CLSI starting up, listening on ${host}:${port}`)); - loadTcpServer.listen load_tcp_port, host, (error) -> - throw error if error? - logger.info "Load tcp agent listening on load port #{load_tcp_port}" + loadTcpServer.listen(load_tcp_port, host, function(error) { + if (error != null) { throw error; } + return logger.info(`Load tcp agent listening on load port ${load_tcp_port}`); + }); - loadHttpServer.listen load_http_port, host, (error) -> - throw error if error? - logger.info "Load http agent listening on load port #{load_http_port}" + loadHttpServer.listen(load_http_port, host, function(error) { + if (error != null) { throw error; } + return logger.info(`Load http agent listening on load port ${load_http_port}`); + }); +} -module.exports = app +module.exports = app; -setInterval () -> - ProjectPersistenceManager.clearExpiredProjects() -, tenMinutes = 10 * 60 * 1000 +setInterval(() => ProjectPersistenceManager.clearExpiredProjects() +, (tenMinutes = 10 * 60 * 1000)); + +function __guard__(value, transform) { + return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; +} \ No newline at end of file diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index ad3f04d8bd..5d0bb75fb3 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -1,71 +1,89 @@ -Path = require "path" +const Path = require("path"); -module.exports = - # Options are passed to Sequelize. - # See http://sequelizejs.com/documentation#usage-options for details - mysql: - clsi: - database: "clsi" - username: "clsi" - dialect: "sqlite" - storage: process.env["SQLITE_PATH"] or Path.resolve(__dirname + "/../db.sqlite") - pool: - max: 1 +module.exports = { + // Options are passed to Sequelize. + // See http://sequelizejs.com/documentation#usage-options for details + mysql: { + clsi: { + database: "clsi", + username: "clsi", + dialect: "sqlite", + storage: process.env["SQLITE_PATH"] || Path.resolve(__dirname + "/../db.sqlite"), + pool: { + max: 1, min: 1 - retry: + }, + retry: { max: 10 + } + } + }, - compileSizeLimit: process.env["COMPILE_SIZE_LIMIT"] or "7mb" + compileSizeLimit: process.env["COMPILE_SIZE_LIMIT"] || "7mb", - path: - compilesDir: Path.resolve(__dirname + "/../compiles") - clsiCacheDir: Path.resolve(__dirname + "/../cache") - synctexBaseDir: (project_id) -> Path.join(@compilesDir, project_id) + path: { + compilesDir: Path.resolve(__dirname + "/../compiles"), + clsiCacheDir: Path.resolve(__dirname + "/../cache"), + synctexBaseDir(project_id) { return Path.join(this.compilesDir, project_id); } + }, - internal: - clsi: - port: 3013 - host: process.env["LISTEN_ADDRESS"] or "localhost" + internal: { + clsi: { + port: 3013, + host: process.env["LISTEN_ADDRESS"] || "localhost" + }, - load_balancer_agent: - report_load:true - load_port: 3048 + load_balancer_agent: { + report_load:true, + load_port: 3048, local_port: 3049 - apis: - clsi: - url: "http://#{process.env['CLSI_HOST'] or 'localhost'}:3013" + } + }, + apis: { + clsi: { + url: `http://${process.env['CLSI_HOST'] || 'localhost'}:3013` + } + }, - smokeTest: process.env["SMOKE_TEST"] or false - project_cache_length_ms: 1000 * 60 * 60 * 24 - parallelFileDownloads: process.env["FILESTORE_PARALLEL_FILE_DOWNLOADS"] or 1 - parallelSqlQueryLimit: process.env["FILESTORE_PARALLEL_SQL_QUERY_LIMIT"] or 1 - filestoreDomainOveride: process.env["FILESTORE_DOMAIN_OVERRIDE"] - texliveImageNameOveride: process.env["TEX_LIVE_IMAGE_NAME_OVERRIDE"] - sentry: + smokeTest: process.env["SMOKE_TEST"] || false, + project_cache_length_ms: 1000 * 60 * 60 * 24, + parallelFileDownloads: process.env["FILESTORE_PARALLEL_FILE_DOWNLOADS"] || 1, + parallelSqlQueryLimit: process.env["FILESTORE_PARALLEL_SQL_QUERY_LIMIT"] || 1, + filestoreDomainOveride: process.env["FILESTORE_DOMAIN_OVERRIDE"], + texliveImageNameOveride: process.env["TEX_LIVE_IMAGE_NAME_OVERRIDE"], + sentry: { dsn: process.env['SENTRY_DSN'] + } +}; -if process.env["DOCKER_RUNNER"] - module.exports.clsi = - dockerRunner: process.env["DOCKER_RUNNER"] == "true" - docker: - image: process.env["TEXLIVE_IMAGE"] or "quay.io/sharelatex/texlive-full:2017.1" - env: +if (process.env["DOCKER_RUNNER"]) { + let seccomp_profile_path; + module.exports.clsi = { + dockerRunner: process.env["DOCKER_RUNNER"] === "true", + docker: { + image: process.env["TEXLIVE_IMAGE"] || "quay.io/sharelatex/texlive-full:2017.1", + env: { HOME: "/tmp" - socketPath: "/var/run/docker.sock" - user: process.env["TEXLIVE_IMAGE_USER"] or "tex" - expireProjectAfterIdleMs: 24 * 60 * 60 * 1000 + }, + socketPath: "/var/run/docker.sock", + user: process.env["TEXLIVE_IMAGE_USER"] || "tex" + }, + expireProjectAfterIdleMs: 24 * 60 * 60 * 1000, checkProjectsIntervalMs: 10 * 60 * 1000 + }; - try - seccomp_profile_path = Path.resolve(__dirname + "/../seccomp/clsi-profile.json") - module.exports.clsi.docker.seccomp_profile = JSON.stringify(JSON.parse(require("fs").readFileSync(seccomp_profile_path))) - catch error - console.log error, "could not load seccom profile from #{seccomp_profile_path}" + try { + seccomp_profile_path = Path.resolve(__dirname + "/../seccomp/clsi-profile.json"); + module.exports.clsi.docker.seccomp_profile = JSON.stringify(JSON.parse(require("fs").readFileSync(seccomp_profile_path))); + } catch (error) { + console.log(error, `could not load seccom profile from ${seccomp_profile_path}`); + } - module.exports.path.synctexBaseDir = -> "/compile" + module.exports.path.synctexBaseDir = () => "/compile"; - module.exports.path.sandboxedCompilesHostDir = process.env["COMPILES_HOST_DIR"] + module.exports.path.sandboxedCompilesHostDir = process.env["COMPILES_HOST_DIR"]; - module.exports.path.synctexBinHostPath = process.env["SYNCTEX_BIN_HOST_PATH"] + module.exports.path.synctexBinHostPath = process.env["SYNCTEX_BIN_HOST_PATH"]; +} From a5c7051fc9dd4aeaa8e73e88f31269d3020bf354 Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:16:18 +0100 Subject: [PATCH 533/754] prettier: convert individual decaffeinated files to Prettier format --- services/clsi/app.js | 519 ++++++++++++---------- services/clsi/config/settings.defaults.js | 173 ++++---- 2 files changed, 388 insertions(+), 304 deletions(-) diff --git a/services/clsi/app.js b/services/clsi/app.js index 99427da29e..c03fcd8b29 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -5,294 +5,367 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let tenMinutes; -const Metrics = require("metrics-sharelatex"); -Metrics.initialize("clsi"); +let tenMinutes +const Metrics = require('metrics-sharelatex') +Metrics.initialize('clsi') -const CompileController = require("./app/js/CompileController"); -const Settings = require("settings-sharelatex"); -const logger = require("logger-sharelatex"); -logger.initialize("clsi"); +const CompileController = require('./app/js/CompileController') +const Settings = require('settings-sharelatex') +const logger = require('logger-sharelatex') +logger.initialize('clsi') if ((Settings.sentry != null ? Settings.sentry.dsn : undefined) != null) { - logger.initializeErrorReporting(Settings.sentry.dsn); + logger.initializeErrorReporting(Settings.sentry.dsn) } -const smokeTest = require("smoke-test-sharelatex"); -const ContentTypeMapper = require("./app/js/ContentTypeMapper"); -const Errors = require('./app/js/Errors'); +const smokeTest = require('smoke-test-sharelatex') +const ContentTypeMapper = require('./app/js/ContentTypeMapper') +const Errors = require('./app/js/Errors') -const Path = require("path"); -const fs = require("fs"); +const Path = require('path') +const fs = require('fs') +Metrics.open_sockets.monitor(logger) +Metrics.memory.monitor(logger) -Metrics.open_sockets.monitor(logger); -Metrics.memory.monitor(logger); +const ProjectPersistenceManager = require('./app/js/ProjectPersistenceManager') +const OutputCacheManager = require('./app/js/OutputCacheManager') -const ProjectPersistenceManager = require("./app/js/ProjectPersistenceManager"); -const OutputCacheManager = require("./app/js/OutputCacheManager"); +require('./app/js/db').sync() -require("./app/js/db").sync(); +const express = require('express') +const bodyParser = require('body-parser') +const app = express() -const express = require("express"); -const bodyParser = require("body-parser"); -const app = express(); - -Metrics.injectMetricsRoute(app); -app.use(Metrics.http.monitor(logger)); +Metrics.injectMetricsRoute(app) +app.use(Metrics.http.monitor(logger)) // Compile requests can take longer than the default two -// minutes (including file download time), so bump up the +// minutes (including file download time), so bump up the // timeout a bit. -const TIMEOUT = 10 * 60 * 1000; +const TIMEOUT = 10 * 60 * 1000 app.use(function(req, res, next) { - req.setTimeout(TIMEOUT); - res.setTimeout(TIMEOUT); - res.removeHeader("X-Powered-By"); - return next(); -}); + req.setTimeout(TIMEOUT) + res.setTimeout(TIMEOUT) + res.removeHeader('X-Powered-By') + return next() +}) app.param('project_id', function(req, res, next, project_id) { - if ((project_id != null ? project_id.match(/^[a-zA-Z0-9_-]+$/) : undefined)) { - return next(); - } else { - return next(new Error("invalid project id")); - } -}); + if (project_id != null ? project_id.match(/^[a-zA-Z0-9_-]+$/) : undefined) { + return next() + } else { + return next(new Error('invalid project id')) + } +}) app.param('user_id', function(req, res, next, user_id) { - if ((user_id != null ? user_id.match(/^[0-9a-f]{24}$/) : undefined)) { - return next(); - } else { - return next(new Error("invalid user id")); - } -}); + if (user_id != null ? user_id.match(/^[0-9a-f]{24}$/) : undefined) { + return next() + } else { + return next(new Error('invalid user id')) + } +}) app.param('build_id', function(req, res, next, build_id) { - if ((build_id != null ? build_id.match(OutputCacheManager.BUILD_REGEX) : undefined)) { - return next(); - } else { - return next(new Error(`invalid build id ${build_id}`)); - } -}); + if ( + build_id != null + ? build_id.match(OutputCacheManager.BUILD_REGEX) + : undefined + ) { + return next() + } else { + return next(new Error(`invalid build id ${build_id}`)) + } +}) +app.post( + '/project/:project_id/compile', + bodyParser.json({ limit: Settings.compileSizeLimit }), + CompileController.compile +) +app.post('/project/:project_id/compile/stop', CompileController.stopCompile) +app.delete('/project/:project_id', CompileController.clearCache) -app.post("/project/:project_id/compile", bodyParser.json({limit: Settings.compileSizeLimit}), CompileController.compile); -app.post("/project/:project_id/compile/stop", CompileController.stopCompile); -app.delete("/project/:project_id", CompileController.clearCache); - -app.get("/project/:project_id/sync/code", CompileController.syncFromCode); -app.get("/project/:project_id/sync/pdf", CompileController.syncFromPdf); -app.get("/project/:project_id/wordcount", CompileController.wordcount); -app.get("/project/:project_id/status", CompileController.status); +app.get('/project/:project_id/sync/code', CompileController.syncFromCode) +app.get('/project/:project_id/sync/pdf', CompileController.syncFromPdf) +app.get('/project/:project_id/wordcount', CompileController.wordcount) +app.get('/project/:project_id/status', CompileController.status) // Per-user containers -app.post("/project/:project_id/user/:user_id/compile", bodyParser.json({limit: Settings.compileSizeLimit}), CompileController.compile); -app.post("/project/:project_id/user/:user_id/compile/stop", CompileController.stopCompile); -app.delete("/project/:project_id/user/:user_id", CompileController.clearCache); +app.post( + '/project/:project_id/user/:user_id/compile', + bodyParser.json({ limit: Settings.compileSizeLimit }), + CompileController.compile +) +app.post( + '/project/:project_id/user/:user_id/compile/stop', + CompileController.stopCompile +) +app.delete('/project/:project_id/user/:user_id', CompileController.clearCache) -app.get("/project/:project_id/user/:user_id/sync/code", CompileController.syncFromCode); -app.get("/project/:project_id/user/:user_id/sync/pdf", CompileController.syncFromPdf); -app.get("/project/:project_id/user/:user_id/wordcount", CompileController.wordcount); +app.get( + '/project/:project_id/user/:user_id/sync/code', + CompileController.syncFromCode +) +app.get( + '/project/:project_id/user/:user_id/sync/pdf', + CompileController.syncFromPdf +) +app.get( + '/project/:project_id/user/:user_id/wordcount', + CompileController.wordcount +) -const ForbidSymlinks = require("./app/js/StaticServerForbidSymlinks"); +const ForbidSymlinks = require('./app/js/StaticServerForbidSymlinks') // create a static server which does not allow access to any symlinks // avoids possible mismatch of root directory between middleware check // and serving the files -const staticServer = ForbidSymlinks(express.static, Settings.path.compilesDir, { setHeaders(res, path, stat) { - if (Path.basename(path) === "output.pdf") { - // Calculate an etag in the same way as nginx - // https://github.com/tj/send/issues/65 - const etag = (path, stat) => - `"${Math.ceil(+stat.mtime / 1000).toString(16)}` + - '-' + Number(stat.size).toString(16) + '"' - ; - res.set("Etag", etag(path, stat)); - } - return res.set("Content-Type", ContentTypeMapper.map(path)); -} -} -); +const staticServer = ForbidSymlinks(express.static, Settings.path.compilesDir, { + setHeaders(res, path, stat) { + if (Path.basename(path) === 'output.pdf') { + // Calculate an etag in the same way as nginx + // https://github.com/tj/send/issues/65 + const etag = (path, stat) => + `"${Math.ceil(+stat.mtime / 1000).toString(16)}` + + '-' + + Number(stat.size).toString(16) + + '"' + res.set('Etag', etag(path, stat)) + } + return res.set('Content-Type', ContentTypeMapper.map(path)) + } +}) -app.get("/project/:project_id/user/:user_id/build/:build_id/output/*", function(req, res, next) { - // for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) - req.url = `/${req.params.project_id}-${req.params.user_id}/` + OutputCacheManager.path(req.params.build_id, `/${req.params[0]}`); - return staticServer(req, res, next); -}); +app.get('/project/:project_id/user/:user_id/build/:build_id/output/*', function( + req, + res, + next +) { + // for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) + req.url = + `/${req.params.project_id}-${req.params.user_id}/` + + OutputCacheManager.path(req.params.build_id, `/${req.params[0]}`) + return staticServer(req, res, next) +}) -app.get("/project/:project_id/build/:build_id/output/*", function(req, res, next) { - // for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) - req.url = `/${req.params.project_id}/` + OutputCacheManager.path(req.params.build_id, `/${req.params[0]}`); - return staticServer(req, res, next); -}); +app.get('/project/:project_id/build/:build_id/output/*', function( + req, + res, + next +) { + // for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) + req.url = + `/${req.params.project_id}/` + + OutputCacheManager.path(req.params.build_id, `/${req.params[0]}`) + return staticServer(req, res, next) +}) -app.get("/project/:project_id/user/:user_id/output/*", function(req, res, next) { - // for specific user get the path to the top level file - req.url = `/${req.params.project_id}-${req.params.user_id}/${req.params[0]}`; - return staticServer(req, res, next); -}); +app.get('/project/:project_id/user/:user_id/output/*', function( + req, + res, + next +) { + // for specific user get the path to the top level file + req.url = `/${req.params.project_id}-${req.params.user_id}/${req.params[0]}` + return staticServer(req, res, next) +}) -app.get("/project/:project_id/output/*", function(req, res, next) { - if (((req.query != null ? req.query.build : undefined) != null) && req.query.build.match(OutputCacheManager.BUILD_REGEX)) { - // for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) - req.url = `/${req.params.project_id}/` + OutputCacheManager.path(req.query.build, `/${req.params[0]}`); - } else { - req.url = `/${req.params.project_id}/${req.params[0]}`; - } - return staticServer(req, res, next); -}); +app.get('/project/:project_id/output/*', function(req, res, next) { + if ( + (req.query != null ? req.query.build : undefined) != null && + req.query.build.match(OutputCacheManager.BUILD_REGEX) + ) { + // for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) + req.url = + `/${req.params.project_id}/` + + OutputCacheManager.path(req.query.build, `/${req.params[0]}`) + } else { + req.url = `/${req.params.project_id}/${req.params[0]}` + } + return staticServer(req, res, next) +}) -app.get("/oops", function(req, res, next) { - logger.error({err: "hello"}, "test error"); - return res.send("error\n"); -}); +app.get('/oops', function(req, res, next) { + logger.error({ err: 'hello' }, 'test error') + return res.send('error\n') +}) - -app.get("/status", (req, res, next) => res.send("CLSI is alive\n")); +app.get('/status', (req, res, next) => res.send('CLSI is alive\n')) const resCacher = { - contentType(setContentType){ - this.setContentType = setContentType; - }, - send(code, body){ - this.code = code; - this.body = body; - }, + contentType(setContentType) { + this.setContentType = setContentType + }, + send(code, body) { + this.code = code + this.body = body + }, - //default the server to be down - code:500, - body:{}, - setContentType:"application/json" -}; + // default the server to be down + code: 500, + body: {}, + setContentType: 'application/json' +} if (Settings.smokeTest) { - let runSmokeTest; - (runSmokeTest = function() { - logger.log("running smoke tests"); - smokeTest.run(require.resolve(__dirname + "/test/smoke/js/SmokeTests.js"))({}, resCacher); - return setTimeout(runSmokeTest, 30 * 1000); - })(); + let runSmokeTest + ;(runSmokeTest = function() { + logger.log('running smoke tests') + smokeTest.run(require.resolve(__dirname + '/test/smoke/js/SmokeTests.js'))( + {}, + resCacher + ) + return setTimeout(runSmokeTest, 30 * 1000) + })() } -app.get("/health_check", function(req, res){ - res.contentType(resCacher != null ? resCacher.setContentType : undefined); - return res.status(resCacher != null ? resCacher.code : undefined).send(resCacher != null ? resCacher.body : undefined); -}); +app.get('/health_check', function(req, res) { + res.contentType(resCacher != null ? resCacher.setContentType : undefined) + return res + .status(resCacher != null ? resCacher.code : undefined) + .send(resCacher != null ? resCacher.body : undefined) +}) -app.get("/smoke_test_force", (req, res)=> smokeTest.run(require.resolve(__dirname + "/test/smoke/js/SmokeTests.js"))(req, res)); +app.get('/smoke_test_force', (req, res) => + smokeTest.run(require.resolve(__dirname + '/test/smoke/js/SmokeTests.js'))( + req, + res + ) +) -const profiler = require("v8-profiler-node8"); -app.get("/profile", function(req, res) { - const time = parseInt(req.query.time || "1000"); - profiler.startProfiling("test"); - return setTimeout(function() { - const profile = profiler.stopProfiling("test"); - return res.json(profile); - } - , time); -}); +const profiler = require('v8-profiler-node8') +app.get('/profile', function(req, res) { + const time = parseInt(req.query.time || '1000') + profiler.startProfiling('test') + return setTimeout(function() { + const profile = profiler.stopProfiling('test') + return res.json(profile) + }, time) +}) -app.get("/heapdump", (req, res)=> - require('heapdump').writeSnapshot(`/tmp/${Date.now()}.clsi.heapsnapshot`, (err, filename)=> res.send(filename)) -); +app.get('/heapdump', (req, res) => + require('heapdump').writeSnapshot( + `/tmp/${Date.now()}.clsi.heapsnapshot`, + (err, filename) => res.send(filename) + ) +) app.use(function(error, req, res, next) { - if (error instanceof Errors.NotFoundError) { - logger.warn({err: error, url: req.url}, "not found error"); - return res.sendStatus(404); - } else { - logger.error({err: error, url: req.url}, "server error"); - return res.sendStatus((error != null ? error.statusCode : undefined) || 500); - } -}); + if (error instanceof Errors.NotFoundError) { + logger.warn({ err: error, url: req.url }, 'not found error') + return res.sendStatus(404) + } else { + logger.error({ err: error, url: req.url }, 'server error') + return res.sendStatus((error != null ? error.statusCode : undefined) || 500) + } +}) -const net = require("net"); -const os = require("os"); - -let STATE = "up"; +const net = require('net') +const os = require('os') +let STATE = 'up' const loadTcpServer = net.createServer(function(socket) { - socket.on("error", function(err){ - if (err.code === "ECONNRESET") { - // this always comes up, we don't know why - return; - } - logger.err({err}, "error with socket on load check"); - return socket.destroy(); - }); - - if ((STATE === "up") && Settings.internal.load_balancer_agent.report_load) { - let availableWorkingCpus; - const currentLoad = os.loadavg()[0]; + socket.on('error', function(err) { + if (err.code === 'ECONNRESET') { + // this always comes up, we don't know why + return + } + logger.err({ err }, 'error with socket on load check') + return socket.destroy() + }) - // staging clis's have 1 cpu core only - if (os.cpus().length === 1) { - availableWorkingCpus = 1; - } else { - availableWorkingCpus = os.cpus().length - 1; - } + if (STATE === 'up' && Settings.internal.load_balancer_agent.report_load) { + let availableWorkingCpus + const currentLoad = os.loadavg()[0] - const freeLoad = availableWorkingCpus - currentLoad; - let freeLoadPercentage = Math.round((freeLoad / availableWorkingCpus) * 100); - if (freeLoadPercentage <= 0) { - freeLoadPercentage = 1; // when its 0 the server is set to drain and will move projects to different servers - } - socket.write(`up, ${freeLoadPercentage}%\n`, "ASCII"); - return socket.end(); - } else { - socket.write(`${STATE}\n`, "ASCII"); - return socket.end(); - } -}); + // staging clis's have 1 cpu core only + if (os.cpus().length === 1) { + availableWorkingCpus = 1 + } else { + availableWorkingCpus = os.cpus().length - 1 + } -const loadHttpServer = express(); + const freeLoad = availableWorkingCpus - currentLoad + let freeLoadPercentage = Math.round((freeLoad / availableWorkingCpus) * 100) + if (freeLoadPercentage <= 0) { + freeLoadPercentage = 1 // when its 0 the server is set to drain and will move projects to different servers + } + socket.write(`up, ${freeLoadPercentage}%\n`, 'ASCII') + return socket.end() + } else { + socket.write(`${STATE}\n`, 'ASCII') + return socket.end() + } +}) -loadHttpServer.post("/state/up", function(req, res, next) { - STATE = "up"; - logger.info("getting message to set server to down"); - return res.sendStatus(204); -}); +const loadHttpServer = express() -loadHttpServer.post("/state/down", function(req, res, next) { - STATE = "down"; - logger.info("getting message to set server to down"); - return res.sendStatus(204); -}); +loadHttpServer.post('/state/up', function(req, res, next) { + STATE = 'up' + logger.info('getting message to set server to down') + return res.sendStatus(204) +}) -loadHttpServer.post("/state/maint", function(req, res, next) { - STATE = "maint"; - logger.info("getting message to set server to maint"); - return res.sendStatus(204); -}); - +loadHttpServer.post('/state/down', function(req, res, next) { + STATE = 'down' + logger.info('getting message to set server to down') + return res.sendStatus(204) +}) -const port = (__guard__(Settings.internal != null ? Settings.internal.clsi : undefined, x => x.port) || 3013); -const host = (__guard__(Settings.internal != null ? Settings.internal.clsi : undefined, x1 => x1.host) || "localhost"); +loadHttpServer.post('/state/maint', function(req, res, next) { + STATE = 'maint' + logger.info('getting message to set server to maint') + return res.sendStatus(204) +}) -const load_tcp_port = Settings.internal.load_balancer_agent.load_port; -const load_http_port = Settings.internal.load_balancer_agent.local_port; +const port = + __guard__( + Settings.internal != null ? Settings.internal.clsi : undefined, + x => x.port + ) || 3013 +const host = + __guard__( + Settings.internal != null ? Settings.internal.clsi : undefined, + x1 => x1.host + ) || 'localhost' -if (!module.parent) { // Called directly - app.listen(port, host, error => logger.info(`CLSI starting up, listening on ${host}:${port}`)); +const load_tcp_port = Settings.internal.load_balancer_agent.load_port +const load_http_port = Settings.internal.load_balancer_agent.local_port - loadTcpServer.listen(load_tcp_port, host, function(error) { - if (error != null) { throw error; } - return logger.info(`Load tcp agent listening on load port ${load_tcp_port}`); - }); +if (!module.parent) { + // Called directly + app.listen(port, host, error => + logger.info(`CLSI starting up, listening on ${host}:${port}`) + ) - loadHttpServer.listen(load_http_port, host, function(error) { - if (error != null) { throw error; } - return logger.info(`Load http agent listening on load port ${load_http_port}`); - }); + loadTcpServer.listen(load_tcp_port, host, function(error) { + if (error != null) { + throw error + } + return logger.info(`Load tcp agent listening on load port ${load_tcp_port}`) + }) + + loadHttpServer.listen(load_http_port, host, function(error) { + if (error != null) { + throw error + } + return logger.info( + `Load http agent listening on load port ${load_http_port}` + ) + }) } -module.exports = app; - -setInterval(() => ProjectPersistenceManager.clearExpiredProjects() -, (tenMinutes = 10 * 60 * 1000)); +module.exports = app +setInterval( + () => ProjectPersistenceManager.clearExpiredProjects(), + (tenMinutes = 10 * 60 * 1000) +) function __guard__(value, transform) { - return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; -} \ No newline at end of file + return typeof value !== 'undefined' && value !== null + ? transform(value) + : undefined +} diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index 5d0bb75fb3..b0fd0cbd35 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -1,89 +1,100 @@ -const Path = require("path"); +const Path = require('path') module.exports = { - // Options are passed to Sequelize. - // See http://sequelizejs.com/documentation#usage-options for details - mysql: { - clsi: { - database: "clsi", - username: "clsi", - dialect: "sqlite", - storage: process.env["SQLITE_PATH"] || Path.resolve(__dirname + "/../db.sqlite"), - pool: { - max: 1, - min: 1 - }, - retry: { - max: 10 - } - } - }, + // Options are passed to Sequelize. + // See http://sequelizejs.com/documentation#usage-options for details + mysql: { + clsi: { + database: 'clsi', + username: 'clsi', + dialect: 'sqlite', + storage: + process.env.SQLITE_PATH || Path.resolve(__dirname + '/../db.sqlite'), + pool: { + max: 1, + min: 1 + }, + retry: { + max: 10 + } + } + }, - compileSizeLimit: process.env["COMPILE_SIZE_LIMIT"] || "7mb", - - path: { - compilesDir: Path.resolve(__dirname + "/../compiles"), - clsiCacheDir: Path.resolve(__dirname + "/../cache"), - synctexBaseDir(project_id) { return Path.join(this.compilesDir, project_id); } - }, + compileSizeLimit: process.env.COMPILE_SIZE_LIMIT || '7mb', - internal: { - clsi: { - port: 3013, - host: process.env["LISTEN_ADDRESS"] || "localhost" - }, - - load_balancer_agent: { - report_load:true, - load_port: 3048, - local_port: 3049 - } - }, - apis: { - clsi: { - url: `http://${process.env['CLSI_HOST'] || 'localhost'}:3013` - } - }, + path: { + compilesDir: Path.resolve(__dirname + '/../compiles'), + clsiCacheDir: Path.resolve(__dirname + '/../cache'), + synctexBaseDir(project_id) { + return Path.join(this.compilesDir, project_id) + } + }, - - smokeTest: process.env["SMOKE_TEST"] || false, - project_cache_length_ms: 1000 * 60 * 60 * 24, - parallelFileDownloads: process.env["FILESTORE_PARALLEL_FILE_DOWNLOADS"] || 1, - parallelSqlQueryLimit: process.env["FILESTORE_PARALLEL_SQL_QUERY_LIMIT"] || 1, - filestoreDomainOveride: process.env["FILESTORE_DOMAIN_OVERRIDE"], - texliveImageNameOveride: process.env["TEX_LIVE_IMAGE_NAME_OVERRIDE"], - sentry: { - dsn: process.env['SENTRY_DSN'] - } -}; + internal: { + clsi: { + port: 3013, + host: process.env.LISTEN_ADDRESS || 'localhost' + }, + load_balancer_agent: { + report_load: true, + load_port: 3048, + local_port: 3049 + } + }, + apis: { + clsi: { + url: `http://${process.env.CLSI_HOST || 'localhost'}:3013` + } + }, -if (process.env["DOCKER_RUNNER"]) { - let seccomp_profile_path; - module.exports.clsi = { - dockerRunner: process.env["DOCKER_RUNNER"] === "true", - docker: { - image: process.env["TEXLIVE_IMAGE"] || "quay.io/sharelatex/texlive-full:2017.1", - env: { - HOME: "/tmp" - }, - socketPath: "/var/run/docker.sock", - user: process.env["TEXLIVE_IMAGE_USER"] || "tex" - }, - expireProjectAfterIdleMs: 24 * 60 * 60 * 1000, - checkProjectsIntervalMs: 10 * 60 * 1000 - }; - - try { - seccomp_profile_path = Path.resolve(__dirname + "/../seccomp/clsi-profile.json"); - module.exports.clsi.docker.seccomp_profile = JSON.stringify(JSON.parse(require("fs").readFileSync(seccomp_profile_path))); - } catch (error) { - console.log(error, `could not load seccom profile from ${seccomp_profile_path}`); - } - - module.exports.path.synctexBaseDir = () => "/compile"; - - module.exports.path.sandboxedCompilesHostDir = process.env["COMPILES_HOST_DIR"]; - - module.exports.path.synctexBinHostPath = process.env["SYNCTEX_BIN_HOST_PATH"]; + smokeTest: process.env.SMOKE_TEST || false, + project_cache_length_ms: 1000 * 60 * 60 * 24, + parallelFileDownloads: process.env.FILESTORE_PARALLEL_FILE_DOWNLOADS || 1, + parallelSqlQueryLimit: process.env.FILESTORE_PARALLEL_SQL_QUERY_LIMIT || 1, + filestoreDomainOveride: process.env.FILESTORE_DOMAIN_OVERRIDE, + texliveImageNameOveride: process.env.TEX_LIVE_IMAGE_NAME_OVERRIDE, + sentry: { + dsn: process.env.SENTRY_DSN + } +} + +if (process.env.DOCKER_RUNNER) { + let seccomp_profile_path + module.exports.clsi = { + dockerRunner: process.env.DOCKER_RUNNER === 'true', + docker: { + image: + process.env.TEXLIVE_IMAGE || + 'quay.io/sharelatex/texlive-full:2017.1', + env: { + HOME: '/tmp' + }, + socketPath: '/var/run/docker.sock', + user: process.env.TEXLIVE_IMAGE_USER || 'tex' + }, + expireProjectAfterIdleMs: 24 * 60 * 60 * 1000, + checkProjectsIntervalMs: 10 * 60 * 1000 + } + + try { + seccomp_profile_path = Path.resolve( + __dirname + '/../seccomp/clsi-profile.json' + ) + module.exports.clsi.docker.seccomp_profile = JSON.stringify( + JSON.parse(require('fs').readFileSync(seccomp_profile_path)) + ) + } catch (error) { + console.log( + error, + `could not load seccom profile from ${seccomp_profile_path}` + ) + } + + module.exports.path.synctexBaseDir = () => '/compile' + + module.exports.path.sandboxedCompilesHostDir = + process.env.COMPILES_HOST_DIR + + module.exports.path.synctexBinHostPath = process.env.SYNCTEX_BIN_HOST_PATH } From 4c0fd2b6f42b3ec73a6eecafe267e2457c5b9498 Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Wed, 19 Feb 2020 12:38:54 +0100 Subject: [PATCH 534/754] fixed test paths --- services/clsi/test/unit/js/DockerLockManagerTests.js | 2 +- services/clsi/test/unit/js/DockerRunnerTests.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/test/unit/js/DockerLockManagerTests.js b/services/clsi/test/unit/js/DockerLockManagerTests.js index 9dcf9dcc14..177d7a21f4 100644 --- a/services/clsi/test/unit/js/DockerLockManagerTests.js +++ b/services/clsi/test/unit/js/DockerLockManagerTests.js @@ -15,7 +15,7 @@ require('chai').should() require('coffee-script') const modulePath = require('path').join( __dirname, - '../../../app/coffee/DockerLockManager' + '../../../app/js/DockerLockManager' ) describe('LockManager', function() { diff --git a/services/clsi/test/unit/js/DockerRunnerTests.js b/services/clsi/test/unit/js/DockerRunnerTests.js index e43a044c80..597c5d309d 100644 --- a/services/clsi/test/unit/js/DockerRunnerTests.js +++ b/services/clsi/test/unit/js/DockerRunnerTests.js @@ -20,7 +20,7 @@ const { expect } = require('chai') require('coffee-script') const modulePath = require('path').join( __dirname, - '../../../app/coffee/DockerRunner' + '../../../app/js/DockerRunner' ) const Path = require('path') From fa37ed865ae98c36e483c18c730c3b25291164bc Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Thu, 20 Feb 2020 17:24:28 +0100 Subject: [PATCH 535/754] added container monitor cleanup to fix hanging tests --- services/clsi/app/js/DockerRunner.js | 31 ++++++++++++++----- services/clsi/npm-shrinkwrap.json | 6 ---- services/clsi/package.json | 1 - .../test/unit/js/DockerLockManagerTests.js | 1 - .../clsi/test/unit/js/DockerRunnerTests.js | 5 ++- 5 files changed, 27 insertions(+), 17 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index 5ac234b7e0..393ce3df37 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -35,6 +35,9 @@ const usingSiblingContainers = () => x => x.sandboxedCompilesHostDir ) != null +let containerMonitorTimeout +let containerMonitorInterval + module.exports = DockerRunner = { ERR_NOT_DIRECTORY: new Error('not a directory'), ERR_TERMINATED: new Error('terminated'), @@ -646,17 +649,29 @@ module.exports = DockerRunner = { { maxAge: DockerRunner.MAX_CONTAINER_AGE }, 'starting container expiry' ) + + // guarantee only one monitor is running + DockerRunner.stopContainerMonitor() + // randomise the start time const randomDelay = Math.floor(Math.random() * 5 * 60 * 1000) - return setTimeout( - () => - setInterval( - () => DockerRunner.destroyOldContainers(), - (oneHour = 60 * 60 * 1000) - ), + containerMonitorTimeout = setTimeout(() => { + containerMonitorInterval = setInterval( + () => DockerRunner.destroyOldContainers(), + (oneHour = 60 * 60 * 1000) + ) + }, randomDelay) + }, - randomDelay - ) + stopContainerMonitor() { + if (containerMonitorTimeout) { + clearTimeout(containerMonitorTimeout) + containerMonitorTimeout = undefined + } + if (containerMonitorInterval) { + clearInterval(containerMonitorTimeout) + containerMonitorTimeout = undefined + } } } diff --git a/services/clsi/npm-shrinkwrap.json b/services/clsi/npm-shrinkwrap.json index 40fed3c24f..05e00cd79a 100644 --- a/services/clsi/npm-shrinkwrap.json +++ b/services/clsi/npm-shrinkwrap.json @@ -1536,12 +1536,6 @@ "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=" }, - "coffeescript": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.6.0.tgz", - "integrity": "sha1-bdTeHrYveE2MjYCWdVLLpUf/2d4=", - "dev": true - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", diff --git a/services/clsi/package.json b/services/clsi/package.json index c38621aa23..87403ca5bd 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -44,7 +44,6 @@ "babel-eslint": "^10.0.3", "bunyan": "^0.22.1", "chai": "~1.8.1", - "coffeescript": "1.6.0", "eslint": "^6.6.0", "eslint-config-prettier": "^6.10.0", "eslint-config-standard": "^14.1.0", diff --git a/services/clsi/test/unit/js/DockerLockManagerTests.js b/services/clsi/test/unit/js/DockerLockManagerTests.js index 177d7a21f4..bc13c5ade5 100644 --- a/services/clsi/test/unit/js/DockerLockManagerTests.js +++ b/services/clsi/test/unit/js/DockerLockManagerTests.js @@ -12,7 +12,6 @@ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') require('chai').should() -require('coffee-script') const modulePath = require('path').join( __dirname, '../../../app/js/DockerLockManager' diff --git a/services/clsi/test/unit/js/DockerRunnerTests.js b/services/clsi/test/unit/js/DockerRunnerTests.js index 597c5d309d..d17d906dc7 100644 --- a/services/clsi/test/unit/js/DockerRunnerTests.js +++ b/services/clsi/test/unit/js/DockerRunnerTests.js @@ -17,7 +17,6 @@ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') require('chai').should() const { expect } = require('chai') -require('coffee-script') const modulePath = require('path').join( __dirname, '../../../app/js/DockerRunner' @@ -89,6 +88,10 @@ describe('DockerRunner', function() { return (this.Settings.clsi.docker.env = { PATH: 'mock-path' }) }) + afterEach(function() { + this.DockerRunner.stopContainerMonitor() + }) + describe('run', function() { beforeEach(function(done) { this.DockerRunner._getContainerOptions = sinon From a05c048b3bfd0ab207a292d2319d49336fe590d3 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <das7pad@outlook.com> Date: Wed, 12 Feb 2020 14:39:54 +0100 Subject: [PATCH 536/754] [misc] rename npm-shrinkwrap.json to package-lock.json --- services/clsi/{npm-shrinkwrap.json => package-lock.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename services/clsi/{npm-shrinkwrap.json => package-lock.json} (100%) diff --git a/services/clsi/npm-shrinkwrap.json b/services/clsi/package-lock.json similarity index 100% rename from services/clsi/npm-shrinkwrap.json rename to services/clsi/package-lock.json From 6155c8a09753b05991155dd8a5057d3531897a9c Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <das7pad@outlook.com> Date: Wed, 12 Feb 2020 14:44:16 +0100 Subject: [PATCH 537/754] [misc] cleanup unused dependency on mongo and redis --- services/clsi/buildscript.txt | 2 +- services/clsi/docker-compose.ci.yml | 10 ---------- services/clsi/docker-compose.yml | 11 ----------- 3 files changed, 1 insertion(+), 22 deletions(-) diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index 9cccf33aa7..8db0cd0ee1 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -4,7 +4,7 @@ clsi --env-add= --node-version=10.19.0 --acceptance-creds=None ---dependencies=mongo,redis +--dependencies= --docker-repos=gcr.io/overleaf-ops --env-pass-through=TEXLIVE_IMAGE --script-version=1.3.5 diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index d80a4ce160..aa7243e156 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -27,11 +27,6 @@ services: MOCHA_GREP: ${MOCHA_GREP} NODE_ENV: test TEXLIVE_IMAGE: - depends_on: - mongo: - condition: service_healthy - redis: - condition: service_healthy command: npm run test:acceptance:_run @@ -42,8 +37,3 @@ services: - ./:/tmp/build/ command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs . user: root - redis: - image: redis - - mongo: - image: mongo:3.6 diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 8cc5973301..74b4471eb9 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -36,16 +36,5 @@ services: MOCHA_GREP: ${MOCHA_GREP} LOG_LEVEL: ERROR NODE_ENV: test - depends_on: - mongo: - condition: service_healthy - redis: - condition: service_healthy command: npm run test:acceptance - redis: - image: redis - - mongo: - image: mongo:3.6 - From 92ed86bb33a9539c8a705560ce862f836fafad12 Mon Sep 17 00:00:00 2001 From: decaffeinate <mserranom@gmail.com> Date: Tue, 25 Feb 2020 17:03:36 +0100 Subject: [PATCH 538/754] decaffeinate: Rename SmokeTests.coffee from .coffee to .js --- .../clsi/test/smoke/coffee/{SmokeTests.coffee => SmokeTests.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename services/clsi/test/smoke/coffee/{SmokeTests.coffee => SmokeTests.js} (100%) diff --git a/services/clsi/test/smoke/coffee/SmokeTests.coffee b/services/clsi/test/smoke/coffee/SmokeTests.js similarity index 100% rename from services/clsi/test/smoke/coffee/SmokeTests.coffee rename to services/clsi/test/smoke/coffee/SmokeTests.js From b6991f5ff90ae35a882f31844f607b276081d85c Mon Sep 17 00:00:00 2001 From: decaffeinate <mserranom@gmail.com> Date: Tue, 25 Feb 2020 17:03:37 +0100 Subject: [PATCH 539/754] decaffeinate: Convert SmokeTests.coffee to JS --- services/clsi/test/smoke/coffee/SmokeTests.js | 76 ++++++++++++------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/services/clsi/test/smoke/coffee/SmokeTests.js b/services/clsi/test/smoke/coffee/SmokeTests.js index 9ecf09c17b..a8ad80b5d2 100644 --- a/services/clsi/test/smoke/coffee/SmokeTests.js +++ b/services/clsi/test/smoke/coffee/SmokeTests.js @@ -1,22 +1,29 @@ -chai = require("chai") -chai.should() unless Object.prototype.should? -expect = chai.expect -request = require "request" -Settings = require "settings-sharelatex" +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const chai = require("chai"); +if (Object.prototype.should == null) { chai.should(); } +const { expect } = chai; +const request = require("request"); +const Settings = require("settings-sharelatex"); -buildUrl = (path) -> "http://#{Settings.internal.clsi.host}:#{Settings.internal.clsi.port}/#{path}" +const buildUrl = path => `http://${Settings.internal.clsi.host}:${Settings.internal.clsi.port}/${path}`; -url = buildUrl("project/smoketest-#{process.pid}/compile") +const url = buildUrl(`project/smoketest-${process.pid}/compile`); -describe "Running a compile", -> - before (done) -> - request.post { - url: url - json: - compile: - resources: [ - path: "main.tex" - content: """ +describe("Running a compile", function() { + before(function(done) { + return request.post({ + url, + json: { + compile: { + resources: [{ + path: "main.tex", + content: `\ % Membrane-like surface % Author: Yotam Avital \\documentclass{article} @@ -47,18 +54,31 @@ describe "Running a compile", -> } } \\end{tikzpicture} -\\end{document} - """ +\\end{document}\ +` + } ] - }, (@error, @response, @body) => - done() + } + } + }, (error, response, body) => { + this.error = error; + this.response = response; + this.body = body; + return done(); + }); + }); - it "should return the pdf", -> - for file in @body.compile.outputFiles - return if file.type == "pdf" - throw new Error("no pdf returned") + it("should return the pdf", function() { + for (let file of Array.from(this.body.compile.outputFiles)) { + if (file.type === "pdf") { return; } + } + throw new Error("no pdf returned"); + }); - it "should return the log", -> - for file in @body.compile.outputFiles - return if file.type == "log" - throw new Error("no log returned") + return it("should return the log", function() { + for (let file of Array.from(this.body.compile.outputFiles)) { + if (file.type === "log") { return; } + } + throw new Error("no log returned"); + }); +}); From 2057a565ec03c3a7fe6fc4dc38210adcad9196d5 Mon Sep 17 00:00:00 2001 From: decaffeinate <mserranom@gmail.com> Date: Tue, 25 Feb 2020 17:03:39 +0100 Subject: [PATCH 540/754] decaffeinate: Run post-processing cleanups on SmokeTests.coffee --- services/clsi/test/smoke/coffee/SmokeTests.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/services/clsi/test/smoke/coffee/SmokeTests.js b/services/clsi/test/smoke/coffee/SmokeTests.js index a8ad80b5d2..6540401c7e 100644 --- a/services/clsi/test/smoke/coffee/SmokeTests.js +++ b/services/clsi/test/smoke/coffee/SmokeTests.js @@ -1,3 +1,8 @@ +/* eslint-disable + 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 @@ -69,14 +74,14 @@ describe("Running a compile", function() { }); it("should return the pdf", function() { - for (let file of Array.from(this.body.compile.outputFiles)) { + for (const file of Array.from(this.body.compile.outputFiles)) { if (file.type === "pdf") { return; } } throw new Error("no pdf returned"); }); return it("should return the log", function() { - for (let file of Array.from(this.body.compile.outputFiles)) { + for (const file of Array.from(this.body.compile.outputFiles)) { if (file.type === "log") { return; } } throw new Error("no log returned"); From e20da38e09947defc67ba459cba2edcb0a6e65cc Mon Sep 17 00:00:00 2001 From: decaffeinate <mserranom@gmail.com> Date: Tue, 25 Feb 2020 17:03:57 +0100 Subject: [PATCH 541/754] decaffeinate: Rename loadTest.coffee from .coffee to .js --- services/clsi/test/load/coffee/{loadTest.coffee => loadTest.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename services/clsi/test/load/coffee/{loadTest.coffee => loadTest.js} (100%) diff --git a/services/clsi/test/load/coffee/loadTest.coffee b/services/clsi/test/load/coffee/loadTest.js similarity index 100% rename from services/clsi/test/load/coffee/loadTest.coffee rename to services/clsi/test/load/coffee/loadTest.js From 3901fe9cd20538a4c39065f2958d994b796b13c0 Mon Sep 17 00:00:00 2001 From: decaffeinate <mserranom@gmail.com> Date: Tue, 25 Feb 2020 17:03:57 +0100 Subject: [PATCH 542/754] decaffeinate: Convert loadTest.coffee to JS --- services/clsi/test/load/coffee/loadTest.js | 144 ++++++++++++--------- 1 file changed, 85 insertions(+), 59 deletions(-) diff --git a/services/clsi/test/load/coffee/loadTest.js b/services/clsi/test/load/coffee/loadTest.js index 26a23fba50..d5ddecf665 100644 --- a/services/clsi/test/load/coffee/loadTest.js +++ b/services/clsi/test/load/coffee/loadTest.js @@ -1,71 +1,97 @@ -request = require "request" -Settings = require "settings-sharelatex" -async = require("async") -fs = require("fs") -_ = require("underscore") -concurentCompiles = 5 -totalCompiles = 50 +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const request = require("request"); +const Settings = require("settings-sharelatex"); +const async = require("async"); +const fs = require("fs"); +const _ = require("underscore"); +const concurentCompiles = 5; +const totalCompiles = 50; -buildUrl = (path) -> "http://#{Settings.internal.clsi.host}:#{Settings.internal.clsi.port}/#{path}" +const buildUrl = path => `http://${Settings.internal.clsi.host}:${Settings.internal.clsi.port}/${path}`; -mainTexContent = fs.readFileSync("./bulk.tex", "utf-8") +const mainTexContent = fs.readFileSync("./bulk.tex", "utf-8"); -compileTimes = [] -failedCount = 0 +const compileTimes = []; +let failedCount = 0; -getAverageCompileTime = -> - totalTime = _.reduce compileTimes, (sum, time)-> - sum + time - , 0 - return totalTime / compileTimes.length +const getAverageCompileTime = function() { + const totalTime = _.reduce(compileTimes, (sum, time)=> sum + time + , 0); + return totalTime / compileTimes.length; +}; -makeRequest = (compileNumber, callback)-> - bulkBodyCount = 7 - bodyContent = "" - while --bulkBodyCount - bodyContent = bodyContent+=mainTexContent +const makeRequest = function(compileNumber, callback){ + let bulkBodyCount = 7; + let bodyContent = ""; + while (--bulkBodyCount) { + bodyContent = (bodyContent+=mainTexContent); + } - startTime = new Date() - request.post { - url: buildUrl("project/loadcompile-#{compileNumber}/compile") - json: - compile: - resources: [ - path: "main.tex" - content: """ - \\documentclass{article} - \\begin{document} - #{bodyContent} - \\end{document} - """ + const startTime = new Date(); + return request.post({ + url: buildUrl(`project/loadcompile-${compileNumber}/compile`), + json: { + compile: { + resources: [{ + path: "main.tex", + content: `\ +\\documentclass{article} +\\begin{document} +${bodyContent} +\\end{document}\ +` + } ] - }, (err, response, body)-> - if response.statusCode != 200 - failedCount++ - return callback("compile #{compileNumber} failed") - if err? - failedCount++ - return callback("failed") - totalTime = new Date() - startTime - console.log totalTime+"ms" - compileTimes.push(totalTime) - callback(err) + } + } + }, function(err, response, body){ + if (response.statusCode !== 200) { + failedCount++; + return callback(`compile ${compileNumber} failed`); + } + if (err != null) { + failedCount++; + return callback("failed"); + } + const totalTime = new Date() - startTime; + console.log(totalTime+"ms"); + compileTimes.push(totalTime); + return callback(err); + }); +}; -jobs = _.map [1..totalCompiles], (i)-> - return (cb)-> - makeRequest(i, cb) +const jobs = _.map(__range__(1, totalCompiles, true), i=> + cb=> makeRequest(i, cb) +); -startTime = new Date() -async.parallelLimit jobs, concurentCompiles, (err)-> - if err? - console.error err - console.log("total time taken = #{(new Date() - startTime)/1000}s") - console.log("total compiles = #{totalCompiles}") - console.log("concurent compiles = #{concurentCompiles}") - console.log("average time = #{getAverageCompileTime()/1000}s") - console.log("max time = #{_.max(compileTimes)/1000}s") - console.log("min time = #{_.min(compileTimes)/1000}s") - console.log("total failures = #{failedCount}") +const startTime = new Date(); +async.parallelLimit(jobs, concurentCompiles, function(err){ + if (err != null) { + console.error(err); + } + console.log(`total time taken = ${(new Date() - startTime)/1000}s`); + console.log(`total compiles = ${totalCompiles}`); + console.log(`concurent compiles = ${concurentCompiles}`); + console.log(`average time = ${getAverageCompileTime()/1000}s`); + console.log(`max time = ${_.max(compileTimes)/1000}s`); + console.log(`min time = ${_.min(compileTimes)/1000}s`); + return console.log(`total failures = ${failedCount}`); +}); + +function __range__(left, right, inclusive) { + let range = []; + let ascending = left < right; + let end = !inclusive ? right : ascending ? right + 1 : right - 1; + for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) { + range.push(i); + } + return range; +} \ No newline at end of file From 252f4c704bb8a4ec80bc3219cea55fbdc9181c4a Mon Sep 17 00:00:00 2001 From: decaffeinate <mserranom@gmail.com> Date: Tue, 25 Feb 2020 17:03:58 +0100 Subject: [PATCH 543/754] decaffeinate: Run post-processing cleanups on loadTest.coffee --- services/clsi/test/load/coffee/loadTest.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/services/clsi/test/load/coffee/loadTest.js b/services/clsi/test/load/coffee/loadTest.js index d5ddecf665..2e769a59e5 100644 --- a/services/clsi/test/load/coffee/loadTest.js +++ b/services/clsi/test/load/coffee/loadTest.js @@ -1,3 +1,8 @@ +/* eslint-disable + standard/no-callback-literal, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns @@ -50,7 +55,7 @@ ${bodyContent} ] } } - }, function(err, response, body){ + }, (err, response, body) => { if (response.statusCode !== 200) { failedCount++; return callback(`compile ${compileNumber} failed`); @@ -72,7 +77,7 @@ const jobs = _.map(__range__(1, totalCompiles, true), i=> ); const startTime = new Date(); -async.parallelLimit(jobs, concurentCompiles, function(err){ +async.parallelLimit(jobs, concurentCompiles, (err) => { if (err != null) { console.error(err); } @@ -87,9 +92,9 @@ async.parallelLimit(jobs, concurentCompiles, function(err){ function __range__(left, right, inclusive) { - let range = []; - let ascending = left < right; - let end = !inclusive ? right : ascending ? right + 1 : right - 1; + const range = []; + const ascending = left < right; + const end = !inclusive ? right : ascending ? right + 1 : right - 1; for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) { range.push(i); } From 41533d8888b5172edd5ff43d0c61d18b26db71a8 Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Tue, 25 Feb 2020 17:15:31 +0100 Subject: [PATCH 544/754] moved decaffeinated files to js folder --- services/clsi/test/load/coffee/loadTest.js | 102 ----------------- .../clsi/test/load/{coffee => js}/bulk.tex | 0 services/clsi/test/load/js/loadTest.js | 103 ++++++++++++++++++ .../test/smoke/{coffee => js}/SmokeTests.js | 93 +++++++++------- 4 files changed, 155 insertions(+), 143 deletions(-) delete mode 100644 services/clsi/test/load/coffee/loadTest.js rename services/clsi/test/load/{coffee => js}/bulk.tex (100%) create mode 100644 services/clsi/test/load/js/loadTest.js rename services/clsi/test/smoke/{coffee => js}/SmokeTests.js (62%) diff --git a/services/clsi/test/load/coffee/loadTest.js b/services/clsi/test/load/coffee/loadTest.js deleted file mode 100644 index 2e769a59e5..0000000000 --- a/services/clsi/test/load/coffee/loadTest.js +++ /dev/null @@ -1,102 +0,0 @@ -/* eslint-disable - standard/no-callback-literal, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -const request = require("request"); -const Settings = require("settings-sharelatex"); -const async = require("async"); -const fs = require("fs"); -const _ = require("underscore"); -const concurentCompiles = 5; -const totalCompiles = 50; - -const buildUrl = path => `http://${Settings.internal.clsi.host}:${Settings.internal.clsi.port}/${path}`; - -const mainTexContent = fs.readFileSync("./bulk.tex", "utf-8"); - -const compileTimes = []; -let failedCount = 0; - -const getAverageCompileTime = function() { - const totalTime = _.reduce(compileTimes, (sum, time)=> sum + time - , 0); - return totalTime / compileTimes.length; -}; - -const makeRequest = function(compileNumber, callback){ - let bulkBodyCount = 7; - let bodyContent = ""; - while (--bulkBodyCount) { - bodyContent = (bodyContent+=mainTexContent); - } - - - const startTime = new Date(); - return request.post({ - url: buildUrl(`project/loadcompile-${compileNumber}/compile`), - json: { - compile: { - resources: [{ - path: "main.tex", - content: `\ -\\documentclass{article} -\\begin{document} -${bodyContent} -\\end{document}\ -` - } - ] - } - } - }, (err, response, body) => { - if (response.statusCode !== 200) { - failedCount++; - return callback(`compile ${compileNumber} failed`); - } - if (err != null) { - failedCount++; - return callback("failed"); - } - const totalTime = new Date() - startTime; - console.log(totalTime+"ms"); - compileTimes.push(totalTime); - return callback(err); - }); -}; - - -const jobs = _.map(__range__(1, totalCompiles, true), i=> - cb=> makeRequest(i, cb) -); - -const startTime = new Date(); -async.parallelLimit(jobs, concurentCompiles, (err) => { - if (err != null) { - console.error(err); - } - console.log(`total time taken = ${(new Date() - startTime)/1000}s`); - console.log(`total compiles = ${totalCompiles}`); - console.log(`concurent compiles = ${concurentCompiles}`); - console.log(`average time = ${getAverageCompileTime()/1000}s`); - console.log(`max time = ${_.max(compileTimes)/1000}s`); - console.log(`min time = ${_.min(compileTimes)/1000}s`); - return console.log(`total failures = ${failedCount}`); -}); - - -function __range__(left, right, inclusive) { - const range = []; - const ascending = left < right; - const end = !inclusive ? right : ascending ? right + 1 : right - 1; - for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) { - range.push(i); - } - return range; -} \ No newline at end of file diff --git a/services/clsi/test/load/coffee/bulk.tex b/services/clsi/test/load/js/bulk.tex similarity index 100% rename from services/clsi/test/load/coffee/bulk.tex rename to services/clsi/test/load/js/bulk.tex diff --git a/services/clsi/test/load/js/loadTest.js b/services/clsi/test/load/js/loadTest.js new file mode 100644 index 0000000000..ff9850efd8 --- /dev/null +++ b/services/clsi/test/load/js/loadTest.js @@ -0,0 +1,103 @@ +/* eslint-disable + standard/no-callback-literal, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const request = require('request') +const Settings = require('settings-sharelatex') +const async = require('async') +const fs = require('fs') +const _ = require('underscore') +const concurentCompiles = 5 +const totalCompiles = 50 + +const buildUrl = path => + `http://${Settings.internal.clsi.host}:${Settings.internal.clsi.port}/${path}` + +const mainTexContent = fs.readFileSync('./bulk.tex', 'utf-8') + +const compileTimes = [] +let failedCount = 0 + +const getAverageCompileTime = function() { + const totalTime = _.reduce(compileTimes, (sum, time) => sum + time, 0) + return totalTime / compileTimes.length +} + +const makeRequest = function(compileNumber, callback) { + let bulkBodyCount = 7 + let bodyContent = '' + while (--bulkBodyCount) { + bodyContent = bodyContent += mainTexContent + } + + const startTime = new Date() + return request.post( + { + url: buildUrl(`project/loadcompile-${compileNumber}/compile`), + json: { + compile: { + resources: [ + { + path: 'main.tex', + content: `\ +\\documentclass{article} +\\begin{document} +${bodyContent} +\\end{document}\ +` + } + ] + } + } + }, + (err, response, body) => { + if (response.statusCode !== 200) { + failedCount++ + return callback(`compile ${compileNumber} failed`) + } + if (err != null) { + failedCount++ + return callback('failed') + } + const totalTime = new Date() - startTime + console.log(totalTime + 'ms') + compileTimes.push(totalTime) + return callback(err) + } + ) +} + +const jobs = _.map(__range__(1, totalCompiles, true), i => cb => + makeRequest(i, cb) +) + +const startTime = new Date() +async.parallelLimit(jobs, concurentCompiles, err => { + if (err != null) { + console.error(err) + } + console.log(`total time taken = ${(new Date() - startTime) / 1000}s`) + console.log(`total compiles = ${totalCompiles}`) + console.log(`concurent compiles = ${concurentCompiles}`) + console.log(`average time = ${getAverageCompileTime() / 1000}s`) + console.log(`max time = ${_.max(compileTimes) / 1000}s`) + console.log(`min time = ${_.min(compileTimes) / 1000}s`) + return console.log(`total failures = ${failedCount}`) +}) + +function __range__(left, right, inclusive) { + const range = [] + const ascending = left < right + const end = !inclusive ? right : ascending ? right + 1 : right - 1 + for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) { + range.push(i) + } + return range +} diff --git a/services/clsi/test/smoke/coffee/SmokeTests.js b/services/clsi/test/smoke/js/SmokeTests.js similarity index 62% rename from services/clsi/test/smoke/coffee/SmokeTests.js rename to services/clsi/test/smoke/js/SmokeTests.js index 6540401c7e..851ea85079 100644 --- a/services/clsi/test/smoke/coffee/SmokeTests.js +++ b/services/clsi/test/smoke/js/SmokeTests.js @@ -10,25 +10,30 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const chai = require("chai"); -if (Object.prototype.should == null) { chai.should(); } -const { expect } = chai; -const request = require("request"); -const Settings = require("settings-sharelatex"); +const chai = require('chai') +if (Object.prototype.should == null) { + chai.should() +} +const { expect } = chai +const request = require('request') +const Settings = require('settings-sharelatex') -const buildUrl = path => `http://${Settings.internal.clsi.host}:${Settings.internal.clsi.port}/${path}`; +const buildUrl = path => + `http://${Settings.internal.clsi.host}:${Settings.internal.clsi.port}/${path}` -const url = buildUrl(`project/smoketest-${process.pid}/compile`); +const url = buildUrl(`project/smoketest-${process.pid}/compile`) -describe("Running a compile", function() { - before(function(done) { - return request.post({ - url, - json: { - compile: { - resources: [{ - path: "main.tex", - content: `\ +describe('Running a compile', function() { + before(function(done) { + return request.post( + { + url, + json: { + compile: { + resources: [ + { + path: 'main.tex', + content: `\ % Membrane-like surface % Author: Yotam Avital \\documentclass{article} @@ -61,29 +66,35 @@ describe("Running a compile", function() { \\end{tikzpicture} \\end{document}\ ` - } - ] - } - } - }, (error, response, body) => { - this.error = error; - this.response = response; - this.body = body; - return done(); - }); - }); + } + ] + } + } + }, + (error, response, body) => { + this.error = error + this.response = response + this.body = body + return done() + } + ) + }) - it("should return the pdf", function() { - for (const file of Array.from(this.body.compile.outputFiles)) { - if (file.type === "pdf") { return; } - } - throw new Error("no pdf returned"); - }); - - return it("should return the log", function() { - for (const file of Array.from(this.body.compile.outputFiles)) { - if (file.type === "log") { return; } - } - throw new Error("no log returned"); - }); -}); + it('should return the pdf', function() { + for (const file of Array.from(this.body.compile.outputFiles)) { + if (file.type === 'pdf') { + return + } + } + throw new Error('no pdf returned') + }) + + return it('should return the log', function() { + for (const file of Array.from(this.body.compile.outputFiles)) { + if (file.type === 'log') { + return + } + } + throw new Error('no log returned') + }) +}) From 01c46e9e947cd0806a4b4393ac81b4ce340f31a6 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Wed, 11 Mar 2020 10:06:55 +0000 Subject: [PATCH 545/754] remove ./bin/install_texlive_gce.sh which shouldn't be needed we shouldn't have needed this for a while, I think it is a cause of startup delay, however this should have stopped other missing texlive images in the past which is strange --- services/clsi/bin/install_texlive_gce.sh | 21 --------------------- services/clsi/entrypoint.sh | 1 - 2 files changed, 22 deletions(-) delete mode 100755 services/clsi/bin/install_texlive_gce.sh diff --git a/services/clsi/bin/install_texlive_gce.sh b/services/clsi/bin/install_texlive_gce.sh deleted file mode 100755 index ee6efba471..0000000000 --- a/services/clsi/bin/install_texlive_gce.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -METADATA=http://metadata.google.internal./computeMetadata/v1 -SVC_ACCT=$METADATA/instance/service-accounts/default -PROJECT_URL=$METADATA/project/project-id -ACCESS_TOKEN=$(curl -s -H 'Metadata-Flavor: Google' $SVC_ACCT/token | cut -d'"' -f 4) -if [ -z "$ACCESS_TOKEN" ]; then - echo "No acccess token to download texlive-full images from google container, continuing without downloading. This is likely not a google cloud enviroment." - exit 0 -fi -PROJECT=$(curl -s -H 'Metadata-Flavor: Google' $PROJECT_URL) -if [ -z "$PROJECT" ]; then - echo "No project name to download texlive-full images from google container, continuing without downloading. This is likely not a google cloud enviroment." - exit 0 -fi -docker login -u '_token' -p $ACCESS_TOKEN https://gcr.io -docker pull --all-tags gcr.io/$PROJECT/texlive-full -cp /app/bin/synctex /app/bin/synctex-mount/synctex - -echo "Finished downloading texlive-full images" - - diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh index 71ced1412b..15a41db949 100755 --- a/services/clsi/entrypoint.sh +++ b/services/clsi/entrypoint.sh @@ -21,5 +21,4 @@ chown -R node:node /app chown -R node:node /app/bin -./bin/install_texlive_gce.sh exec runuser -u node -- "$@" From 44ec451b1196f5173d1e9f221a90d7afd123392d Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Wed, 11 Mar 2020 11:55:44 +0100 Subject: [PATCH 546/754] updated build-scripts --- services/clsi/.eslintrc | 1 - services/clsi/.prettierrc | 1 - services/clsi/Dockerfile | 1 - services/clsi/Makefile | 1 - services/clsi/buildscript.txt | 10 +- services/clsi/docker-compose.ci.yml | 1 - services/clsi/docker-compose.yml | 1 - services/clsi/package-lock.json | 207 +++++++++++++++++++--------- services/clsi/package.json | 10 +- 9 files changed, 154 insertions(+), 79 deletions(-) diff --git a/services/clsi/.eslintrc b/services/clsi/.eslintrc index 42a4b5cace..2e945d6ffb 100644 --- a/services/clsi/.eslintrc +++ b/services/clsi/.eslintrc @@ -1,7 +1,6 @@ // this file was auto-generated, do not edit it directly. // instead run bin/update_build_scripts from // https://github.com/sharelatex/sharelatex-dev-environment -// Version: 1.3.5 { "extends": [ "standard", diff --git a/services/clsi/.prettierrc b/services/clsi/.prettierrc index 5845b82113..24f9ec526f 100644 --- a/services/clsi/.prettierrc +++ b/services/clsi/.prettierrc @@ -1,7 +1,6 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.3.5 { "semi": false, "singleQuote": true diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 9faccd4957..27158b5d6a 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -1,7 +1,6 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.3.5 FROM node:10.19.0 as base diff --git a/services/clsi/Makefile b/services/clsi/Makefile index 88234f2a23..0f1d274f50 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -1,7 +1,6 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.3.5 BUILD_NUMBER ?= local BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index 8db0cd0ee1..52686425b5 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -1,10 +1,10 @@ clsi ---public-repo=True ---language=es ---env-add= ---node-version=10.19.0 --acceptance-creds=None --dependencies= --docker-repos=gcr.io/overleaf-ops +--env-add= --env-pass-through=TEXLIVE_IMAGE ---script-version=1.3.5 +--language=es +--node-version=10.19.0 +--public-repo=True +--script-version=2.0.0 diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index aa7243e156..facbd5fc92 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -1,7 +1,6 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.3.5 version: "2.3" diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 74b4471eb9..6fc9eab26b 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -1,7 +1,6 @@ # This file was auto-generated, do not edit it directly. # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -# Version: 1.3.5 version: "2.3" diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index 05e00cd79a..c9f9978a07 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -851,6 +851,12 @@ "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, "@types/console-log-level": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@types/console-log-level/-/console-log-level-1.4.0.tgz", @@ -1039,12 +1045,20 @@ } }, "ansi-escapes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", - "integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", "dev": true, "requires": { - "type-fest": "^0.8.1" + "type-fest": "^0.11.0" + }, + "dependencies": { + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + } } }, "ansi-regex": { @@ -1973,9 +1987,9 @@ "dev": true }, "eslint": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.6.0.tgz", - "integrity": "sha512-PpEBq7b6qY/qrOmpYQ/jTMDYfuQMELR4g4WI1M/NaSDDD/bdcMb+dj4Hgks7p41kW2caXsPsEZAEAyAgjVVC0g==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -1993,7 +2007,7 @@ "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", "glob-parent": "^5.0.0", - "globals": "^11.7.0", + "globals": "^12.1.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", @@ -2006,7 +2020,7 @@ "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.8.2", + "optionator": "^0.8.3", "progress": "^2.0.0", "regexpp": "^2.0.1", "semver": "^6.1.2", @@ -2018,9 +2032,9 @@ }, "dependencies": { "ajv": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz", - "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", + "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -2050,6 +2064,15 @@ "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", "dev": true }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, "lodash": { "version": "4.17.15", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", @@ -2252,12 +2275,24 @@ } }, "eslint-plugin-mocha": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-6.2.2.tgz", - "integrity": "sha512-oNhPzfkT6Q6CJ0HMVJ2KLxEWG97VWGTmuHOoRcDLE0U88ugUyFNV9wrT2XIt5cGtqc5W9k38m4xTN34L09KhBA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-6.3.0.tgz", + "integrity": "sha512-Cd2roo8caAyG21oKaaNTj7cqeYRWW1I2B5SfpKRp0Ip1gkfwoR1Ow0IGlPWnNjzywdF4n+kHL8/9vM6zCJUxdg==", "dev": true, "requires": { - "ramda": "^0.26.1" + "eslint-utils": "^2.0.0", + "ramda": "^0.27.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", + "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + } } }, "eslint-plugin-node": { @@ -2382,20 +2417,26 @@ "dev": true }, "espree": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz", - "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", "dev": true, "requires": { - "acorn": "^7.1.0", - "acorn-jsx": "^5.1.0", + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", "eslint-visitor-keys": "^1.1.0" }, "dependencies": { "acorn": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", - "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", + "dev": true + }, + "acorn-jsx": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", + "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", "dev": true } } @@ -3104,23 +3145,23 @@ "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=" }, "inquirer": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.4.tgz", - "integrity": "sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", + "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", "dev": true, "requires": { "ansi-escapes": "^4.2.1", - "chalk": "^2.4.2", + "chalk": "^3.0.0", "cli-cursor": "^3.1.0", "cli-width": "^2.0.0", "external-editor": "^3.0.3", "figures": "^3.0.0", "lodash": "^4.17.15", "mute-stream": "0.0.8", - "run-async": "^2.2.0", + "run-async": "^2.4.0", "rxjs": "^6.5.3", "string-width": "^4.1.0", - "strip-ansi": "^5.1.0", + "strip-ansi": "^6.0.0", "through": "^2.3.6" }, "dependencies": { @@ -3130,6 +3171,47 @@ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -3142,6 +3224,15 @@ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true }, + "run-async": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", + "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", @@ -3151,34 +3242,24 @@ "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - } + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" } } } @@ -5189,9 +5270,9 @@ "dev": true }, "ramda": { - "version": "0.26.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", - "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.0.tgz", + "integrity": "sha512-pVzZdDpWwWqEVVLshWUHjNwuVP7SfcmPraYuqocJp1yo2U1R7P+5QAfDhdItkuoGqIBnBYrtPp7rEPqDn9HlZA==", "dev": true }, "range-parser": { @@ -5815,19 +5896,19 @@ "dev": true }, "sqlite3": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.0.6.tgz", - "integrity": "sha512-EqBXxHdKiwvNMRCgml86VTL5TK1i0IKiumnfxykX0gh6H6jaKijAXvE9O1N7+omfNSawR2fOmIyJZcfe8HYWpw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.1.1.tgz", + "integrity": "sha512-CvT5XY+MWnn0HkbwVKJAyWEMfzpAPwnTiB3TobA5Mri44SrTovmmh499NPQP+gatkeOipqPlBLel7rn4E/PCQg==", "requires": { - "nan": "~2.10.0", + "nan": "^2.12.1", "node-pre-gyp": "^0.11.0", "request": "^2.87.0" }, "dependencies": { "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" } } }, diff --git a/services/clsi/package.json b/services/clsi/package.json index 87403ca5bd..6ff6ea2490 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -14,8 +14,8 @@ "test:unit": "npm run test:unit:_run -- --grep=$MOCHA_GREP", "nodemon": "nodemon --config nodemon.json", "lint": "node_modules/.bin/eslint .", - "format": "node_modules/.bin/prettier-eslint '**/*.js' --list-different", - "format:fix": "node_modules/.bin/prettier-eslint '**/*.js' --write" + "format": "node_modules/.bin/prettier-eslint $PWD'/**/*.js' --list-different", + "format:fix": "node_modules/.bin/prettier-eslint $PWD'/**/*.js' --write" }, "author": "James Allen <james@sharelatex.com>", "dependencies": { @@ -35,7 +35,7 @@ "sequelize": "^4.38.0", "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.1.0", "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", - "sqlite3": "^4.0.6", + "sqlite3": "^4.1.1", "underscore": "^1.8.2", "v8-profiler-node8": "^6.0.1", "wrench": "~1.5.4" @@ -44,7 +44,7 @@ "babel-eslint": "^10.0.3", "bunyan": "^0.22.1", "chai": "~1.8.1", - "eslint": "^6.6.0", + "eslint": "^6.8.0", "eslint-config-prettier": "^6.10.0", "eslint-config-standard": "^14.1.0", "eslint-config-standard-jsx": "^8.1.0", @@ -53,7 +53,7 @@ "eslint-plugin-chai-friendly": "^0.5.0", "eslint-plugin-import": "^2.20.1", "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-mocha": "^6.2.2", + "eslint-plugin-mocha": "^6.3.0", "eslint-plugin-node": "^11.0.0", "eslint-plugin-prettier": "^3.1.2", "eslint-plugin-promise": "^4.2.1", From a18ddffce9e531fef2e1e95539766ad56d3612f6 Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Wed, 11 Mar 2020 11:58:42 +0100 Subject: [PATCH 547/754] npm audit fix --- services/clsi/package-lock.json | 139 +++++++++++++++----------------- services/clsi/package.json | 4 +- 2 files changed, 66 insertions(+), 77 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index c9f9978a07..7d3c0e268a 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -879,7 +879,7 @@ "@types/geojson": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.6.tgz", - "integrity": "sha1-PgKXJyjGkkjCrwjWCkjLuGgP/98=" + "integrity": "sha512-Xqg/lIZMrUd0VRmSRbCAewtwGZiAk3mEUDvV4op1tGl+LvyPcb/MIOSxTl9z+9+J+R4/vpjiCAT4xeKzH9ji1w==" }, "@types/json-schema": { "version": "7.0.4", @@ -1015,9 +1015,9 @@ } }, "acorn": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz", - "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==" + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", + "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==" }, "acorn-jsx": { "version": "5.1.0", @@ -1282,9 +1282,9 @@ } }, "bluebird": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", - "integrity": "sha1-fQHG+WFsmlGrD4xUmnnf5uwz76c=" + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "body-parser": { "version": "1.18.3", @@ -1847,9 +1847,9 @@ } }, "dottie": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.1.tgz", - "integrity": "sha1-aXrZ1yAE23V00h+JJGajwoWJNlk=" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", + "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" }, "dtrace-provider": { "version": "0.2.8", @@ -2805,9 +2805,9 @@ } }, "generic-pool": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.4.2.tgz", - "integrity": "sha1-kv9xllINZwg5pnMICSoSqt8valk=" + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.5.0.tgz", + "integrity": "sha512-dEkxmX+egB2o4NR80c/q+xzLLzLX+k68/K8xv81XprD+Sk7ZtP14VugeCz+fUwv5FzpWq40pPtAkzPRqT8ka9w==" }, "get-caller-file": { "version": "2.0.5", @@ -3595,9 +3595,9 @@ } }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha1-s56mIp72B+zYniyN8SU2iRysm40=" + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "lodash.at": { "version": "4.6.0", @@ -3999,9 +3999,9 @@ "integrity": "sha1-dZ6kkayX1UusWtd2mW4qWMwbwiU=" }, "moment-timezone": { - "version": "0.5.23", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.23.tgz", - "integrity": "sha1-fLsA2ywUxxsZMDy0ew+wpthlFGM=", + "version": "0.5.28", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.28.tgz", + "integrity": "sha512-TDJkZvAyKIVWg5EtVqRzU97w0Rb0YVbfpqyjgu6GwXCAohVRqwZjf4fOzDE6p1Ch98Sro/8hQQi65WDXW5STPw==", "requires": { "moment": ">= 2.9.0" } @@ -4062,40 +4062,34 @@ } }, "mysql": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.6.2.tgz", - "integrity": "sha1-k3Gd0yT1fUHET7bX+GDPjmOFk7A=", + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", + "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", "requires": { - "bignumber.js": "2.0.7", - "readable-stream": "~1.1.13", - "require-all": "~1.0.0" + "bignumber.js": "9.0.0", + "readable-stream": "2.3.7", + "safe-buffer": "5.1.2", + "sqlstring": "2.3.1" }, "dependencies": { "bignumber.js": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.7.tgz", - "integrity": "sha1-husHB89qURCQnSPm6nQ0wU9QDxw=" - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" }, "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" } } }, @@ -5425,11 +5419,6 @@ } } }, - "require-all": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/require-all/-/require-all-1.0.0.tgz", - "integrity": "sha1-hINwjnzkxt+tmItQgPl4KbktIic=" - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -5639,16 +5628,16 @@ } }, "sequelize": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-4.42.0.tgz", - "integrity": "sha1-Q5Rnunv+fVr8xW1is+CRhg+/GPM=", + "version": "4.44.4", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-4.44.4.tgz", + "integrity": "sha512-nkHmYkbwQK7uwpgW9VBalCBnQqQ8mslTdgcBthtJLORuPvAYRPlfkXZMVUU9TLLJt9CX+/y0MYg0DpcP6ywsEQ==", "requires": { "bluebird": "^3.5.0", "cls-bluebird": "^2.1.0", "debug": "^3.1.0", "depd": "^1.1.0", "dottie": "^2.0.0", - "generic-pool": "^3.4.0", + "generic-pool": "3.5.0", "inflection": "1.12.0", "lodash": "^4.17.1", "moment": "^2.20.0", @@ -5665,20 +5654,15 @@ "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha1-6D0X3hbYp++3cX7b5fsQE17uYps=", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "requires": { "ms": "^2.1.1" } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=" - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha1-G0r0lV6zB3xQHCOHL8ZROBFYcTE=" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -5912,6 +5896,11 @@ } } }, + "sqlstring": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", + "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" + }, "sshpk": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", @@ -6197,17 +6186,17 @@ } }, "terraformer": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/terraformer/-/terraformer-1.0.9.tgz", - "integrity": "sha1-d4Uf70pJyQs0XcU88mgJ/fKdzaY=", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/terraformer/-/terraformer-1.0.10.tgz", + "integrity": "sha512-5c6cAfKTZHAeRdT8sIRRidhN1w+vsmf3RmQn+PKksFhTUnsBtjQdbJG2vaxM6T47IU2EeR1S8t8UjTYY9Q1yJA==", "requires": { - "@types/geojson": "^1.0.0" + "@types/geojson": "^7946.0.0 || ^1.0.0" } }, "terraformer-wkt-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/terraformer-wkt-parser/-/terraformer-wkt-parser-1.2.0.tgz", - "integrity": "sha1-ydasPf8l9MC9NE6WH0JpSWGDTDQ=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/terraformer-wkt-parser/-/terraformer-wkt-parser-1.2.1.tgz", + "integrity": "sha512-+CJyNLWb3lJ9RsZMTM66BY0MT3yIo4l4l22Jd9CrZuwzk54fsu4Sc7zejuS9fCITTuTQy3p06d4MZMVI7v5wSg==", "requires": { "@types/geojson": "^1.0.0", "terraformer": "~1.0.5" @@ -6445,9 +6434,9 @@ } }, "validator": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-10.9.0.tgz", - "integrity": "sha1-0QwRZztQYft8z0wRFEEkEbK6wqg=" + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", + "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==" }, "vary": { "version": "1.1.2", @@ -6567,9 +6556,9 @@ } }, "wkx": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.6.tgz", - "integrity": "sha1-Ioq1kuZFc4Lqb7efyCUFjQf85SM=", + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.8.tgz", + "integrity": "sha512-ikPXMM9IR/gy/LwiOSqWlSL3X/J5uk9EO2hHNRXS41eTLXaUFEVw9fn/593jW/tE5tedNg8YjT5HkCa4FqQZyQ==", "requires": { "@types/node": "*" } diff --git a/services/clsi/package.json b/services/clsi/package.json index 6ff6ea2490..350cefa59d 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -30,9 +30,9 @@ "lynx": "0.0.11", "metrics-sharelatex": "^2.3.0", "mkdirp": "0.3.5", - "mysql": "2.6.2", + "mysql": "^2.18.1", "request": "^2.21.0", - "sequelize": "^4.38.0", + "sequelize": "^4.44.4", "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.1.0", "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", "sqlite3": "^4.1.1", From 052b5624d357d222fed0abb8f5e7681427632963 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Wed, 11 Mar 2020 11:06:46 +0000 Subject: [PATCH 548/754] copy synctex over to /app/bin/synctex-mount in entrypoint --- services/clsi/entrypoint.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh index 15a41db949..07d902acc2 100755 --- a/services/clsi/entrypoint.sh +++ b/services/clsi/entrypoint.sh @@ -21,4 +21,6 @@ chown -R node:node /app chown -R node:node /app/bin +cp /app/bin/synctex /app/bin/synctex-mount/synctex + exec runuser -u node -- "$@" From 9883c0e3ff1dc1ff2f070115081d8b59284eb28e Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Wed, 11 Mar 2020 12:20:48 +0100 Subject: [PATCH 549/754] updated minor/patch dependencies --- services/clsi/package-lock.json | 597 +++++++++++++++++++------------- services/clsi/package.json | 26 +- 2 files changed, 363 insertions(+), 260 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index 7d3c0e268a..ac92dddb0f 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -14,23 +14,17 @@ } }, "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.7.tgz", + "integrity": "sha512-DQwjiKJqH4C3qGiyQCAExJHoZssn49JTMJgZ8SANGgVFdkupcUhLOdkAeoC6kmHZCPfoDG5M0b6cFlSN5wW7Ew==", "dev": true, "requires": { - "@babel/types": "^7.8.3", + "@babel/types": "^7.8.7", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" }, "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -80,9 +74,9 @@ } }, "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.7.tgz", + "integrity": "sha512-9JWls8WilDXFGxs0phaXAZgpxTZhSk/yOYH2hTHC0X1yC7Z78IJfvR1vJ+rmJKq3I35td2XzXzN6ZLYlna+r/A==", "dev": true }, "@babel/runtime": { @@ -94,29 +88,47 @@ "regenerator-runtime": "^0.13.2" } }, + "@babel/runtime-corejs3": { + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.8.7.tgz", + "integrity": "sha512-sc7A+H4I8kTd7S61dgB9RomXu/C+F4IrRr4Ytze4dnfx7AXEpCrejSNpjx7vq6y/Bak9S6Kbk65a/WgMLtg43Q==", + "dev": true, + "requires": { + "core-js-pure": "^3.0.0", + "regenerator-runtime": "^0.13.4" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz", + "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==", + "dev": true + } + } + }, "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" } }, "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.6.tgz", + "integrity": "sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", + "@babel/generator": "^7.8.6", "@babel/helper-function-name": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" @@ -131,12 +143,6 @@ "ms": "^2.1.1" } }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -146,22 +152,14 @@ } }, "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.7.tgz", + "integrity": "sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw==", "dev": true, "requires": { "esutils": "^2.0.2", "lodash": "^4.17.13", "to-fast-properties": "^2.0.0" - }, - "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - } } }, "@google-cloud/common": { @@ -230,9 +228,9 @@ "integrity": "sha512-4yAHDC52TEMCNcMzVC8WlqnKKKq+Ssi2lXoUg9zWWkZ6U6tq9ZBRYLHHCRdfU+EU9YJsVmivwGcKYCjRGjnf4Q==" }, "coffeescript": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.4.1.tgz", - "integrity": "sha512-34GV1aHrsMpTaO3KfMJL40ZNuvKDR/g98THHnE9bQj8HjMaZvSrLik99WWqyMhRtbe8V5hpx5iLgdcSvM/S2wg==" + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.5.1.tgz", + "integrity": "sha512-J2jRPX0eeFh5VKyVnoLrfVFgLZtnnmp96WQSLAS8OrLm2wtQLcnikYKe1gViJKDH7vucjuhHvBKKBP3rKcD1tQ==" }, "debug": { "version": "3.2.6", @@ -898,9 +896,9 @@ "integrity": "sha1-IOhWUbYv2GZW5Xycm8dxqxVwvFk=" }, "@types/request": { - "version": "2.48.3", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.3.tgz", - "integrity": "sha512-3Wo2jNYwqgXcIz/rrq18AdOZUQB8cQ34CXZo+LUwPJNpvRAL86+Kc2wwI8mqpz9Cr1V+enIox5v+WZhy/p3h8w==", + "version": "2.48.4", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.4.tgz", + "integrity": "sha512-W1t1MTKYR8PxICH+A4HgEIPuAC3sbljoEVfyZbeFJJDbr30guDspJri2XOaM2E+Un7ZjrihaDi7cf6fPa2tbgw==", "requires": { "@types/caseless": "*", "@types/node": "*", @@ -926,9 +924,9 @@ "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==" }, "@types/tough-cookie": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", - "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==" + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.6.tgz", + "integrity": "sha512-wHNBMnkoEBiRAd3s8KTKwIuO9biFtTf0LehITzBhSco+HQI0xkXZbLOD55SW3Aqw3oUkHstkm5SPv58yaAdFPQ==" }, "@typescript-eslint/experimental-utils": { "version": "1.13.0", @@ -1006,12 +1004,12 @@ } }, "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" + "mime-types": "~2.1.24", + "negotiator": "0.6.2" } }, "acorn": { @@ -1034,11 +1032,11 @@ } }, "ajv": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz", - "integrity": "sha1-ys7M9HS/P8POOxR0Q3EaJAY8ww0=", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", + "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", "requires": { - "fast-deep-equal": "^2.0.1", + "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" @@ -1150,7 +1148,7 @@ "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha1-jSR136tVO7M+d7VOWeiAu4ziMTY=", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", "requires": { "safer-buffer": "~2.1.0" } @@ -1179,9 +1177,9 @@ "dev": true }, "async": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/async/-/async-0.2.9.tgz", - "integrity": "sha1-32MGD789Myhqdqr21Vophtn/hhk=" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" }, "async-listener": { "version": "0.6.10", @@ -1203,9 +1201,9 @@ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha1-8OAD2cqef1nHpQiUXXsu+aBKVC8=" + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" }, "axios": { "version": "0.18.1", @@ -1223,15 +1221,15 @@ "dev": true }, "babel-eslint": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.3.tgz", - "integrity": "sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@babel/types": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", "eslint-visitor-keys": "^1.0.0", "resolve": "^1.12.0" } @@ -1287,20 +1285,30 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "body-parser": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", - "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", "requires": { - "bytes": "3.0.0", + "bytes": "3.1.0", "content-type": "~1.0.4", "debug": "2.6.9", "depd": "~1.1.2", - "http-errors": "~1.6.3", - "iconv-lite": "0.4.23", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", "on-finished": "~2.3.0", - "qs": "6.5.2", - "raw-body": "2.3.3", - "type-is": "~1.6.16" + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } } }, "boolify": { @@ -1359,13 +1367,15 @@ "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==" }, "bunyan": { - "version": "0.22.3", - "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-0.22.3.tgz", - "integrity": "sha1-ehncG0yMZF90AkGnQPIkUUfGfsI=", + "version": "1.8.12", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz", + "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=", "dev": true, "requires": { - "dtrace-provider": "0.2.8", - "mv": "~2" + "dtrace-provider": "~0.8", + "moment": "^2.10.6", + "mv": "~2", + "safe-json-stringify": "~1" } }, "buster-core": { @@ -1384,9 +1394,9 @@ } }, "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, "callsites": { "version": "3.1.0", @@ -1566,9 +1576,9 @@ "dev": true }, "combined-stream": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", - "integrity": "sha1-LR0kMXr7ir6V1tLAsHtXgTU52Cg=", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "requires": { "delayed-stream": "~1.0.0" } @@ -1617,14 +1627,17 @@ "dev": true }, "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } }, "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=" + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, "continuation-local-storage": { "version": "3.2.1", @@ -1651,6 +1664,12 @@ "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==", "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==", + "dev": true + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -1852,11 +1871,23 @@ "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" }, "dtrace-provider": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.2.8.tgz", - "integrity": "sha1-4kPxkhmqlfvw2PL/sH9b1k6U/iA=", + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", + "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", "dev": true, - "optional": true + "optional": true, + "requires": { + "nan": "^2.14.0" + }, + "dependencies": { + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "dev": true, + "optional": true + } + } }, "duplexify": { "version": "3.7.1", @@ -2348,9 +2379,9 @@ "dev": true }, "eslint-plugin-react": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.18.3.tgz", - "integrity": "sha512-Bt56LNHAQCoou88s8ViKRjMB2+36XRejCQ1VoLj716KI1MoE99HpTVvIThJ0rvFmG4E4Gsq+UgToEjn+j044Bg==", + "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==", "dev": true, "requires": { "array-includes": "^3.1.1", @@ -2361,8 +2392,10 @@ "object.fromentries": "^2.0.2", "object.values": "^1.1.1", "prop-types": "^15.7.2", - "resolve": "^1.14.2", - "string.prototype.matchall": "^4.0.2" + "resolve": "^1.15.1", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.2", + "xregexp": "^4.3.0" }, "dependencies": { "doctrine": { @@ -2382,6 +2415,12 @@ "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 } } }, @@ -2497,46 +2536,46 @@ } }, "express": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", - "integrity": "sha1-/d72GSYQniTFFeqX/S8b2/Yt8S4=", + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", "requires": { - "accepts": "~1.3.5", + "accepts": "~1.3.7", "array-flatten": "1.1.1", - "body-parser": "1.18.3", - "content-disposition": "0.5.2", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", "content-type": "~1.0.4", - "cookie": "0.3.1", + "cookie": "0.4.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "~1.1.2", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.1.1", + "finalhandler": "~1.1.2", "fresh": "0.5.2", "merge-descriptors": "1.0.1", "methods": "~1.1.2", "on-finished": "~2.3.0", - "parseurl": "~1.3.2", + "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.4", - "qs": "6.5.2", - "range-parser": "~1.2.0", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", "safe-buffer": "5.1.2", - "send": "0.16.2", - "serve-static": "1.13.2", - "setprototypeof": "1.1.0", - "statuses": "~1.4.0", - "type-is": "~1.6.16", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" }, "dependencies": { - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha1-u3PURtonlhBu/MG2AaJT1sRr0Ic=" + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" } } }, @@ -2573,9 +2612,9 @@ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" }, "fast-diff": { "version": "1.2.0", @@ -2623,24 +2662,17 @@ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" }, "finalhandler": { - "version": "1.1.1", - "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", - "integrity": "sha1-7r9O2EAHnIP0JJA4ydcDAIMBsQU=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.4.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", "unpipe": "~1.0.0" - }, - "dependencies": { - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha1-u3PURtonlhBu/MG2AaJT1sRr0Ic=" - } } }, "find-up": { @@ -2711,7 +2743,7 @@ "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha1-3M5SwF9kTymManq5Nr1yTO/786Y=", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -2946,7 +2978,7 @@ "har-validator": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha1-HvievT5JllV2de7ZiTEQ3DUPoIA=", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" @@ -3013,14 +3045,15 @@ "dev": true }, "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", "requires": { "depd": "~1.1.2", "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" } }, "http-proxy-agent": { @@ -3276,9 +3309,9 @@ } }, "ipaddr.js": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", - "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, "is": { "version": "3.3.0", @@ -3680,6 +3713,38 @@ "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", "optional": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } } } }, @@ -3829,9 +3894,9 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "metrics-sharelatex": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/metrics-sharelatex/-/metrics-sharelatex-2.3.0.tgz", - "integrity": "sha512-qQv4UhI0Pn89WtIEUkysy4fgaCxmIw2S7+2pUB5b15Q4dzleHNplop5peTEOf8FIcURFshjPPJiLOGCJAYph7Q==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/metrics-sharelatex/-/metrics-sharelatex-2.5.1.tgz", + "integrity": "sha512-C2gmkl/tUnq3IlSX/x3dixGhdvfD6H9FR9mBf9lnkeyy2arafxhCU6u+1IQj6byjBM7vGpYHyjwWnmoi3Vb+ZQ==", "requires": { "@google-cloud/debug-agent": "^3.0.0", "@google-cloud/profiler": "^0.2.3", @@ -3860,21 +3925,21 @@ } }, "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha1-Eh+evEnjdm8xGnbh+hyAA8SwOqY=" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", - "integrity": "sha1-C2oM5v2+lXbiXx8tL96IMNwK0Ng=" + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" }, "mime-types": { - "version": "2.1.21", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", - "integrity": "sha1-KJlaoey3cHQv5q5+WPkYHHRLP5Y=", + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", "requires": { - "mime-db": "~1.37.0" + "mime-db": "1.43.0" } }, "mimic-fn": { @@ -4126,9 +4191,9 @@ } }, "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, "nice-try": { "version": "1.0.5", @@ -4227,7 +4292,7 @@ "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha1-R6ewFrqmi1+g7PPe4IqFxnmsZFU=" + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "object-assign": { "version": "4.1.1", @@ -4401,9 +4466,9 @@ } }, "parse-duration": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-0.1.1.tgz", - "integrity": "sha1-ExFN3JiRwezSgANiRFVN5DZHoiY=" + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-0.1.2.tgz", + "integrity": "sha512-0qfMZyjOUFBeEIvJ5EayfXJqaEXxQ+Oj2b7tWJM3hvEXvXsYCk05EDVI23oYnEw2NaFYUWdABEVPBvBMh8L/pA==" }, "parse-json": { "version": "2.2.0", @@ -4420,9 +4485,9 @@ "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==" }, "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "path-exists": { "version": "3.0.0", @@ -5183,18 +5248,18 @@ } }, "proxy-addr": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", - "integrity": "sha1-7PxzO/Iv+Mb0B/onUye5q2fki5M=", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", "requires": { "forwarded": "~0.1.2", - "ipaddr.js": "1.8.0" + "ipaddr.js": "1.9.1" } }, "psl": { - "version": "1.1.31", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", - "integrity": "sha1-6aqG0BAbWxBcvpOsa3hM1UcnYYQ=" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", + "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" }, "pump": { "version": "1.0.3", @@ -5253,9 +5318,9 @@ "integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew=" }, "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha1-yzroBuh0BERYTvFUzo7pjUA/PjY=" + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, "quick-lru": { "version": "4.0.1", @@ -5270,9 +5335,9 @@ "dev": true }, "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raven": { "version": "1.1.3", @@ -5294,14 +5359,24 @@ } }, "raw-body": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha1-GzJOzmtXBuFThVvBFIxlu39uoMM=", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.3", - "iconv-lite": "0.4.23", + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", "unpipe": "1.0.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } } }, "rc": { @@ -5323,9 +5398,9 @@ } }, "react-is": { - "version": "16.12.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", - "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==", + "version": "16.13.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz", + "integrity": "sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA==", "dev": true }, "read-pkg": { @@ -5386,9 +5461,9 @@ "dev": true }, "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha1-nC/KT301tZLv5Xx/ClXoEFIST+8=", + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -5397,7 +5472,7 @@ "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~2.3.2", - "har-validator": "~5.1.0", + "har-validator": "~5.1.3", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", @@ -5407,15 +5482,24 @@ "performance-now": "^2.1.0", "qs": "~6.5.2", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", + "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" }, "dependencies": { - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha1-G0r0lV6zB3xQHCOHL8ZROBFYcTE=" + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } } } }, @@ -5601,9 +5685,9 @@ "integrity": "sha1-fnQlb7qknHWqfHogXMInmcrIAAQ=" }, "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha1-bsyh4PjBVtFBWXVZhI32RzCmu8E=", + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", "requires": { "debug": "2.6.9", "depd": "~1.1.2", @@ -5612,18 +5696,18 @@ "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" + "range-parser": "~1.2.1", + "statuses": "~1.5.0" }, "dependencies": { - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha1-u3PURtonlhBu/MG2AaJT1sRr0Ic=" + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" } } }, @@ -5667,14 +5751,14 @@ } }, "serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha1-CV6Ecv1bRiN9tQzkhqQ/S4bGzsE=", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "parseurl": "~1.3.2", - "send": "0.16.2" + "parseurl": "~1.3.3", + "send": "0.17.1" } }, "set-blocking": { @@ -5683,9 +5767,9 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, "settings-sharelatex": { "version": "git+https://github.com/sharelatex/settings-sharelatex.git#93f63d029b52fef8825c3a401b2b6a7ba29b4750", @@ -5902,9 +5986,9 @@ "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" }, "sshpk": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", - "integrity": "sha1-yUbWvZsaOdDoY1dj9SQtbtbctik=", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -6222,9 +6306,9 @@ } }, "timekeeper": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/timekeeper/-/timekeeper-0.0.4.tgz", - "integrity": "sha1-kNt58X2Ni1NiFUOJSSuXJ2LP0nY=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/timekeeper/-/timekeeper-2.2.0.tgz", + "integrity": "sha512-W3AmPTJWZkRwu+iSNxPIsLZ2ByADsOLbbLxe46UJyWj3mlYLlwucKiq+/dPm0l9wTzqoF3/2PH0AGFCebjq23A==", "dev": true }, "tmp": { @@ -6268,6 +6352,11 @@ "to-no-case": "^1.0.0" } }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, "toposort-class": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", @@ -6276,7 +6365,7 @@ "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha1-U/Nto/R3g7CSWvoG/587FlKA94E=", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "requires": { "psl": "^1.1.24", "punycode": "^1.4.1" @@ -6329,12 +6418,12 @@ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" }, "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha1-+JzjQVQcZysl7nrjxz3uOyvlAZQ=", + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "requires": { "media-typer": "0.3.0", - "mime-types": "~2.1.18" + "mime-types": "~2.1.24" } }, "typedarray": { @@ -6349,9 +6438,9 @@ "dev": true }, "underscore": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", - "integrity": "sha1-BtzjSg5op7q8KbNluOdLiSUgOWE=" + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.2.tgz", + "integrity": "sha512-D39qtimx0c1fI3ya1Lnhk3E9nONswSKhnffBI0gME9C99fYOkNi04xs8K6pePLhvl1frbDemkaBQ5ikWllR2HQ==" }, "unpipe": { "version": "1.0.0", @@ -6388,12 +6477,12 @@ "dev": true }, "v8-profiler-node8": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/v8-profiler-node8/-/v8-profiler-node8-6.0.1.tgz", - "integrity": "sha512-DD7L0c/2KeFFQxs20VaFPHq/CSintttW4YB+QJdJ5ZohxOegbsMCvnZKHRHVA9UZfVYqq6ZsLaywe74+3JpZjw==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/v8-profiler-node8/-/v8-profiler-node8-6.1.1.tgz", + "integrity": "sha512-mKS7TXRRYi70hvbv5c1tk9AbuqNrtbLc+jFLlsZ2TpaC1l5lWryBlDLZKJ1JP6hjSbMEjW1ucjWLSaKsaPnGXg==", "requires": { - "nan": "^2.5.1", - "node-pre-gyp": "^0.11.0" + "nan": "^2.14.0", + "node-pre-gyp": "^0.13.0" }, "dependencies": { "mkdirp": { @@ -6404,10 +6493,15 @@ "minimist": "0.0.8" } }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + }, "node-pre-gyp": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", - "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz", + "integrity": "sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ==", "requires": { "detect-libc": "^1.0.2", "mkdirp": "^0.5.1", @@ -6650,6 +6744,15 @@ } } }, + "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, + "requires": { + "@babel/runtime-corejs3": "^7.8.3" + } + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/services/clsi/package.json b/services/clsi/package.json index 350cefa59d..5408410927 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -19,30 +19,30 @@ }, "author": "James Allen <james@sharelatex.com>", "dependencies": { - "async": "0.2.9", - "body-parser": "^1.2.0", + "async": "3.2.0", + "body-parser": "^1.19.0", "dockerode": "^2.5.3", - "express": "^4.2.0", + "express": "^4.17.1", "fs-extra": "^0.16.3", "heapdump": "^0.3.5", - "lockfile": "^1.0.3", + "lockfile": "^1.0.4", "logger-sharelatex": "^1.9.0", "lynx": "0.0.11", - "metrics-sharelatex": "^2.3.0", + "metrics-sharelatex": "^2.5.1", "mkdirp": "0.3.5", "mysql": "^2.18.1", - "request": "^2.21.0", + "request": "^2.88.2", "sequelize": "^4.44.4", "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.1.0", "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", "sqlite3": "^4.1.1", - "underscore": "^1.8.2", - "v8-profiler-node8": "^6.0.1", - "wrench": "~1.5.4" + "underscore": "^1.9.2", + "v8-profiler-node8": "^6.1.1", + "wrench": "~1.5.9" }, "devDependencies": { - "babel-eslint": "^10.0.3", - "bunyan": "^0.22.1", + "babel-eslint": "^10.1.0", + "bunyan": "^1.8.12", "chai": "~1.8.1", "eslint": "^6.8.0", "eslint-config-prettier": "^6.10.0", @@ -57,13 +57,13 @@ "eslint-plugin-node": "^11.0.0", "eslint-plugin-prettier": "^3.1.2", "eslint-plugin-promise": "^4.2.1", - "eslint-plugin-react": "^7.18.3", + "eslint-plugin-react": "^7.19.0", "eslint-plugin-standard": "^4.0.1", "mocha": "^4.0.1", "prettier": "^1.19.1", "prettier-eslint-cli": "^5.0.0", "sandboxed-module": "~0.3.0", "sinon": "~1.7.3", - "timekeeper": "0.0.4" + "timekeeper": "2.2.0" } } From a0b9d74a2516276af445b28c93b820df9b62fbdd Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Wed, 11 Mar 2020 19:25:50 +0100 Subject: [PATCH 550/754] updated dockerode, heapdump, lyns and fs-extra --- services/clsi/package-lock.json | 284 ++++++++++++++++---------------- services/clsi/package.json | 8 +- 2 files changed, 145 insertions(+), 147 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index ac92dddb0f..ae748df8bb 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -981,15 +981,6 @@ } } }, - "JSONStream": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz", - "integrity": "sha1-wQI3G27Dp887hHygDCC7D85Mbeo=", - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -1271,12 +1262,23 @@ "integrity": "sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ=" }, "bl": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha1-oWCRFxcQPAdBDO9j71Gzl8Alr5w=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.1.tgz", + "integrity": "sha512-FL/TdvchukRCuWVxT0YMO/7+L5TNeNrVFvRU2IY63aUyv9mpt8splf2NEr6qXtPo5fya5a66YohQKvGNmLrWNA==", "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" + "readable-stream": "^3.4.0" + }, + "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" + } + } } }, "bluebird": { @@ -1332,34 +1334,15 @@ "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", "dev": true }, - "buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha1-iQ3ZDZI6hz4I4Q5f1RpX5bfM4Ow=", - "requires": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha1-vX3CauKXLQ7aJTvgYdupkjScGfA=" - }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" }, - "buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" - }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8=" + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, "builtin-modules": { "version": "3.1.0", @@ -1600,14 +1583,26 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha1-kEvfGUzTEi/Gdcd/xKw9T/D9GjQ=", + "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": "^2.2.2", + "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" + } + } } }, "console-control-strings": { @@ -1792,60 +1787,49 @@ "dev": true }, "docker-modem": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-1.0.7.tgz", - "integrity": "sha1-aXAqlcBg7rZ3X3nM3Mc05ZRpcqQ=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-2.1.1.tgz", + "integrity": "sha512-zSFwYN4AP38LJhTIOpZMjiDbAqSJbv8+u9i/Xq5XABIeTzgp83VF63epu6sVHWxe+6tfhMXqgV+sYjZWh/UzSQ==", "requires": { - "JSONStream": "1.3.2", - "debug": "^3.2.5", - "readable-stream": "~1.0.26-4", - "split-ca": "^1.0.0" + "debug": "^4.1.1", + "readable-stream": "^3.5.0", + "split-ca": "^1.0.1", + "ssh2": "^0.8.7" }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha1-6D0X3hbYp++3cX7b5fsQE17uYps=", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "requires": { "ms": "^2.1.1" } }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" } } }, "dockerode": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-2.5.7.tgz", - "integrity": "sha1-E9yewPfzU6wOUSJJ538y0aqhGZ4=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-3.1.0.tgz", + "integrity": "sha512-E0KknBBTlIVEvtt2XJRZ3he59u2UN8Yr1A08Sey/BKIox+WlwnJp5fL5SKyhPgNmSXgamPEuKYCJxMi31uj0Nw==", "requires": { - "concat-stream": "~1.6.2", - "docker-modem": "1.0.x", - "tar-fs": "~1.16.3" + "concat-stream": "~2.0.0", + "docker-modem": "^2.1.0", + "tar-fs": "~2.0.0" } }, "doctrine": { @@ -2763,16 +2747,16 @@ "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=" + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "fs-extra": { - "version": "0.16.5", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.16.5.tgz", - "integrity": "sha1-GtZh+myGyWCM0bSe/G/Og0k5p1A=", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "requires": { - "graceful-fs": "^3.0.5", - "jsonfile": "^2.0.0", - "rimraf": "^2.2.8" + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, "fs-minipass": { @@ -2940,12 +2924,9 @@ } }, "graceful-fs": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", - "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", - "requires": { - "natives": "^1.1.0" - } + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" }, "growl": { "version": "1.7.0", @@ -3026,11 +3007,18 @@ "dev": true }, "heapdump": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/heapdump/-/heapdump-0.3.12.tgz", - "integrity": "sha1-ViO+eBaoqSqy1CsbQi+egppY2ok=", + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/heapdump/-/heapdump-0.3.15.tgz", + "integrity": "sha512-n8aSFscI9r3gfhOcAECAtXFaQ1uy4QSke6bnaL+iymYZ/dWs9cqDqHM+rALfsHUwukUbxsdlECZ0pKmJdQ/4OA==", "requires": { - "nan": "^2.11.1" + "nan": "^2.13.2" + }, + "dependencies": { + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + } } }, "hex2dec": { @@ -3513,26 +3501,13 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "requires": { "graceful-fs": "^4.1.6" - }, - "dependencies": { - "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha1-/7cD4QZuig7qpMi4C6klPu77+wA=", - "optional": true - } } }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -3819,9 +3794,9 @@ "integrity": "sha1-mgD3bco26yP6BTUK/htYXUKZ5ks=" }, "lynx": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.0.11.tgz", - "integrity": "sha1-LPoU5EP9LZKlm3efQVZ84cxpZaM=", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.2.0.tgz", + "integrity": "sha1-eeZnRTDaQYPoeVO9aGFx4HDaULk=", "requires": { "mersenne": "~0.0.3", "statsd-parser": "~0.0.4" @@ -4163,11 +4138,6 @@ "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.0.tgz", "integrity": "sha1-nUQ/214TogdwzF5gLu5ZdgpoWIU=" }, - "natives": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz", - "integrity": "sha1-pgO0pJirdxc2ErnqGs3sTZgPALs=" - }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -5262,9 +5232,9 @@ "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" }, "pump": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", - "integrity": "sha1-Xf6DEcM7v2/BgmH580cCxHwIqVQ=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -5985,6 +5955,24 @@ "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" }, + "ssh2": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-0.8.8.tgz", + "integrity": "sha512-egJVQkf3sbjECTY6rCeg8rgV/fab6S/7E5kpYqHT3Fe/YpfJbLYeA1qTcB2d+LRUUAjqKi7rlbfWkaP66YdpAQ==", + "requires": { + "ssh2-streams": "~0.4.9" + } + }, + "ssh2-streams": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/ssh2-streams/-/ssh2-streams-0.4.9.tgz", + "integrity": "sha512-glMQKeYKuA+rLaH16fJC3nZMV1BWklbxuYCR4C5/LlBSf2yaoNRpPU7Ul702xsi5nvYjIx9XDkKBJwrBjkDynw==", + "requires": { + "asn1": "~0.2.0", + "bcrypt-pbkdf": "^1.0.2", + "streamsearch": "~0.1.2" + } + }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", @@ -6029,6 +6017,11 @@ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -6215,14 +6208,14 @@ } }, "tar-fs": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", - "integrity": "sha1-lmpiiEHaLEAQQGqCFny9Xgxy1Qk=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.0.tgz", + "integrity": "sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==", "requires": { - "chownr": "^1.0.1", + "chownr": "^1.1.1", "mkdirp": "^0.5.1", - "pump": "^1.0.0", - "tar-stream": "^1.1.2" + "pump": "^3.0.0", + "tar-stream": "^2.0.0" }, "dependencies": { "mkdirp": { @@ -6236,17 +6229,27 @@ } }, "tar-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha1-jqVdqzeXIlPZqa+Q/c1VmuQ1xVU=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz", + "integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==", "requires": { - "bl": "^1.0.0", - "buffer-alloc": "^1.2.0", - "end-of-stream": "^1.0.0", + "bl": "^4.0.1", + "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", - "readable-stream": "^2.3.0", - "to-buffer": "^1.1.1", - "xtend": "^4.0.0" + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "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" + } + } } }, "tdigest": { @@ -6320,11 +6323,6 @@ "os-tmpdir": "~1.0.2" } }, - "to-buffer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=" - }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -6442,6 +6440,11 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.2.tgz", "integrity": "sha512-D39qtimx0c1fI3ya1Lnhk3E9nONswSKhnffBI0gME9C99fYOkNi04xs8K6pePLhvl1frbDemkaBQ5ikWllR2HQ==" }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -6753,11 +6756,6 @@ "@babel/runtime-corejs3": "^7.8.3" } }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" - }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", diff --git a/services/clsi/package.json b/services/clsi/package.json index 5408410927..bf4a532b6a 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -21,13 +21,13 @@ "dependencies": { "async": "3.2.0", "body-parser": "^1.19.0", - "dockerode": "^2.5.3", + "dockerode": "^3.1.0", "express": "^4.17.1", - "fs-extra": "^0.16.3", - "heapdump": "^0.3.5", + "fs-extra": "^8.1.0", + "heapdump": "^0.3.15", "lockfile": "^1.0.4", "logger-sharelatex": "^1.9.0", - "lynx": "0.0.11", + "lynx": "0.2.0", "metrics-sharelatex": "^2.5.1", "mkdirp": "0.3.5", "mysql": "^2.18.1", From 35f69e4f8bd441415afc187064f43bbbe2054e94 Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Wed, 11 Mar 2020 19:39:08 +0100 Subject: [PATCH 551/754] updated mkdirp --- services/clsi/package-lock.json | 13 ++++++++++--- services/clsi/package.json | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index ae748df8bb..42d16ee8fe 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -3954,9 +3954,9 @@ } }, "mkdirp": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", - "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz", + "integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==" }, "mocha": { "version": "4.1.0", @@ -5864,6 +5864,13 @@ "growl": "1.7.x", "jade": "0.26.3", "mkdirp": "0.3.5" + }, + "dependencies": { + "mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=" + } } } } diff --git a/services/clsi/package.json b/services/clsi/package.json index bf4a532b6a..659726b7ac 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -29,7 +29,7 @@ "logger-sharelatex": "^1.9.0", "lynx": "0.2.0", "metrics-sharelatex": "^2.5.1", - "mkdirp": "0.3.5", + "mkdirp": "1.0.3", "mysql": "^2.18.1", "request": "^2.88.2", "sequelize": "^4.44.4", From cf6c8ab49649e79750c676f004d012cda0491da6 Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Thu, 12 Mar 2020 10:22:08 +0100 Subject: [PATCH 552/754] removed mkdirp dependency and replaced with fs.mkdir --- services/clsi/app/js/ResourceWriter.js | 3 +-- services/clsi/package-lock.json | 5 ----- services/clsi/package.json | 1 - services/clsi/test/unit/js/ResourceWriterTests.js | 7 ++++--- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/services/clsi/app/js/ResourceWriter.js b/services/clsi/app/js/ResourceWriter.js index ba9706bce2..5a234e0d49 100644 --- a/services/clsi/app/js/ResourceWriter.js +++ b/services/clsi/app/js/ResourceWriter.js @@ -19,7 +19,6 @@ const UrlCache = require('./UrlCache') const Path = require('path') const fs = require('fs') const async = require('async') -const mkdirp = require('mkdirp') const OutputFileFinder = require('./OutputFileFinder') const ResourceStateManager = require('./ResourceStateManager') const Metrics = require('./Metrics') @@ -302,7 +301,7 @@ module.exports = ResourceWriter = { if (error != null) { return callback(error) } - return mkdirp(Path.dirname(path), function(error) { + return fs.mkdir(Path.dirname(path), { recursive: true }, function(error) { if (error != null) { return callback(error) } diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index 42d16ee8fe..c7b9e4d4f6 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -3953,11 +3953,6 @@ "minipass": "^2.2.1" } }, - "mkdirp": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz", - "integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==" - }, "mocha": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz", diff --git a/services/clsi/package.json b/services/clsi/package.json index 659726b7ac..7317cae7bf 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -29,7 +29,6 @@ "logger-sharelatex": "^1.9.0", "lynx": "0.2.0", "metrics-sharelatex": "^2.5.1", - "mkdirp": "1.0.3", "mysql": "^2.18.1", "request": "^2.88.2", "sequelize": "^4.44.4", diff --git a/services/clsi/test/unit/js/ResourceWriterTests.js b/services/clsi/test/unit/js/ResourceWriterTests.js index 189908dc6b..b542f251e2 100644 --- a/services/clsi/test/unit/js/ResourceWriterTests.js +++ b/services/clsi/test/unit/js/ResourceWriterTests.js @@ -31,7 +31,6 @@ describe('ResourceWriter', function() { './ResourceStateManager': (this.ResourceStateManager = {}), wrench: (this.wrench = {}), './UrlCache': (this.UrlCache = {}), - mkdirp: (this.mkdirp = sinon.stub().callsArg(1)), './OutputFileFinder': (this.OutputFileFinder = {}), 'logger-sharelatex': { log: sinon.stub(), err: sinon.stub() }, './Metrics': (this.Metrics = { @@ -346,6 +345,7 @@ describe('ResourceWriter', function() { describe('_writeResourceToDisk', function() { describe('with a url based resource', function() { beforeEach(function() { + this.fs.mkdir = sinon.stub().callsArg(2) this.resource = { path: 'main.tex', url: 'http://www.example.com/main.tex', @@ -363,7 +363,7 @@ describe('ResourceWriter', function() { }) it('should ensure the directory exists', function() { - return this.mkdirp + this.fs.mkdir .calledWith( path.dirname(path.join(this.basePath, this.resource.path)) ) @@ -397,6 +397,7 @@ describe('ResourceWriter', function() { content: 'Hello world' } this.fs.writeFile = sinon.stub().callsArg(2) + this.fs.mkdir = sinon.stub().callsArg(2) return this.ResourceWriter._writeResourceToDisk( this.project_id, this.resource, @@ -406,7 +407,7 @@ describe('ResourceWriter', function() { }) it('should ensure the directory exists', function() { - return this.mkdirp + return this.fs.mkdir .calledWith( path.dirname(path.join(this.basePath, this.resource.path)) ) From 7e7d234350628b75cb5fe42c7d52c8c9ebda9abc Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Thu, 12 Mar 2020 10:35:11 +0100 Subject: [PATCH 553/754] updated mocha and sinon, fixed tests --- services/clsi/package-lock.json | 448 +++++++++++++++--- services/clsi/package.json | 4 +- .../clsi/test/unit/js/CompileManagerTests.js | 6 +- .../clsi/test/unit/js/DockerRunnerTests.js | 18 +- .../clsi/test/unit/js/LockManagerTests.js | 7 +- .../test/unit/js/ResourceStateManagerTests.js | 32 +- .../clsi/test/unit/js/ResourceWriterTests.js | 36 +- services/clsi/test/unit/js/UrlFetcherTests.js | 10 +- 8 files changed, 442 insertions(+), 119 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index c7b9e4d4f6..a42045edde 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -844,6 +844,67 @@ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.15.0.tgz", "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==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + }, + "dependencies": { + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + } + } + }, + "@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==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@sinonjs/formatio": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", + "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^5.0.2" + } + }, + "@sinonjs/samsam": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.0.3.tgz", + "integrity": "sha512-QucHkc2uMJ0pFGjJUDP3F9dq5dx8QIaqISl9QgwLOh6P9yv877uONPGXh/OH/0zmM3tW1JjuJltAZV2l7zU+uQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + }, + "dependencies": { + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + } + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "@types/caseless": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", @@ -1033,6 +1094,12 @@ "uri-js": "^4.2.2" } }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, "ansi-escapes": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", @@ -1064,6 +1131,16 @@ "color-convert": "^1.9.0" } }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", @@ -1248,6 +1325,12 @@ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", "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==", + "dev": true + }, "bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -1328,10 +1411,19 @@ "concat-map": "0.0.1" } }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, "browser-stdout": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, "buffer-equal-constant-time": { @@ -1361,21 +1453,6 @@ "safe-json-stringify": "~1" } }, - "buster-core": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/buster-core/-/buster-core-0.6.4.tgz", - "integrity": "sha1-J79rrWdCROpyDzEdkAoMoct4YFA=", - "dev": true - }, - "buster-format": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/buster-format/-/buster-format-0.5.6.tgz", - "integrity": "sha1-K4bDIuz14bCubm55Bev884fSq5U=", - "dev": true, - "requires": { - "buster-core": "=0.6.4" - } - }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -1453,6 +1530,22 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + } + }, "chownr": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", @@ -2645,6 +2738,15 @@ "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, "finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -2673,6 +2775,15 @@ "resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz", "integrity": "sha1-WKRmaX34piBc39vzlVNri9d3pfY=" }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, "flat-cache": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", @@ -2772,6 +2883,13 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "fsevents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "dev": true, + "optional": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -2984,9 +3102,9 @@ } }, "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "has-symbols": { @@ -3001,9 +3119,9 @@ "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, "heapdump": { @@ -3312,6 +3430,15 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, "is-bluebird": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", @@ -3357,6 +3484,12 @@ "is-extglob": "^2.1.1" } }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, "is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", @@ -3529,6 +3662,12 @@ "object.assign": "^4.1.0" } }, + "just-extend": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.0.tgz", + "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==", + "dev": true + }, "jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -3617,6 +3756,12 @@ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, "lodash.has": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", @@ -3650,6 +3795,15 @@ "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==" }, + "log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2" + } + }, "logger-sharelatex": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/logger-sharelatex/-/logger-sharelatex-1.9.0.tgz", @@ -3954,64 +4108,77 @@ } }, "mocha": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz", - "integrity": "sha1-fYbPvPNcuCnidUwy4XNV7AUzh5Q=", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.0.tgz", + "integrity": "sha512-MymHK8UkU0K15Q/zX7uflZgVoRWiTjy0fXE/QjKts6mowUvGxOdPhZ2qj3b0iZdUrNZlW9LAIMFHB4IW+2b3EQ==", "dev": true, "requires": { - "browser-stdout": "1.3.0", - "commander": "2.11.0", - "debug": "3.1.0", - "diff": "3.3.1", + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "chokidar": "3.3.0", + "debug": "3.2.6", + "diff": "3.5.0", "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.3", - "he": "1.1.1", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "3.0.0", + "minimatch": "3.0.4", "mkdirp": "0.5.1", - "supports-color": "4.4.0" + "ms": "2.1.1", + "node-environment-flags": "1.0.6", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.0", + "yargs-parser": "13.1.1", + "yargs-unparser": "1.6.0" }, "dependencies": { - "commander": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha1-FXFS/R56bI2YpbcVzzdt+SgARWM=", - "dev": true - }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "diff": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", - "integrity": "sha1-qoVnpu7QPFMfyJ0/cRzQ5SWd7HU=", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "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" + "locate-path": "^3.0.0" } }, "growl": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", - "integrity": "sha1-GSa6kM8+3+KttJJ/WIC8IsZseQ8=", + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", @@ -4020,6 +4187,21 @@ "requires": { "minimist": "0.0.8" } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } } } }, @@ -4166,6 +4348,54 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "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==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^6.0.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, + "node-environment-flags": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "node-fetch": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", @@ -4224,6 +4454,12 @@ "validate-npm-package-license": "^3.0.1" } }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, "npm-bundled": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz", @@ -4312,6 +4548,16 @@ "has": "^1.0.3" } }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, "object.values": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", @@ -4509,6 +4755,12 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, + "picomatch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz", + "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==", + "dev": true + }, "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", @@ -5403,6 +5655,15 @@ "util-deprecate": "~1.0.1" } }, + "readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "requires": { + "picomatch": "^2.0.4" + } + }, "regenerator-runtime": { "version": "0.13.3", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", @@ -5784,12 +6045,41 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "sinon": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.7.3.tgz", - "integrity": "sha1-emnWnNApRYbHQyVO7/G1g6UJl/I=", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.0.1.tgz", + "integrity": "sha512-iTTyiQo5T94jrOx7X7QLBZyucUJ2WvL9J13+96HMfm2CGoJYbIPqRfl6wgNcqmzk0DI28jeGx5bUTXizkrqBmg==", "dev": true, "requires": { - "buster-format": "~0.5" + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^6.0.0", + "@sinonjs/formatio": "^5.0.1", + "@sinonjs/samsam": "^5.0.3", + "diff": "^4.0.2", + "nise": "^4.0.1", + "supports-color": "^7.1.0" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "slice-ansi": { @@ -6101,12 +6391,12 @@ "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=" }, "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha1-iD992rwWUUKyphQn8zUt7RldGj4=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", "dev": true, "requires": { - "has-flag": "^2.0.0" + "has-flag": "^3.0.0" } }, "table": { @@ -6336,6 +6626,15 @@ "resolved": "https://registry.npmjs.org/to-no-case/-/to-no-case-1.0.2.tgz", "integrity": "sha1-xyKQcWTvaxeBMsjmmTAhLRtKoWo=" }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, "to-snake-case": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-snake-case/-/to-snake-case-1.0.0.tgz", @@ -6865,6 +7164,17 @@ "decamelize": "^1.2.0" } }, + "yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + } + }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/services/clsi/package.json b/services/clsi/package.json index 7317cae7bf..bdbbd5ffe5 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -58,11 +58,11 @@ "eslint-plugin-promise": "^4.2.1", "eslint-plugin-react": "^7.19.0", "eslint-plugin-standard": "^4.0.1", - "mocha": "^4.0.1", + "mocha": "^7.1.0", "prettier": "^1.19.1", "prettier-eslint-cli": "^5.0.0", "sandboxed-module": "~0.3.0", - "sinon": "~1.7.3", + "sinon": "~9.0.1", "timekeeper": "2.2.0" } } diff --git a/services/clsi/test/unit/js/CompileManagerTests.js b/services/clsi/test/unit/js/CompileManagerTests.js index ae50bcc618..180f6f311f 100644 --- a/services/clsi/test/unit/js/CompileManagerTests.js +++ b/services/clsi/test/unit/js/CompileManagerTests.js @@ -347,10 +347,10 @@ describe('CompileManager', function() { .should.equal(true) }) - return it('should call the callback with an error from the stderr', function() { - this.callback.calledWith(new Error()).should.equal(true) + it('should call the callback with an error from the stderr', function() { + this.callback.calledWithExactly(sinon.match(Error)).should.equal(true) - return this.callback.args[0][0].message.should.equal( + this.callback.args[0][0].message.should.equal( `rm -r ${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id} failed: ${this.error}` ) }) diff --git a/services/clsi/test/unit/js/DockerRunnerTests.js b/services/clsi/test/unit/js/DockerRunnerTests.js index d17d906dc7..eea66b12c1 100644 --- a/services/clsi/test/unit/js/DockerRunnerTests.js +++ b/services/clsi/test/unit/js/DockerRunnerTests.js @@ -464,8 +464,8 @@ describe('DockerRunner', function() { return this.createContainer.called.should.equal(false) }) - return it('should call the callback with an error', function() { - return this.callback.calledWith(new Error()).should.equal(true) + it('should call the callback with an error', function() { + this.callback.calledWith(sinon.match(Error)).should.equal(true) }) }) @@ -488,8 +488,8 @@ describe('DockerRunner', function() { return this.createContainer.called.should.equal(false) }) - return it('should call the callback with an error', function() { - return this.callback.calledWith(new Error()).should.equal(true) + it('should call the callback with an error', function() { + this.callback.calledWith(sinon.match(Error)).should.equal(true) }) }) @@ -582,10 +582,12 @@ describe('DockerRunner', function() { return this.container.kill.called.should.equal(true) }) - return it('should call the callback with an error', function() { - const error = new Error('container timed out') - error.timedout = true - return this.callback.calledWith(error).should.equal(true) + it('should call the callback with an error', function() { + this.callback.calledWith(sinon.match(Error)).should.equal(true) + + const errorObj = this.callback.args[0][0] + expect(errorObj.message).to.include('container timed out') + expect(errorObj.timedout).equal(true) }) }) }) diff --git a/services/clsi/test/unit/js/LockManagerTests.js b/services/clsi/test/unit/js/LockManagerTests.js index ea6c3419f9..cb8ab9b747 100644 --- a/services/clsi/test/unit/js/LockManagerTests.js +++ b/services/clsi/test/unit/js/LockManagerTests.js @@ -84,9 +84,10 @@ describe('DockerLockManager', function() { return this.runner.called.should.equal(false) }) - return it('should return an error', function() { - const error = new Errors.AlreadyCompilingError() - return this.callback.calledWithExactly(error).should.equal(true) + it('should return an error', function() { + this.callback + .calledWithExactly(sinon.match(Errors.AlreadyCompilingError)) + .should.equal(true) }) }) }) diff --git a/services/clsi/test/unit/js/ResourceStateManagerTests.js b/services/clsi/test/unit/js/ResourceStateManagerTests.js index c0e89ef7ad..4c16ed1f96 100644 --- a/services/clsi/test/unit/js/ResourceStateManagerTests.js +++ b/services/clsi/test/unit/js/ResourceStateManagerTests.js @@ -11,6 +11,7 @@ */ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') +const { expect } = require('chai') const should = require('chai').should() const modulePath = require('path').join( __dirname, @@ -132,11 +133,13 @@ describe('ResourceStateManager', function() { ) }) - return it('should call the callback with an error', function() { - const error = new Errors.FilesOutOfSyncError( - 'invalid state for incremental update' - ) - return this.callback.calledWith(error).should.equal(true) + it('should call the callback with an error', function() { + this.callback + .calledWith(sinon.match(Errors.FilesOutOfSyncError)) + .should.equal(true) + + const message = this.callback.args[0][0].message + expect(message).to.include('invalid state for incremental update') }) }) }) @@ -174,11 +177,15 @@ describe('ResourceStateManager', function() { ) }) - return it('should call the callback with an error', function() { - const error = new Errors.FilesOutOfSyncError( + it('should call the callback with an error', function() { + this.callback + .calledWith(sinon.match(Errors.FilesOutOfSyncError)) + .should.equal(true) + + const message = this.callback.args[0][0].message + expect(message).to.include( 'resource files missing in incremental update' ) - return this.callback.calledWith(error).should.equal(true) }) }) @@ -198,10 +205,11 @@ describe('ResourceStateManager', function() { ) }) - return it('should call the callback with an error', function() { - return this.callback - .calledWith(new Error('relative path in resource file list')) - .should.equal(true) + it('should call the callback with an error', function() { + this.callback.calledWith(sinon.match(Error)).should.equal(true) + + const message = this.callback.args[0][0].message + expect(message).to.include('relative path in resource file list') }) }) }) diff --git a/services/clsi/test/unit/js/ResourceWriterTests.js b/services/clsi/test/unit/js/ResourceWriterTests.js index b542f251e2..2844ce7187 100644 --- a/services/clsi/test/unit/js/ResourceWriterTests.js +++ b/services/clsi/test/unit/js/ResourceWriterTests.js @@ -12,6 +12,7 @@ */ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') +const { expect } = require('chai') const should = require('chai').should() const modulePath = require('path').join( __dirname, @@ -447,10 +448,11 @@ describe('ResourceWriter', function() { return this.fs.writeFile.called.should.equal(false) }) - return it('should return an error', function() { - return this.callback - .calledWith(new Error('resource path is outside root directory')) - .should.equal(true) + it('should return an error', function() { + this.callback.calledWith(sinon.match(Error)).should.equal(true) + + const message = this.callback.args[0][0].message + expect(message).to.include('resource path is outside root directory') }) }) }) @@ -468,21 +470,18 @@ describe('ResourceWriter', function() { describe('with an invalid path', function() { beforeEach(function() { - return this.ResourceWriter.checkPath( - 'foo', - 'baz/../../bar', - this.callback - ) + this.ResourceWriter.checkPath('foo', 'baz/../../bar', this.callback) }) - return it('should return an error', function() { - return this.callback - .calledWith(new Error('resource path is outside root directory')) - .should.equal(true) + it('should return an error', function() { + this.callback.calledWith(sinon.match(Error)).should.equal(true) + + const message = this.callback.args[0][0].message + expect(message).to.include('resource path is outside root directory') }) }) - return describe('with another invalid path matching on a prefix', function() { + describe('with another invalid path matching on a prefix', function() { beforeEach(function() { return this.ResourceWriter.checkPath( 'foo', @@ -491,10 +490,11 @@ describe('ResourceWriter', function() { ) }) - return it('should return an error', function() { - return this.callback - .calledWith(new Error('resource path is outside root directory')) - .should.equal(true) + it('should return an error', function() { + this.callback.calledWith(sinon.match(Error)).should.equal(true) + + const message = this.callback.args[0][0].message + expect(message).to.include('resource path is outside root directory') }) }) }) diff --git a/services/clsi/test/unit/js/UrlFetcherTests.js b/services/clsi/test/unit/js/UrlFetcherTests.js index e5ce52b95a..c435f450dd 100644 --- a/services/clsi/test/unit/js/UrlFetcherTests.js +++ b/services/clsi/test/unit/js/UrlFetcherTests.js @@ -10,6 +10,7 @@ */ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') +const { expect } = require('chai') require('chai').should() const modulePath = require('path').join(__dirname, '../../../app/js/UrlFetcher') const { EventEmitter } = require('events') @@ -140,10 +141,11 @@ describe('UrlFetcher', function() { return this.urlStream.emit('end') }) - return it('should call the callback with an error', function() { - return this.callback - .calledWith(new Error('URL returned non-success status code: 404')) - .should.equal(true) + it('should call the callback with an error', function() { + this.callback.calledWith(sinon.match(Error)).should.equal(true) + + const message = this.callback.args[0][0].message + expect(message).to.include('URL returned non-success status code: 404') }) }) From d5bd1790a8f14f51235cc56cbaeb7d6b41abc399 Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Mon, 16 Mar 2020 16:31:02 +0100 Subject: [PATCH 554/754] updated sequelize --- .../clsi/app/js/ProjectPersistenceManager.js | 2 +- services/clsi/app/js/UrlCache.js | 4 +- services/clsi/package-lock.json | 108 ++++++++---------- services/clsi/package.json | 2 +- 4 files changed, 52 insertions(+), 64 deletions(-) diff --git a/services/clsi/app/js/ProjectPersistenceManager.js b/services/clsi/app/js/ProjectPersistenceManager.js index 8015baa907..46eee74771 100644 --- a/services/clsi/app/js/ProjectPersistenceManager.js +++ b/services/clsi/app/js/ProjectPersistenceManager.js @@ -32,7 +32,7 @@ module.exports = ProjectPersistenceManager = { db.Project.findOrCreate({ where: { project_id } }) .spread((project, created) => project - .updateAttributes({ lastAccessed: new Date() }) + .update({ lastAccessed: new Date() }) .then(() => cb()) .error(cb) ) diff --git a/services/clsi/app/js/UrlCache.js b/services/clsi/app/js/UrlCache.js index babdf9cf4c..9b982e5e63 100644 --- a/services/clsi/app/js/UrlCache.js +++ b/services/clsi/app/js/UrlCache.js @@ -227,7 +227,7 @@ module.exports = UrlCache = { callback = function(error, urlDetails) {} } const job = cb => - db.UrlCache.find({ where: { url, project_id } }) + db.UrlCache.findOne({ where: { url, project_id } }) .then(urlDetails => cb(null, urlDetails)) .error(cb) return dbQueue.queue.push(job, callback) @@ -241,7 +241,7 @@ module.exports = UrlCache = { db.UrlCache.findOrCreate({ where: { url, project_id } }) .spread((urlDetails, created) => urlDetails - .updateAttributes({ lastModified }) + .update({ lastModified }) .then(() => cb()) .error(cb) ) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index a42045edde..dcb2e94669 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -935,11 +935,6 @@ "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", "dev": true }, - "@types/geojson": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.6.tgz", - "integrity": "sha512-Xqg/lIZMrUd0VRmSRbCAewtwGZiAk3mEUDvV4op1tGl+LvyPcb/MIOSxTl9z+9+J+R4/vpjiCAT4xeKzH9ji1w==" - }, "@types/json-schema": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", @@ -1131,6 +1126,11 @@ "color-convert": "^1.9.0" } }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, "anymatch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", @@ -2938,11 +2938,6 @@ "json-bigint": "^0.3.0" } }, - "generic-pool": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.5.0.tgz", - "integrity": "sha512-dEkxmX+egB2o4NR80c/q+xzLLzLX+k68/K8xv81XprD+Sk7ZtP14VugeCz+fUwv5FzpWq40pPtAkzPRqT8ka9w==" - }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -4107,6 +4102,14 @@ "minipass": "^2.2.1" } }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, "mocha": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.0.tgz", @@ -4421,16 +4424,6 @@ "rimraf": "^2.6.1", "semver": "^5.3.0", "tar": "^4" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - } } }, "nopt": { @@ -5803,12 +5796,11 @@ } }, "retry-as-promised": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-2.3.2.tgz", - "integrity": "sha1-zZdO5P2bX+A8vzGHHuSCIcB3N7c=", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", + "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", "requires": { - "bluebird": "^3.4.6", - "debug": "^2.6.9" + "any-promise": "^1.3.0" } }, "retry-axios": { @@ -5938,44 +5930,57 @@ } }, "sequelize": { - "version": "4.44.4", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-4.44.4.tgz", - "integrity": "sha512-nkHmYkbwQK7uwpgW9VBalCBnQqQ8mslTdgcBthtJLORuPvAYRPlfkXZMVUU9TLLJt9CX+/y0MYg0DpcP6ywsEQ==", + "version": "5.21.5", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.21.5.tgz", + "integrity": "sha512-n9hR5K4uQGmBGK/Y/iqewCeSFmKVsd0TRnh0tfoLoAkmXbKC4tpeK96RhKs7d+TTMtrJlgt2TNLVBaAxEwC4iw==", "requires": { "bluebird": "^3.5.0", "cls-bluebird": "^2.1.0", - "debug": "^3.1.0", - "depd": "^1.1.0", + "debug": "^4.1.1", "dottie": "^2.0.0", - "generic-pool": "3.5.0", "inflection": "1.12.0", - "lodash": "^4.17.1", - "moment": "^2.20.0", - "moment-timezone": "^0.5.14", - "retry-as-promised": "^2.3.2", - "semver": "^5.5.0", - "terraformer-wkt-parser": "^1.1.2", + "lodash": "^4.17.15", + "moment": "^2.24.0", + "moment-timezone": "^0.5.21", + "retry-as-promised": "^3.2.0", + "semver": "^6.3.0", + "sequelize-pool": "^2.3.0", "toposort-class": "^1.0.1", - "uuid": "^3.2.1", - "validator": "^10.4.0", - "wkx": "^0.4.1" + "uuid": "^3.3.3", + "validator": "^10.11.0", + "wkx": "^0.4.8" }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "requires": { "ms": "^2.1.1" } }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, + "sequelize-pool": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-2.3.0.tgz", + "integrity": "sha512-Ibz08vnXvkZ8LJTiUOxRcj1Ckdn7qafNZ2t59jYHMX1VIebTAOYefWdRYFt6z6+hy52WGthAHAoLc9hvk3onqA==" + }, "serve-static": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", @@ -6564,23 +6569,6 @@ "uuid": "^3.3.2" } }, - "terraformer": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/terraformer/-/terraformer-1.0.10.tgz", - "integrity": "sha512-5c6cAfKTZHAeRdT8sIRRidhN1w+vsmf3RmQn+PKksFhTUnsBtjQdbJG2vaxM6T47IU2EeR1S8t8UjTYY9Q1yJA==", - "requires": { - "@types/geojson": "^7946.0.0 || ^1.0.0" - } - }, - "terraformer-wkt-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/terraformer-wkt-parser/-/terraformer-wkt-parser-1.2.1.tgz", - "integrity": "sha512-+CJyNLWb3lJ9RsZMTM66BY0MT3yIo4l4l22Jd9CrZuwzk54fsu4Sc7zejuS9fCITTuTQy3p06d4MZMVI7v5wSg==", - "requires": { - "@types/geojson": "^1.0.0", - "terraformer": "~1.0.5" - } - }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", diff --git a/services/clsi/package.json b/services/clsi/package.json index bdbbd5ffe5..6760a47da9 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -31,7 +31,7 @@ "metrics-sharelatex": "^2.5.1", "mysql": "^2.18.1", "request": "^2.88.2", - "sequelize": "^4.44.4", + "sequelize": "^5.21.5", "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.1.0", "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", "sqlite3": "^4.1.1", From 8641d0ef0fddb5d332214c2b094fe02355ec575d Mon Sep 17 00:00:00 2001 From: mserranom <mserranom@gmail.com> Date: Mon, 16 Mar 2020 17:14:04 +0100 Subject: [PATCH 555/754] updated sandboxed-module, chai and metrics-sharelatex --- services/clsi/package-lock.json | 87 ++++++++++--------- services/clsi/package.json | 6 +- .../test/unit/js/ResourceStateManagerTests.js | 1 + .../clsi/test/unit/js/ResourceWriterTests.js | 1 + 4 files changed, 52 insertions(+), 43 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index dcb2e94669..9167d627e9 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -1227,9 +1227,9 @@ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "assertion-error": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", - "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, "ast-types-flow": { @@ -1487,13 +1487,17 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "chai": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-1.8.1.tgz", - "integrity": "sha1-zHeGbV5+vKK9dRRLHtw3Coh4X3I=", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", "dev": true, "requires": { - "assertion-error": "1.0.0", - "deep-eql": "0.1.3" + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" } }, "chalk": { @@ -1530,6 +1534,12 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, "chokidar": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", @@ -1810,12 +1820,12 @@ "dev": true }, "deep-eql": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", - "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { - "type-detect": "0.1.1" + "type-detect": "^4.0.0" } }, "deep-extend": { @@ -2944,6 +2954,12 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, "get-stdin": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", @@ -4018,9 +4034,9 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "metrics-sharelatex": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/metrics-sharelatex/-/metrics-sharelatex-2.5.1.tgz", - "integrity": "sha512-C2gmkl/tUnq3IlSX/x3dixGhdvfD6H9FR9mBf9lnkeyy2arafxhCU6u+1IQj6byjBM7vGpYHyjwWnmoi3Vb+ZQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/metrics-sharelatex/-/metrics-sharelatex-2.6.0.tgz", + "integrity": "sha512-kPWCtgBrRZwLXCxqJVVn3c7g+GHQEBGYBpwCIt0Vqp0NaKvgKiPkJMkoPg9vkCsjsN2AgpGxXcOAdnHAjxfrzA==", "requires": { "@google-cloud/debug-agent": "^3.0.0", "@google-cloud/profiler": "^0.2.3", @@ -4314,9 +4330,9 @@ } }, "nan": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.0.tgz", - "integrity": "sha1-nUQ/214TogdwzF5gLu5ZdgpoWIU=" + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" }, "natural-compare": { "version": "1.4.0", @@ -4743,6 +4759,12 @@ } } }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -5875,21 +5897,13 @@ "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=" }, "sandboxed-module": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/sandboxed-module/-/sandboxed-module-0.3.0.tgz", - "integrity": "sha1-8fvvvYCaT2kHO9B8rm/H2y6vX2o=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sandboxed-module/-/sandboxed-module-2.0.3.tgz", + "integrity": "sha1-x+VFkzm7y6KMUwPusz9ug4e/upY=", "dev": true, "requires": { "require-like": "0.1.2", - "stack-trace": "0.0.6" - }, - "dependencies": { - "stack-trace": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.6.tgz", - "integrity": "sha1-HnGb1qJin/CcGJ4Xqe+QKpT8XbA=", - "dev": true - } + "stack-trace": "0.0.9" } }, "sax": { @@ -6238,13 +6252,6 @@ "nan": "^2.12.1", "node-pre-gyp": "^0.11.0", "request": "^2.87.0" - }, - "dependencies": { - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" - } } }, "sqlstring": { @@ -6694,9 +6701,9 @@ } }, "type-detect": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", - "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, "type-fest": { diff --git a/services/clsi/package.json b/services/clsi/package.json index 6760a47da9..ed0db3e309 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -28,7 +28,7 @@ "lockfile": "^1.0.4", "logger-sharelatex": "^1.9.0", "lynx": "0.2.0", - "metrics-sharelatex": "^2.5.1", + "metrics-sharelatex": "^2.6.0", "mysql": "^2.18.1", "request": "^2.88.2", "sequelize": "^5.21.5", @@ -42,7 +42,7 @@ "devDependencies": { "babel-eslint": "^10.1.0", "bunyan": "^1.8.12", - "chai": "~1.8.1", + "chai": "~4.2.0", "eslint": "^6.8.0", "eslint-config-prettier": "^6.10.0", "eslint-config-standard": "^14.1.0", @@ -61,7 +61,7 @@ "mocha": "^7.1.0", "prettier": "^1.19.1", "prettier-eslint-cli": "^5.0.0", - "sandboxed-module": "~0.3.0", + "sandboxed-module": "^2.0.3", "sinon": "~9.0.1", "timekeeper": "2.2.0" } diff --git a/services/clsi/test/unit/js/ResourceStateManagerTests.js b/services/clsi/test/unit/js/ResourceStateManagerTests.js index 4c16ed1f96..efc4065bae 100644 --- a/services/clsi/test/unit/js/ResourceStateManagerTests.js +++ b/services/clsi/test/unit/js/ResourceStateManagerTests.js @@ -23,6 +23,7 @@ const Errors = require('../../../app/js/Errors') describe('ResourceStateManager', function() { beforeEach(function() { this.ResourceStateManager = SandboxedModule.require(modulePath, { + singleOnly: true, requires: { fs: (this.fs = {}), 'logger-sharelatex': { log: sinon.stub(), err: sinon.stub() }, diff --git a/services/clsi/test/unit/js/ResourceWriterTests.js b/services/clsi/test/unit/js/ResourceWriterTests.js index 2844ce7187..68aa4560d6 100644 --- a/services/clsi/test/unit/js/ResourceWriterTests.js +++ b/services/clsi/test/unit/js/ResourceWriterTests.js @@ -24,6 +24,7 @@ describe('ResourceWriter', function() { beforeEach(function() { let Timer this.ResourceWriter = SandboxedModule.require(modulePath, { + singleOnly: true, requires: { fs: (this.fs = { mkdir: sinon.stub().callsArg(1), From 3e07af3f1be4f6a62c43388aa2dd573f920eab55 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Fri, 20 Mar 2020 13:37:58 +0000 Subject: [PATCH 556/754] limit clsi lifespan via health checks and PROCESS_LIFE_SPAN_LIMIT_MS --- services/clsi/app.js | 44 ++++++++++------------- services/clsi/config/settings.defaults.js | 9 ++--- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/services/clsi/app.js b/services/clsi/app.js index c03fcd8b29..ec67140c1c 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -22,7 +22,6 @@ const ContentTypeMapper = require('./app/js/ContentTypeMapper') const Errors = require('./app/js/Errors') const Path = require('path') -const fs = require('fs') Metrics.open_sockets.monitor(logger) Metrics.memory.monitor(logger) @@ -208,23 +207,35 @@ const resCacher = { setContentType: 'application/json' } +const startupTime = Date.now() +const checkIfProcessIsTooOld = function() { + if ( + Settings.processLifespanLimitMs && + startupTime + Settings.processLifespanLimitMs < Date.now() + ) { + logger.log('shutting down, process is too old') + resCacher.send = function() {} + resCacher.statusCode = 500 + resCacher.body = { processToOld: true } + } +} + if (Settings.smokeTest) { - let runSmokeTest - ;(runSmokeTest = function() { + const runSmokeTest = function() { + checkIfProcessIsTooOld() logger.log('running smoke tests') smokeTest.run(require.resolve(__dirname + '/test/smoke/js/SmokeTests.js'))( {}, resCacher ) return setTimeout(runSmokeTest, 30 * 1000) - })() + } + runSmokeTest() } app.get('/health_check', function(req, res) { - res.contentType(resCacher != null ? resCacher.setContentType : undefined) - return res - .status(resCacher != null ? resCacher.code : undefined) - .send(resCacher != null ? resCacher.body : undefined) + res.contentType(resCacher.setContentType) + return res.status(resCacher.code).send(resCacher.body) }) app.get('/smoke_test_force', (req, res) => @@ -234,23 +245,6 @@ app.get('/smoke_test_force', (req, res) => ) ) -const profiler = require('v8-profiler-node8') -app.get('/profile', function(req, res) { - const time = parseInt(req.query.time || '1000') - profiler.startProfiling('test') - return setTimeout(function() { - const profile = profiler.stopProfiling('test') - return res.json(profile) - }, time) -}) - -app.get('/heapdump', (req, res) => - require('heapdump').writeSnapshot( - `/tmp/${Date.now()}.clsi.heapsnapshot`, - (err, filename) => res.send(filename) - ) -) - app.use(function(error, req, res, next) { if (error instanceof Errors.NotFoundError) { logger.warn({ err: error, url: req.url }, 'not found error') diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index b0fd0cbd35..fb7384bbbd 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -22,6 +22,9 @@ module.exports = { compileSizeLimit: process.env.COMPILE_SIZE_LIMIT || '7mb', + processLifespanLimitMs: + process.env.PROCESS_LIFE_SPAN_LIMIT_MS || 60 * 60 * 24 * 1000 * 2, + path: { compilesDir: Path.resolve(__dirname + '/../compiles'), clsiCacheDir: Path.resolve(__dirname + '/../cache'), @@ -65,8 +68,7 @@ if (process.env.DOCKER_RUNNER) { dockerRunner: process.env.DOCKER_RUNNER === 'true', docker: { image: - process.env.TEXLIVE_IMAGE || - 'quay.io/sharelatex/texlive-full:2017.1', + process.env.TEXLIVE_IMAGE || 'quay.io/sharelatex/texlive-full:2017.1', env: { HOME: '/tmp' }, @@ -93,8 +95,7 @@ if (process.env.DOCKER_RUNNER) { module.exports.path.synctexBaseDir = () => '/compile' - module.exports.path.sandboxedCompilesHostDir = - process.env.COMPILES_HOST_DIR + module.exports.path.sandboxedCompilesHostDir = process.env.COMPILES_HOST_DIR module.exports.path.synctexBinHostPath = process.env.SYNCTEX_BIN_HOST_PATH } From ac5feb4a7707630a8ffb239e2c710167246d7beb Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <das7pad@outlook.com> Date: Mon, 23 Mar 2020 16:18:07 +0100 Subject: [PATCH 557/754] [misc] bump logger-sharelatex to 1.9.1 --- services/clsi/package-lock.json | 6 +++--- services/clsi/package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index 9167d627e9..c64e7d4485 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -3816,9 +3816,9 @@ } }, "logger-sharelatex": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/logger-sharelatex/-/logger-sharelatex-1.9.0.tgz", - "integrity": "sha512-yVTuha82047IiMOQLgQHCZGKkJo6I2+2KtiFKpgkIooR2yZaoTEvAeoMwBesSDSpGUpvUJ/+9UI+PmRyc+PQKQ==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/logger-sharelatex/-/logger-sharelatex-1.9.1.tgz", + "integrity": "sha512-9s6JQnH/PN+Js2CmI8+J3MQCTNlRzP2Dh4pcekXrV6Jm5J4HzyPi+6d3zfBskZ4NBmaUVw9hC4p5dmdaRmh4mQ==", "requires": { "@google-cloud/logging-bunyan": "^2.0.0", "@overleaf/o-error": "^2.0.0", diff --git a/services/clsi/package.json b/services/clsi/package.json index ed0db3e309..00440a8b1f 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -26,7 +26,7 @@ "fs-extra": "^8.1.0", "heapdump": "^0.3.15", "lockfile": "^1.0.4", - "logger-sharelatex": "^1.9.0", + "logger-sharelatex": "^1.9.1", "lynx": "0.2.0", "metrics-sharelatex": "^2.6.0", "mysql": "^2.18.1", From 4a19f022fc3d90201868d318b1b95b73c4b3bbcd Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <das7pad@outlook.com> Date: Fri, 27 Mar 2020 10:39:45 +0100 Subject: [PATCH 558/754] [misc] keep up with the error signature of dockerode/docker-modem https://github.com/apocas/docker-modem/blob/v2.1.1/lib/modem.js#L296 --- services/clsi/app/js/DockerRunner.js | 6 +----- services/clsi/test/unit/js/DockerRunnerTests.js | 6 +++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index 393ce3df37..3594b3a6d4 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -99,11 +99,7 @@ module.exports = DockerRunner = { error, output ) { - if ( - __guard__(error != null ? error.message : undefined, x => - x.match('HTTP code is 500') - ) - ) { + if (error && error.statusCode === 500) { logger.log( { err: error, project_id }, 'error running container so destroying and retrying' diff --git a/services/clsi/test/unit/js/DockerRunnerTests.js b/services/clsi/test/unit/js/DockerRunnerTests.js index eea66b12c1..83992833f3 100644 --- a/services/clsi/test/unit/js/DockerRunnerTests.js +++ b/services/clsi/test/unit/js/DockerRunnerTests.js @@ -202,9 +202,9 @@ describe('DockerRunner', function() { } if (firstTime) { firstTime = false - return callback( - new Error('HTTP code is 500 which indicates error: server error') - ) + const error = new Error('(HTTP code 500) server error - ...') + error.statusCode = 500 + return callback(error) } else { return callback(null, this.output) } From 6b029214091c31cf25ac805a21b747185fc9ef3b Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Fri, 3 Apr 2020 12:18:09 +0200 Subject: [PATCH 559/754] [misc] bump the build-scripts to version 2.1.0 This will put acceptance and unit tests in own namespaces so that they can run and be teared down individually. --- services/clsi/Makefile | 42 ++++++++++++++++++++++++++++------- services/clsi/buildscript.txt | 2 +- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/services/clsi/Makefile b/services/clsi/Makefile index 0f1d274f50..c938f87add 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -5,6 +5,8 @@ BUILD_NUMBER ?= local BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) PROJECT_NAME = clsi +BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]') + DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ BRANCH_NAME=$(BRANCH_NAME) \ @@ -12,6 +14,12 @@ DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ MOCHA_GREP=${MOCHA_GREP} \ docker-compose ${DOCKER_COMPOSE_FLAGS} +DOCKER_COMPOSE_TEST_ACCEPTANCE = \ + COMPOSE_PROJECT_NAME=test_acceptance_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) + +DOCKER_COMPOSE_TEST_UNIT = \ + COMPOSE_PROJECT_NAME=test_unit_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) + clean: docker rmi ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) docker rmi gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) @@ -28,23 +36,41 @@ lint: test: format lint test_unit test_acceptance test_unit: - @[ ! -d test/unit ] && echo "clsi has no unit tests" || $(DOCKER_COMPOSE) run --rm test_unit +ifneq (,$(wildcard test/unit)) + $(DOCKER_COMPOSE_TEST_UNIT) run --rm test_unit + $(MAKE) test_unit_clean +endif -test_acceptance: test_clean test_acceptance_pre_run test_acceptance_run +test_clean: test_unit_clean +test_unit_clean: +ifneq (,$(wildcard test/unit)) + $(DOCKER_COMPOSE_TEST_UNIT) down -v -t 0 +endif -test_acceptance_debug: test_clean test_acceptance_pre_run test_acceptance_run_debug +test_acceptance: test_acceptance_clean test_acceptance_pre_run test_acceptance_run + $(MAKE) test_acceptance_clean + +test_acceptance_debug: test_acceptance_clean test_acceptance_pre_run test_acceptance_run_debug + $(MAKE) test_acceptance_clean test_acceptance_run: - @[ ! -d test/acceptance ] && echo "clsi has no acceptance tests" || $(DOCKER_COMPOSE) run --rm test_acceptance +ifneq (,$(wildcard test/acceptance)) + $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run --rm test_acceptance +endif test_acceptance_run_debug: - @[ ! -d test/acceptance ] && echo "clsi has no acceptance tests" || $(DOCKER_COMPOSE) run -p 127.0.0.9:19999:19999 --rm test_acceptance npm run test:acceptance -- --inspect=0.0.0.0:19999 --inspect-brk +ifneq (,$(wildcard test/acceptance)) + $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run -p 127.0.0.9:19999:19999 --rm test_acceptance npm run test:acceptance -- --inspect=0.0.0.0:19999 --inspect-brk +endif -test_clean: - $(DOCKER_COMPOSE) down -v -t 0 +test_clean: test_acceptance_clean +test_acceptance_clean: + $(DOCKER_COMPOSE_TEST_ACCEPTANCE) down -v -t 0 test_acceptance_pre_run: - @[ ! -f test/acceptance/js/scripts/pre-run ] && echo "clsi has no pre acceptance tests task" || $(DOCKER_COMPOSE) run --rm test_acceptance test/acceptance/js/scripts/pre-run +ifneq (,$(wildcard test/acceptance/js/scripts/pre-run)) + $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run --rm test_acceptance test/acceptance/js/scripts/pre-run +endif build: docker build --pull --tag ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index 52686425b5..1f0893492f 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -7,4 +7,4 @@ clsi --language=es --node-version=10.19.0 --public-repo=True ---script-version=2.0.0 +--script-version=2.1.0 From 67774325692c88e6a067adb9a76a5225960816a3 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <das7pad@outlook.com> Date: Thu, 26 Mar 2020 11:18:50 +0100 Subject: [PATCH 560/754] [misc] add a metric for failing downloads --- services/clsi/app/js/ResourceWriter.js | 1 + services/clsi/test/unit/js/ResourceWriterTests.js | 1 + 2 files changed, 2 insertions(+) diff --git a/services/clsi/app/js/ResourceWriter.js b/services/clsi/app/js/ResourceWriter.js index 5a234e0d49..ed3a1e8760 100644 --- a/services/clsi/app/js/ResourceWriter.js +++ b/services/clsi/app/js/ResourceWriter.js @@ -324,6 +324,7 @@ module.exports = ResourceWriter = { }, 'error downloading file for resources' ) + Metrics.inc('download-failed') } return callback() } diff --git a/services/clsi/test/unit/js/ResourceWriterTests.js b/services/clsi/test/unit/js/ResourceWriterTests.js index 68aa4560d6..a632c1bdff 100644 --- a/services/clsi/test/unit/js/ResourceWriterTests.js +++ b/services/clsi/test/unit/js/ResourceWriterTests.js @@ -36,6 +36,7 @@ describe('ResourceWriter', function() { './OutputFileFinder': (this.OutputFileFinder = {}), 'logger-sharelatex': { log: sinon.stub(), err: sinon.stub() }, './Metrics': (this.Metrics = { + inc: sinon.stub(), Timer: (Timer = (function() { Timer = class Timer { static initClass() { From 5b1656b88482c48893a4819046d67b53b3cfee1f Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <das7pad@outlook.com> Date: Wed, 19 Feb 2020 10:32:50 +0000 Subject: [PATCH 561/754] [misc] drop debug output and log docker version on stderr --- services/clsi/entrypoint.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh index 07d902acc2..e28bbe6624 100755 --- a/services/clsi/entrypoint.sh +++ b/services/clsi/entrypoint.sh @@ -1,9 +1,6 @@ #!/bin/sh -echo "Changing permissions of /var/run/docker.sock for sibling containers" -ls -al /var/run/docker.sock -docker --version -cat /etc/passwd +docker --version >&2 DOCKER_GROUP=$(stat -c '%g' /var/run/docker.sock) groupadd --non-unique --gid ${DOCKER_GROUP} dockeronhost From 3309adf2ad1415ec4ba33381f56bf996beb9c3ff Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <das7pad@outlook.com> Date: Wed, 19 Feb 2020 12:23:20 +0100 Subject: [PATCH 562/754] [misc] move the sqlite database into a db/ directory --- services/clsi/.dockerignore | 1 + services/clsi/Dockerfile | 2 ++ services/clsi/buildscript.txt | 1 + services/clsi/config/settings.defaults.js | 8 +++----- services/clsi/db/.gitignore | 2 ++ 5 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 services/clsi/db/.gitignore diff --git a/services/clsi/.dockerignore b/services/clsi/.dockerignore index ba1c3442de..35f8905ec5 100644 --- a/services/clsi/.dockerignore +++ b/services/clsi/.dockerignore @@ -5,3 +5,4 @@ gitrev .npm .nvmrc nodemon.json +db/ diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 27158b5d6a..3fbae08b32 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -24,5 +24,7 @@ COPY . /app FROM base COPY --from=app /app /app +RUN mkdir -p db \ +&& chown node:node db CMD ["node", "--expose-gc", "app.js"] diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index 1f0893492f..72b0f6a3d6 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -1,5 +1,6 @@ clsi --acceptance-creds=None +--data-dirs=db --dependencies= --docker-repos=gcr.io/overleaf-ops --env-add= diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index b0fd0cbd35..021c9cd74f 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -9,7 +9,7 @@ module.exports = { username: 'clsi', dialect: 'sqlite', storage: - process.env.SQLITE_PATH || Path.resolve(__dirname + '/../db.sqlite'), + process.env.SQLITE_PATH || Path.resolve(__dirname + '/../db/db.sqlite'), pool: { max: 1, min: 1 @@ -65,8 +65,7 @@ if (process.env.DOCKER_RUNNER) { dockerRunner: process.env.DOCKER_RUNNER === 'true', docker: { image: - process.env.TEXLIVE_IMAGE || - 'quay.io/sharelatex/texlive-full:2017.1', + process.env.TEXLIVE_IMAGE || 'quay.io/sharelatex/texlive-full:2017.1', env: { HOME: '/tmp' }, @@ -93,8 +92,7 @@ if (process.env.DOCKER_RUNNER) { module.exports.path.synctexBaseDir = () => '/compile' - module.exports.path.sandboxedCompilesHostDir = - process.env.COMPILES_HOST_DIR + module.exports.path.sandboxedCompilesHostDir = process.env.COMPILES_HOST_DIR module.exports.path.synctexBinHostPath = process.env.SYNCTEX_BIN_HOST_PATH } diff --git a/services/clsi/db/.gitignore b/services/clsi/db/.gitignore new file mode 100644 index 0000000000..d6b7ef32c8 --- /dev/null +++ b/services/clsi/db/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore From b9deec10951aef07caca13ef4ebf1b9a90e801de Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <das7pad@outlook.com> Date: Wed, 19 Feb 2020 12:06:28 +0100 Subject: [PATCH 563/754] [misc] narrow down write access/ownership for the run-time user --- services/clsi/.dockerignore | 2 ++ services/clsi/Dockerfile | 4 ++-- services/clsi/buildscript.txt | 2 +- services/clsi/entrypoint.sh | 17 ++++++++--------- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/services/clsi/.dockerignore b/services/clsi/.dockerignore index 35f8905ec5..74fdc35e80 100644 --- a/services/clsi/.dockerignore +++ b/services/clsi/.dockerignore @@ -5,4 +5,6 @@ gitrev .npm .nvmrc nodemon.json +cache/ +compiles/ db/ diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 3fbae08b32..40615ad8c3 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -24,7 +24,7 @@ COPY . /app FROM base COPY --from=app /app /app -RUN mkdir -p db \ -&& chown node:node db +RUN mkdir -p cache compiles db \ +&& chown node:node cache compiles db CMD ["node", "--expose-gc", "app.js"] diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index 72b0f6a3d6..81d65464f9 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -1,6 +1,6 @@ clsi --acceptance-creds=None ---data-dirs=db +--data-dirs=cache,compiles,db --dependencies= --docker-repos=gcr.io/overleaf-ops --env-add= diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh index e28bbe6624..3e3f838258 100755 --- a/services/clsi/entrypoint.sh +++ b/services/clsi/entrypoint.sh @@ -2,22 +2,21 @@ docker --version >&2 +# add the node user to the docker group on the host DOCKER_GROUP=$(stat -c '%g' /var/run/docker.sock) groupadd --non-unique --gid ${DOCKER_GROUP} dockeronhost usermod -aG dockeronhost node -mkdir -p /app/cache -chown -R node:node /app/cache +# compatibility: initial volume setup +chown node:node /app/cache +chown node:node /app/compiles +chown node:node /app/db -mkdir -p /app/compiles -chown -R node:node /app/compiles - -chown -R node:node /app/bin/synctex +# acceptance tests mkdir -p /app/test/acceptance/fixtures/tmp/ -chown -R node:node /app - -chown -R node:node /app/bin +chown -R node:node /app/test/acceptance/fixtures +# make synctex available for remount in compiles cp /app/bin/synctex /app/bin/synctex-mount/synctex exec runuser -u node -- "$@" From 101c4ea4dc487637bd87c7508ffbd47ae19336a5 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <das7pad@outlook.com> Date: Fri, 27 Mar 2020 11:10:27 +0100 Subject: [PATCH 564/754] [misc] use a directory in /tmp for temporary data --- services/clsi/entrypoint.sh | 4 --- .../acceptance/js/ExampleDocumentTests.js | 29 +++++++++++-------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh index 3e3f838258..2696574873 100755 --- a/services/clsi/entrypoint.sh +++ b/services/clsi/entrypoint.sh @@ -12,10 +12,6 @@ chown node:node /app/cache chown node:node /app/compiles chown node:node /app/db -# acceptance tests -mkdir -p /app/test/acceptance/fixtures/tmp/ -chown -R node:node /app/test/acceptance/fixtures - # make synctex available for remount in compiles cp /app/bin/synctex /app/bin/synctex-mount/synctex diff --git a/services/clsi/test/acceptance/js/ExampleDocumentTests.js b/services/clsi/test/acceptance/js/ExampleDocumentTests.js index 110b5d6f9a..0134c0e106 100644 --- a/services/clsi/test/acceptance/js/ExampleDocumentTests.js +++ b/services/clsi/test/acceptance/js/ExampleDocumentTests.js @@ -19,11 +19,17 @@ const Client = require('./helpers/Client') const request = require('request') require('chai').should() const fs = require('fs') +const fsExtra = require('fs-extra') const ChildProcess = require('child_process') const ClsiApp = require('./helpers/ClsiApp') const logger = require('logger-sharelatex') const Path = require('path') -const fixturePath = path => Path.normalize(__dirname + '/../fixtures/' + path) +const fixturePath = path => { + if (path.slice(0, 3) === 'tmp') { + return '/tmp/clsi_acceptance_tests' + path.slice(3) + } + return Path.normalize(__dirname + '/../fixtures/' + path) +} const process = require('process') console.log( process.pid, @@ -32,13 +38,6 @@ console.log( process.getgroups(), 'PID' ) -try { - console.log('creating tmp directory', fixturePath('tmp')) - fs.mkdirSync(fixturePath('tmp')) -} catch (error) { - const err = error - console.log(err, fixturePath('tmp'), 'unable to create fixture tmp path') -} const MOCHA_LATEX_TIMEOUT = 60 * 1000 @@ -201,10 +200,16 @@ Client.runServer(4242, fixturePath('examples')) describe('Example Documents', function() { before(function(done) { - return ChildProcess.exec('rm test/acceptance/fixtures/tmp/*').on( - 'exit', - () => ClsiApp.ensureRunning(done) - ) + ClsiApp.ensureRunning(done) + }) + before(function(done) { + fsExtra.remove(fixturePath('tmp'), done) + }) + before(function(done) { + fs.mkdir(fixturePath('tmp'), done) + }) + after(function(done) { + fsExtra.remove(fixturePath('tmp'), done) }) return Array.from(fs.readdirSync(fixturePath('examples'))).map(example_dir => From 66447211d0fe26a0079e8e242059972fc72a43e5 Mon Sep 17 00:00:00 2001 From: Tim Alby <timothee.alby@gmail.com> Date: Fri, 10 Apr 2020 12:25:55 +0200 Subject: [PATCH 565/754] fix arguments order Co-Authored-By: Jakob Ackermann <jakob.ackermann@overleaf.com> --- services/clsi/test/unit/js/DockerRunnerTests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/test/unit/js/DockerRunnerTests.js b/services/clsi/test/unit/js/DockerRunnerTests.js index 83992833f3..3daded897b 100644 --- a/services/clsi/test/unit/js/DockerRunnerTests.js +++ b/services/clsi/test/unit/js/DockerRunnerTests.js @@ -357,8 +357,8 @@ describe('DockerRunner', function() { return this.DockerRunner.startContainer( this.options, this.volumes, - this.callback, - () => {} + () => {}, + this.callback ) }) From e3b1472c7f966088245e0e9a994b6e4cd67d7970 Mon Sep 17 00:00:00 2001 From: Tim Alby <timothee.alby@gmail.com> Date: Fri, 10 Apr 2020 12:28:11 +0200 Subject: [PATCH 566/754] retry once on EPIPE errors Co-Authored-By: Jakob Ackermann <jakob.ackermann@overleaf.com> --- services/clsi/app/js/DockerRunner.js | 34 +++++++---- .../clsi/test/unit/js/DockerRunnerTests.js | 58 +++++++++++++++++++ 2 files changed, 80 insertions(+), 12 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index 3594b3a6d4..32bcf707ce 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -407,19 +407,29 @@ module.exports = DockerRunner = { }) } ) - return container.inspect(function(error, stats) { - if ((error != null ? error.statusCode : undefined) === 404) { - return createAndStartContainer() - } else if (error != null) { - logger.err( - { container_name: name, error }, - 'unable to inspect container to start' - ) - return callback(error) - } else { - return startExistingContainer() + + const callbackWithRetry = error => { + if (error.message.match(/EPIPE/)) { + return inspectContainer(container, callback) } - }) + callback(error) + } + + var inspectContainer = (container, innerCallback) => + container.inspect(function(error, stats) { + if ((error != null ? error.statusCode : undefined) === 404) { + return createAndStartContainer() + } else if (error != null) { + logger.err( + { container_name: name, error }, + 'unable to inspect container to start' + ) + return innerCallback(error) + } else { + return startExistingContainer() + } + }) + inspectContainer(container, callbackWithRetry) }, attachToContainer(containerId, attachStreamHandler, attachStartCallback) { diff --git a/services/clsi/test/unit/js/DockerRunnerTests.js b/services/clsi/test/unit/js/DockerRunnerTests.js index 3daded897b..7284c6e53e 100644 --- a/services/clsi/test/unit/js/DockerRunnerTests.js +++ b/services/clsi/test/unit/js/DockerRunnerTests.js @@ -36,6 +36,7 @@ describe('DockerRunner', function() { 'logger-sharelatex': (this.logger = { log: sinon.stub(), error: sinon.stub(), + err: sinon.stub(), info: sinon.stub(), warn: sinon.stub() }), @@ -387,6 +388,63 @@ describe('DockerRunner', function() { }) }) + describe('when inspect always fails with EPIPE error', function() { + beforeEach(function() { + this.error = new Error('write EPIPE') + this.container.inspect = sinon.stub().yields(this.error) + this.container.start = sinon.stub().yields() + + this.DockerRunner.startContainer( + this.options, + this.volumes, + () => {}, + this.callback + ) + }) + + it('should retry once', function() { + sinon.assert.callOrder( + this.container.inspect, + this.container.inspect, + this.callback + ) + }) + + it('should call back with error', function() { + sinon.assert.calledWith(this.callback, this.error) + }) + }) + + describe('when inspect fails once with EPIPE error', function() { + beforeEach(function() { + this.container.inspect = sinon.stub() + this.container.inspect.onFirstCall().yields(new Error('write EPIPE')) + this.container.inspect.onSecondCall().yields() + this.container.start = sinon.stub().yields() + + this.DockerRunner.startContainer( + this.options, + this.volumes, + () => {}, + this.callback + ) + }) + + it('should retry once and start container', function() { + sinon.assert.callOrder( + this.container.inspect, + this.container.inspect, + this.DockerRunner.attachToContainer, + this.container.start, + this.callback + ) + }) + + it('should call back without error', function() { + sinon.assert.calledWith(this.callback, null) + }) + }) + describe('when the container does not exist', function() { beforeEach(function() { const exists = false From ecaa7035f5743ce7f9116db32c31b24fffdd9d41 Mon Sep 17 00:00:00 2001 From: Tim Alby <timothee.alby@gmail.com> Date: Fri, 10 Apr 2020 12:28:48 +0200 Subject: [PATCH 567/754] add metrics for EPIPE errors Co-Authored-By: Jakob Ackermann <jakob.ackermann@overleaf.com> --- services/clsi/app/js/DockerRunner.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index 32bcf707ce..552f65ea7b 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -26,6 +26,7 @@ const LockManager = require('./DockerLockManager') const fs = require('fs') const Path = require('path') const _ = require('underscore') +const metrics = require('metrics-sharelatex') logger.info('using docker runner') @@ -410,6 +411,7 @@ module.exports = DockerRunner = { const callbackWithRetry = error => { if (error.message.match(/EPIPE/)) { + metrics.inc('container-inspect-epipe-retry') return inspectContainer(container, callback) } callback(error) @@ -420,6 +422,7 @@ module.exports = DockerRunner = { if ((error != null ? error.statusCode : undefined) === 404) { return createAndStartContainer() } else if (error != null) { + metrics.inc('container-inspect-epipe-error') logger.err( { container_name: name, error }, 'unable to inspect container to start' From cb9e9321f02e228cdbdb5a3c306b7b8da24530a3 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Fri, 10 Apr 2020 13:25:40 +0200 Subject: [PATCH 568/754] [DockerRunner] fix metric incrementing and error logging - do not log on first EPIPE - inc 'container-inspect-epipe-error' on permanent error only Co-Authored-By: Tim Alby <timothee.alby@gmail.com> --- services/clsi/app/js/DockerRunner.js | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index 552f65ea7b..6a603d8f74 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -408,31 +408,28 @@ module.exports = DockerRunner = { }) } ) - - const callbackWithRetry = error => { - if (error.message.match(/EPIPE/)) { - metrics.inc('container-inspect-epipe-retry') - return inspectContainer(container, callback) - } - callback(error) - } - - var inspectContainer = (container, innerCallback) => + var inspectContainer = (isRetry) => container.inspect(function(error, stats) { if ((error != null ? error.statusCode : undefined) === 404) { return createAndStartContainer() } else if (error != null) { - metrics.inc('container-inspect-epipe-error') + if (error.message.match(/EPIPE/)) { + if (!isRetry) { + metrics.inc('container-inspect-epipe-retry') + return inspectContainer(true) + } + metrics.inc('container-inspect-epipe-error') + } logger.err( { container_name: name, error }, 'unable to inspect container to start' ) - return innerCallback(error) + return callback(error) } else { return startExistingContainer() } }) - inspectContainer(container, callbackWithRetry) + inspectContainer(false) }, attachToContainer(containerId, attachStreamHandler, attachStartCallback) { From acbfc465dcd3e937c20d7b8ea88e5d32a62071ff Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Thu, 9 Apr 2020 14:11:04 +0100 Subject: [PATCH 569/754] add variance into shutdown time to avoid stampeed --- services/clsi/app.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/services/clsi/app.js b/services/clsi/app.js index ec67140c1c..f96d019fdf 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -208,27 +208,34 @@ const resCacher = { } const startupTime = Date.now() -const checkIfProcessIsTooOld = function() { +const checkIfProcessIsTooOld = function(cont) { + if (typeof Settings.processLifespanLimitMs === 'string') { + Settings.processLifespanLimitMs = parseInt(Settings.processLifespanLimitMs) + Settings.processLifespanLimitMs += + Settings.processLifespanLimitMs * (Math.random() / 10) + } if ( Settings.processLifespanLimitMs && startupTime + Settings.processLifespanLimitMs < Date.now() ) { logger.log('shutting down, process is too old') resCacher.send = function() {} - resCacher.statusCode = 500 + resCacher.code = 500 resCacher.body = { processToOld: true } + } else { + cont() } } if (Settings.smokeTest) { const runSmokeTest = function() { - checkIfProcessIsTooOld() - logger.log('running smoke tests') - smokeTest.run(require.resolve(__dirname + '/test/smoke/js/SmokeTests.js'))( - {}, - resCacher - ) - return setTimeout(runSmokeTest, 30 * 1000) + checkIfProcessIsTooOld(function() { + logger.log('running smoke tests') + smokeTest.run( + require.resolve(__dirname + '/test/smoke/js/SmokeTests.js') + )({}, resCacher) + return setTimeout(runSmokeTest, 30 * 1000) + }) } runSmokeTest() } From cf8533bee1c654b5bd6170eac5be49bcbf9d1d49 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <das7pad@outlook.com> Date: Thu, 19 Sep 2019 00:44:02 +0200 Subject: [PATCH 570/754] [LocalCommandRunner] run: block a double call of the callback The subprocess event handler fires the "error" and "close" event in case of a failure. Both events would call the given callback, resulting in double processing of the subprocess result downstream. Signed-off-by: Jakob Ackermann <das7pad@outlook.com> --- services/clsi/app/js/LocalCommandRunner.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/clsi/app/js/LocalCommandRunner.js b/services/clsi/app/js/LocalCommandRunner.js index 61ecd88794..67f1a33c91 100644 --- a/services/clsi/app/js/LocalCommandRunner.js +++ b/services/clsi/app/js/LocalCommandRunner.js @@ -15,6 +15,7 @@ */ let CommandRunner const { spawn } = require('child_process') +const _ = require('underscore') const logger = require('logger-sharelatex') logger.info('using standard command runner') @@ -24,6 +25,8 @@ module.exports = CommandRunner = { let key, value if (callback == null) { callback = function(error) {} + } else { + callback = _.once(callback) } command = Array.from(command).map(arg => arg.toString().replace('$COMPILE_DIR', directory) From adc73df53d4d91a029fa74a02c61d183a8307821 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@gmail.com> Date: Thu, 23 Apr 2020 11:32:33 +0100 Subject: [PATCH 571/754] cleanup the shutdown code a bit --- services/clsi/app.js | 19 +++++++++---------- services/clsi/config/settings.defaults.js | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/services/clsi/app.js b/services/clsi/app.js index f96d019fdf..80b00ef5f6 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -207,17 +207,16 @@ const resCacher = { setContentType: 'application/json' } -const startupTime = Date.now() +let shutdownTime +if (Settings.processLifespanLimitMs) { + Settings.processLifespanLimitMs += + Settings.processLifespanLimitMs * (Math.random() / 10) + shutdownTime = Date.now() + Settings.processLifespanLimitMs + logger.info('Lifespan limited to ', shutdownTime) +} + const checkIfProcessIsTooOld = function(cont) { - if (typeof Settings.processLifespanLimitMs === 'string') { - Settings.processLifespanLimitMs = parseInt(Settings.processLifespanLimitMs) - Settings.processLifespanLimitMs += - Settings.processLifespanLimitMs * (Math.random() / 10) - } - if ( - Settings.processLifespanLimitMs && - startupTime + Settings.processLifespanLimitMs < Date.now() - ) { + if (shutdownTime && shutdownTime < Date.now()) { logger.log('shutting down, process is too old') resCacher.send = function() {} resCacher.code = 500 diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index fb7384bbbd..e5df06242c 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -23,7 +23,7 @@ module.exports = { compileSizeLimit: process.env.COMPILE_SIZE_LIMIT || '7mb', processLifespanLimitMs: - process.env.PROCESS_LIFE_SPAN_LIMIT_MS || 60 * 60 * 24 * 1000 * 2, + parseInt(process.env.PROCESS_LIFE_SPAN_LIMIT_MS) || 60 * 60 * 24 * 1000 * 2, path: { compilesDir: Path.resolve(__dirname + '/../compiles'), From 8e86f02c4316a040a102869a457366f3634a8b8a Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Thu, 7 May 2020 10:30:14 +0100 Subject: [PATCH 572/754] set encoding when reading from streams using .toString() works most of the time but can lead to utf8 characters being broken across chunk boundaries. https://nodejs.org/api/stream.html#stream_readable_setencoding_encoding --- services/clsi/app/js/CompileManager.js | 2 +- services/clsi/app/js/LocalCommandRunner.js | 2 +- services/clsi/app/js/OutputFileFinder.js | 2 +- services/clsi/app/js/OutputFileOptimiser.js | 2 +- services/clsi/test/unit/js/CompileManagerTests.js | 2 ++ services/clsi/test/unit/js/OutputFileFinderTests.js | 1 + 6 files changed, 7 insertions(+), 4 deletions(-) diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js index 3bf54bc75b..dd62f435ed 100644 --- a/services/clsi/app/js/CompileManager.js +++ b/services/clsi/app/js/CompileManager.js @@ -334,7 +334,7 @@ module.exports = CompileManager = { proc.on('error', callback) let stderr = '' - proc.stderr.on('data', chunk => (stderr += chunk.toString())) + proc.stderr.setEncoding('utf8').on('data', chunk => (stderr += chunk)) return proc.on('close', function(code) { if (code === 0) { diff --git a/services/clsi/app/js/LocalCommandRunner.js b/services/clsi/app/js/LocalCommandRunner.js index 61ecd88794..ccaf50784a 100644 --- a/services/clsi/app/js/LocalCommandRunner.js +++ b/services/clsi/app/js/LocalCommandRunner.js @@ -46,7 +46,7 @@ module.exports = CommandRunner = { const proc = spawn(command[0], command.slice(1), { cwd: directory, env }) let stdout = '' - proc.stdout.on('data', data => (stdout += data)) + proc.stdout.setEncoding('utf8').on('data', data => (stdout += data)) proc.on('error', function(err) { logger.err( diff --git a/services/clsi/app/js/OutputFileFinder.js b/services/clsi/app/js/OutputFileFinder.js index 50012b51b7..831997811b 100644 --- a/services/clsi/app/js/OutputFileFinder.js +++ b/services/clsi/app/js/OutputFileFinder.js @@ -87,7 +87,7 @@ module.exports = OutputFileFinder = { const proc = spawn('find', args) let stdout = '' - proc.stdout.on('data', chunk => (stdout += chunk.toString())) + proc.stdout.setEncoding('utf8').on('data', chunk => (stdout += chunk)) proc.on('error', callback) return proc.on('close', function(code) { if (code !== 0) { diff --git a/services/clsi/app/js/OutputFileOptimiser.js b/services/clsi/app/js/OutputFileOptimiser.js index c0b8cc141a..41ba7b40c3 100644 --- a/services/clsi/app/js/OutputFileOptimiser.js +++ b/services/clsi/app/js/OutputFileOptimiser.js @@ -78,7 +78,7 @@ module.exports = OutputFileOptimiser = { const timer = new Metrics.Timer('qpdf') const proc = spawn('qpdf', args) let stdout = '' - proc.stdout.on('data', chunk => (stdout += chunk.toString())) + proc.stdout.setEncoding('utf8').on('data', chunk => (stdout += chunk)) callback = _.once(callback) // avoid double call back for error and close event proc.on('error', function(err) { logger.warn({ err, args }, 'qpdf failed') diff --git a/services/clsi/test/unit/js/CompileManagerTests.js b/services/clsi/test/unit/js/CompileManagerTests.js index 180f6f311f..74a0a47fff 100644 --- a/services/clsi/test/unit/js/CompileManagerTests.js +++ b/services/clsi/test/unit/js/CompileManagerTests.js @@ -294,6 +294,7 @@ describe('CompileManager', function() { this.proc = new EventEmitter() this.proc.stdout = new EventEmitter() this.proc.stderr = new EventEmitter() + this.proc.stderr.setEncoding = sinon.stub().returns(this.proc.stderr) this.child_process.spawn = sinon.stub().returns(this.proc) this.CompileManager.clearProject( this.project_id, @@ -328,6 +329,7 @@ describe('CompileManager', function() { this.proc = new EventEmitter() this.proc.stdout = new EventEmitter() this.proc.stderr = new EventEmitter() + this.proc.stderr.setEncoding = sinon.stub().returns(this.proc.stderr) this.child_process.spawn = sinon.stub().returns(this.proc) this.CompileManager.clearProject( this.project_id, diff --git a/services/clsi/test/unit/js/OutputFileFinderTests.js b/services/clsi/test/unit/js/OutputFileFinderTests.js index e5f990466e..ee591b408d 100644 --- a/services/clsi/test/unit/js/OutputFileFinderTests.js +++ b/services/clsi/test/unit/js/OutputFileFinderTests.js @@ -70,6 +70,7 @@ describe('OutputFileFinder', function() { beforeEach(function() { this.proc = new EventEmitter() this.proc.stdout = new EventEmitter() + this.proc.stdout.setEncoding = sinon.stub().returns(this.proc.stdout) this.spawn.returns(this.proc) this.directory = '/base/dir' return this.OutputFileFinder._getAllFiles(this.directory, this.callback) From 4947abe88bc49eeaecb85ec12f0f3069bf2c287d Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Thu, 7 May 2020 10:42:05 +0100 Subject: [PATCH 573/754] fix deprecated usage of Buffer constructor --- services/clsi/app/js/OutputFileOptimiser.js | 3 +-- services/clsi/app/js/SafeReader.js | 2 +- services/clsi/test/unit/js/OutputFileOptimiserTests.js | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/services/clsi/app/js/OutputFileOptimiser.js b/services/clsi/app/js/OutputFileOptimiser.js index c0b8cc141a..1af2045295 100644 --- a/services/clsi/app/js/OutputFileOptimiser.js +++ b/services/clsi/app/js/OutputFileOptimiser.js @@ -45,8 +45,7 @@ module.exports = OutputFileOptimiser = { checkIfPDFIsOptimised(file, callback) { const SIZE = 16 * 1024 // check the header of the pdf - const result = new Buffer(SIZE) - result.fill(0) // prevent leakage of uninitialised buffer + const result = Buffer.alloc(SIZE) // fills with zeroes by default return fs.open(file, 'r', function(err, fd) { if (err != null) { return callback(err) diff --git a/services/clsi/app/js/SafeReader.js b/services/clsi/app/js/SafeReader.js index d909e37e73..900826756a 100644 --- a/services/clsi/app/js/SafeReader.js +++ b/services/clsi/app/js/SafeReader.js @@ -43,7 +43,7 @@ module.exports = SafeReader = { } return callback(null, ...Array.from(result)) }) - const buff = new Buffer(size, 0) // fill with zeros + const buff = Buffer.alloc(size) // fills with zeroes by default return fs.read(fd, buff, 0, buff.length, 0, function( err, bytesRead, diff --git a/services/clsi/test/unit/js/OutputFileOptimiserTests.js b/services/clsi/test/unit/js/OutputFileOptimiserTests.js index 4546f08431..b4983bf0f8 100644 --- a/services/clsi/test/unit/js/OutputFileOptimiserTests.js +++ b/services/clsi/test/unit/js/OutputFileOptimiserTests.js @@ -124,7 +124,7 @@ describe('OutputFileOptimiser', function() { this.fs.read = sinon .stub() .withArgs(this.fd) - .yields(null, 100, new Buffer('hello /Linearized 1')) + .yields(null, 100, Buffer.from('hello /Linearized 1')) this.fs.close = sinon .stub() .withArgs(this.fd) @@ -140,7 +140,7 @@ describe('OutputFileOptimiser', function() { this.fs.read = sinon .stub() .withArgs(this.fd) - .yields(null, 100, new Buffer('hello /Linearized 1')) + .yields(null, 100, Buffer.from('hello /Linearized 1')) return this.OutputFileOptimiser.checkIfPDFIsOptimised( this.src, this.callback @@ -169,7 +169,7 @@ describe('OutputFileOptimiser', function() { this.fs.read = sinon .stub() .withArgs(this.fd) - .yields(null, 100, new Buffer('hello not linearized 1')) + .yields(null, 100, Buffer.from('hello not linearized 1')) return this.OutputFileOptimiser.checkIfPDFIsOptimised( this.src, this.callback From 77e8ba74a72911da73ffa29e677697278384dc8a Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@gmail.com> Date: Thu, 14 May 2020 13:09:57 +0100 Subject: [PATCH 574/754] add pipeUrlToFileWithRetry function to retry file downloads 3 times --- services/clsi/app/js/UrlCache.js | 2 +- services/clsi/app/js/UrlFetcher.js | 9 +- .../test/acceptance/js/UrlCachingTests.js | 1 - services/clsi/test/unit/js/UrlCacheTests.js | 6 +- services/clsi/test/unit/js/UrlFetcherTests.js | 244 ++++++++++-------- 5 files changed, 153 insertions(+), 109 deletions(-) diff --git a/services/clsi/app/js/UrlCache.js b/services/clsi/app/js/UrlCache.js index 9b982e5e63..df9c175a63 100644 --- a/services/clsi/app/js/UrlCache.js +++ b/services/clsi/app/js/UrlCache.js @@ -95,7 +95,7 @@ module.exports = UrlCache = { } if (needsDownloading) { logger.log({ url, lastModified }, 'downloading URL') - return UrlFetcher.pipeUrlToFile( + return UrlFetcher.pipeUrlToFileWithRetry( url, UrlCache._cacheFilePathForUrl(project_id, url), error => { diff --git a/services/clsi/app/js/UrlFetcher.js b/services/clsi/app/js/UrlFetcher.js index 19c681ca1f..6c7d83af0e 100644 --- a/services/clsi/app/js/UrlFetcher.js +++ b/services/clsi/app/js/UrlFetcher.js @@ -12,16 +12,23 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let UrlFetcher const request = require('request').defaults({ jar: false }) const fs = require('fs') const logger = require('logger-sharelatex') const settings = require('settings-sharelatex') const URL = require('url') +const async = require('async') const oneMinute = 60 * 1000 module.exports = UrlFetcher = { + pipeUrlToFileWithRetry(url, filePath, callback) { + const doDownload = function(cb) { + UrlFetcher.pipeUrlToFile(url, filePath, cb) + } + async.retry(3, doDownload, callback) + }, + pipeUrlToFile(url, filePath, _callback) { if (_callback == null) { _callback = function(error) {} diff --git a/services/clsi/test/acceptance/js/UrlCachingTests.js b/services/clsi/test/acceptance/js/UrlCachingTests.js index 4d6249784c..b86541b681 100644 --- a/services/clsi/test/acceptance/js/UrlCachingTests.js +++ b/services/clsi/test/acceptance/js/UrlCachingTests.js @@ -11,7 +11,6 @@ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ const Client = require('./helpers/Client') -const request = require('request') require('chai').should() const sinon = require('sinon') const ClsiApp = require('./helpers/ClsiApp') diff --git a/services/clsi/test/unit/js/UrlCacheTests.js b/services/clsi/test/unit/js/UrlCacheTests.js index f056a6eb22..f5c0f3e20d 100644 --- a/services/clsi/test/unit/js/UrlCacheTests.js +++ b/services/clsi/test/unit/js/UrlCacheTests.js @@ -160,7 +160,7 @@ describe('UrlCache', function() { describe('_ensureUrlIsInCache', function() { beforeEach(function() { - this.UrlFetcher.pipeUrlToFile = sinon.stub().callsArg(2) + this.UrlFetcher.pipeUrlToFileWithRetry = sinon.stub().callsArg(2) return (this.UrlCache._updateOrCreateUrlDetails = sinon .stub() .callsArg(3)) @@ -190,7 +190,7 @@ describe('UrlCache', function() { }) it('should download the URL to the cache file', function() { - return this.UrlFetcher.pipeUrlToFile + return this.UrlFetcher.pipeUrlToFileWithRetry .calledWith( this.url, this.UrlCache._cacheFilePathForUrl(this.project_id, this.url) @@ -232,7 +232,7 @@ describe('UrlCache', function() { }) it('should not download the URL to the cache file', function() { - return this.UrlFetcher.pipeUrlToFile.called.should.equal(false) + return this.UrlFetcher.pipeUrlToFileWithRetry.called.should.equal(false) }) return it('should return the callback with the cache file path', function() { diff --git a/services/clsi/test/unit/js/UrlFetcherTests.js b/services/clsi/test/unit/js/UrlFetcherTests.js index c435f450dd..247920979f 100644 --- a/services/clsi/test/unit/js/UrlFetcherTests.js +++ b/services/clsi/test/unit/js/UrlFetcherTests.js @@ -33,72 +33,64 @@ describe('UrlFetcher', function() { } })) }) - - it('should turn off the cookie jar in request', function() { - return this.defaults.calledWith({ jar: false }).should.equal(true) - }) - - describe('rewrite url domain if filestoreDomainOveride is set', function() { - beforeEach(function() { - this.path = '/path/to/file/on/disk' - this.request.get = sinon - .stub() - .returns((this.urlStream = new EventEmitter())) - this.urlStream.pipe = sinon.stub() - this.urlStream.pause = sinon.stub() - this.urlStream.resume = sinon.stub() - this.fs.createWriteStream = sinon - .stub() - .returns((this.fileStream = new EventEmitter())) - return (this.fs.unlink = (file, callback) => callback()) + describe('pipeUrlToFileWithRetry', function() { + this.beforeEach(function() { + this.UrlFetcher.pipeUrlToFile = sinon.stub() }) - it('should use the normal domain when override not set', function(done) { - this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { - this.request.get.args[0][0].url.should.equal(this.url) - return done() + it('should call pipeUrlToFile', function(done) { + this.UrlFetcher.pipeUrlToFile.callsArgWith(2) + this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, err => { + expect(err).to.equal(undefined) + this.UrlFetcher.pipeUrlToFile.called.should.equal(true) + done() }) - this.res = { statusCode: 200 } - this.urlStream.emit('response', this.res) - this.urlStream.emit('end') - return this.fileStream.emit('finish') }) - return it('should use override domain when filestoreDomainOveride is set', function(done) { - this.settings.filestoreDomainOveride = '192.11.11.11' - this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { - this.request.get.args[0][0].url.should.equal( - '192.11.11.11/file/here?query=string' - ) - return done() + it('should call pipeUrlToFile multiple times on error', function(done) { + error = new Error("couldn't download file") + this.UrlFetcher.pipeUrlToFile.callsArgWith(2, error) + this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, err => { + expect(err).to.equal(error) + this.UrlFetcher.pipeUrlToFile.callCount.should.equal(3) + done() + }) + }) + + it('should call pipeUrlToFile twice if only 1 error', function(done) { + this.UrlFetcher.pipeUrlToFile.onCall(0).callsArgWith(2, 'error') + this.UrlFetcher.pipeUrlToFile.onCall(1).callsArgWith(2) + this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, err => { + expect(err).to.equal(undefined) + this.UrlFetcher.pipeUrlToFile.callCount.should.equal(2) + done() }) - this.res = { statusCode: 200 } - this.urlStream.emit('response', this.res) - this.urlStream.emit('end') - return this.fileStream.emit('finish') }) }) - return describe('pipeUrlToFile', function() { - beforeEach(function(done) { - this.path = '/path/to/file/on/disk' - this.request.get = sinon - .stub() - .returns((this.urlStream = new EventEmitter())) - this.urlStream.pipe = sinon.stub() - this.urlStream.pause = sinon.stub() - this.urlStream.resume = sinon.stub() - this.fs.createWriteStream = sinon - .stub() - .returns((this.fileStream = new EventEmitter())) - this.fs.unlink = (file, callback) => callback() - return done() + describe('pipeUrlToFile', function() { + it('should turn off the cookie jar in request', function() { + return this.defaults.calledWith({ jar: false }).should.equal(true) }) - describe('successfully', function() { - beforeEach(function(done) { + describe('rewrite url domain if filestoreDomainOveride is set', function() { + beforeEach(function() { + this.path = '/path/to/file/on/disk' + this.request.get = sinon + .stub() + .returns((this.urlStream = new EventEmitter())) + this.urlStream.pipe = sinon.stub() + this.urlStream.pause = sinon.stub() + this.urlStream.resume = sinon.stub() + this.fs.createWriteStream = sinon + .stub() + .returns((this.fileStream = new EventEmitter())) + return (this.fs.unlink = (file, callback) => callback()) + }) + + it('should use the normal domain when override not set', function(done) { this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { - this.callback() + this.request.get.args[0][0].url.should.equal(this.url) return done() }) this.res = { statusCode: 200 } @@ -107,67 +99,113 @@ describe('UrlFetcher', function() { return this.fileStream.emit('finish') }) - it('should request the URL', function() { - return this.request.get - .calledWith(sinon.match({ url: this.url })) - .should.equal(true) - }) - - it('should open the file for writing', function() { - return this.fs.createWriteStream - .calledWith(this.path) - .should.equal(true) - }) - - it('should pipe the URL to the file', function() { - return this.urlStream.pipe - .calledWith(this.fileStream) - .should.equal(true) - }) - - return it('should call the callback', function() { - return this.callback.called.should.equal(true) - }) - }) - - describe('with non success status code', function() { - beforeEach(function(done) { - this.UrlFetcher.pipeUrlToFile(this.url, this.path, err => { - this.callback(err) + return it('should use override domain when filestoreDomainOveride is set', function(done) { + this.settings.filestoreDomainOveride = '192.11.11.11' + this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { + this.request.get.args[0][0].url.should.equal( + '192.11.11.11/file/here?query=string' + ) return done() }) - this.res = { statusCode: 404 } + this.res = { statusCode: 200 } this.urlStream.emit('response', this.res) - return this.urlStream.emit('end') - }) - - it('should call the callback with an error', function() { - this.callback.calledWith(sinon.match(Error)).should.equal(true) - - const message = this.callback.args[0][0].message - expect(message).to.include('URL returned non-success status code: 404') + this.urlStream.emit('end') + return this.fileStream.emit('finish') }) }) - return describe('with error', function() { + return describe('pipeUrlToFile', function() { beforeEach(function(done) { - this.UrlFetcher.pipeUrlToFile(this.url, this.path, err => { - this.callback(err) - return done() + this.path = '/path/to/file/on/disk' + this.request.get = sinon + .stub() + .returns((this.urlStream = new EventEmitter())) + this.urlStream.pipe = sinon.stub() + this.urlStream.pause = sinon.stub() + this.urlStream.resume = sinon.stub() + this.fs.createWriteStream = sinon + .stub() + .returns((this.fileStream = new EventEmitter())) + this.fs.unlink = (file, callback) => callback() + return done() + }) + + describe('successfully', function() { + beforeEach(function(done) { + this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { + this.callback() + return done() + }) + this.res = { statusCode: 200 } + this.urlStream.emit('response', this.res) + this.urlStream.emit('end') + return this.fileStream.emit('finish') + }) + + it('should request the URL', function() { + return this.request.get + .calledWith(sinon.match({ url: this.url })) + .should.equal(true) + }) + + it('should open the file for writing', function() { + return this.fs.createWriteStream + .calledWith(this.path) + .should.equal(true) + }) + + it('should pipe the URL to the file', function() { + return this.urlStream.pipe + .calledWith(this.fileStream) + .should.equal(true) + }) + + return it('should call the callback', function() { + return this.callback.called.should.equal(true) }) - return this.urlStream.emit( - 'error', - (this.error = new Error('something went wrong')) - ) }) - it('should call the callback with the error', function() { - return this.callback.calledWith(this.error).should.equal(true) + describe('with non success status code', function() { + beforeEach(function(done) { + this.UrlFetcher.pipeUrlToFile(this.url, this.path, err => { + this.callback(err) + return done() + }) + this.res = { statusCode: 404 } + this.urlStream.emit('response', this.res) + return this.urlStream.emit('end') + }) + + it('should call the callback with an error', function() { + this.callback.calledWith(sinon.match(Error)).should.equal(true) + + const message = this.callback.args[0][0].message + expect(message).to.include( + 'URL returned non-success status code: 404' + ) + }) }) - return it('should only call the callback once, even if end is called', function() { - this.urlStream.emit('end') - return this.callback.calledOnce.should.equal(true) + return describe('with error', function() { + beforeEach(function(done) { + this.UrlFetcher.pipeUrlToFile(this.url, this.path, err => { + this.callback(err) + return done() + }) + return this.urlStream.emit( + 'error', + (this.error = new Error('something went wrong')) + ) + }) + + it('should call the callback with the error', function() { + return this.callback.calledWith(this.error).should.equal(true) + }) + + return it('should only call the callback once, even if end is called', function() { + this.urlStream.emit('end') + return this.callback.calledOnce.should.equal(true) + }) }) }) }) From 3db513cfc9e254483a0abbc375d8bf1cd7b97e7c Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 15 May 2020 16:08:10 +0100 Subject: [PATCH 575/754] record latexmk output --- services/clsi/app/js/CompileManager.js | 5 +++++ services/clsi/app/js/ResourceWriter.js | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js index 3bf54bc75b..500adfcea0 100644 --- a/services/clsi/app/js/CompileManager.js +++ b/services/clsi/app/js/CompileManager.js @@ -217,6 +217,11 @@ module.exports = CompileManager = { error = new Error('compilation') error.validate = 'fail' } + // make top-level output accesible to user, write in background for simplicity + if (output != null) { + fs.writeFile(Path.join(compileDir, "output.stdout"), output.stdout, () => { }) + fs.writeFile(Path.join(compileDir, "output.stderr"), output.stderr, () => { }) + } // compile was killed by user, was a validation, or a compile which failed validation if ( (error != null ? error.terminated : undefined) || diff --git a/services/clsi/app/js/ResourceWriter.js b/services/clsi/app/js/ResourceWriter.js index ed3a1e8760..750be323ba 100644 --- a/services/clsi/app/js/ResourceWriter.js +++ b/services/clsi/app/js/ResourceWriter.js @@ -201,6 +201,10 @@ module.exports = ResourceWriter = { // knitr cache should_delete = false } + if (path.match(/^output.(stdout|stderr)$/)) { + // latexmk output + should_delete = true + } if (path.match(/^output-.*/)) { // Tikz cached figures (default case) should_delete = false From 3925839c7ffdeb6cbd5e9733da9c8c7f209ba2fe Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@gmail.com> Date: Mon, 11 May 2020 10:29:16 +0100 Subject: [PATCH 576/754] add refreshExpiryTimeout function on clsi all data lives inside of / dir dynamically reduce size of EXPIRY_TIMEOUT if disk starts to get full --- services/clsi/app.js | 8 +-- .../clsi/app/js/ProjectPersistenceManager.js | 22 +++++++ services/clsi/package-lock.json | 14 +++++ services/clsi/package.json | 1 + .../unit/js/ProjectPersistenceManagerTests.js | 62 ++++++++++++++++++- 5 files changed, 102 insertions(+), 5 deletions(-) diff --git a/services/clsi/app.js b/services/clsi/app.js index 80b00ef5f6..6fd29b0adb 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -359,10 +359,10 @@ if (!module.parent) { module.exports = app -setInterval( - () => ProjectPersistenceManager.clearExpiredProjects(), - (tenMinutes = 10 * 60 * 1000) -) +setInterval(() => { + ProjectPersistenceManager.refreshExpiryTimeout() + ProjectPersistenceManager.clearExpiredProjects() +}, (tenMinutes = 10 * 60 * 1000)) function __guard__(value, transform) { return typeof value !== 'undefined' && value !== null diff --git a/services/clsi/app/js/ProjectPersistenceManager.js b/services/clsi/app/js/ProjectPersistenceManager.js index 46eee74771..2c89f13f81 100644 --- a/services/clsi/app/js/ProjectPersistenceManager.js +++ b/services/clsi/app/js/ProjectPersistenceManager.js @@ -20,10 +20,32 @@ const async = require('async') const logger = require('logger-sharelatex') const oneDay = 24 * 60 * 60 * 1000 const Settings = require('settings-sharelatex') +const diskusage = require('diskusage') module.exports = ProjectPersistenceManager = { EXPIRY_TIMEOUT: Settings.project_cache_length_ms || oneDay * 2.5, + refreshExpiryTimeout(callback) { + if (callback == null) { + callback = function(error) {} + } + diskusage.check('/', function(err, stats) { + if (err) { + logger.err({ err: err }, 'error getting disk usage') + return callback(err) + } + const lowDisk = stats.available / stats.total < 0.1 + const lowerExpiry = ProjectPersistenceManager.EXPIRY_TIMEOUT * 0.9 + if (lowDisk && Settings.project_cache_length_ms / 2 < lowerExpiry) { + logger.warn( + { stats: stats }, + 'disk running low on space, modifying EXPIRY_TIMEOUT' + ) + ProjectPersistenceManager.EXPIRY_TIMEOUT = lowerExpiry + } + callback() + }) + }, markProjectAsJustAccessed(project_id, callback) { if (callback == null) { callback = function(error) {} diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index c64e7d4485..2d27ff12ba 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -1883,6 +1883,15 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", "integrity": "sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ=" }, + "diskusage": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/diskusage/-/diskusage-1.1.3.tgz", + "integrity": "sha512-EAyaxl8hy4Ph07kzlzGTfpbZMNAAAHXSZtNEMwdlnSd1noHzvA6HsgKt4fEMSvaEXQYLSphe5rPMxN4WOj0hcQ==", + "requires": { + "es6-promise": "^4.2.5", + "nan": "^2.14.0" + } + }, "dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -6925,6 +6934,11 @@ "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==" }, + "when": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz", + "integrity": "sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I=" + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/services/clsi/package.json b/services/clsi/package.json index 00440a8b1f..5c25fa1207 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -21,6 +21,7 @@ "dependencies": { "async": "3.2.0", "body-parser": "^1.19.0", + "diskusage": "^1.1.3", "dockerode": "^3.1.0", "express": "^4.17.1", "fs-extra": "^8.1.0", diff --git a/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js b/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js index 0d84fc2455..1a12cfffce 100644 --- a/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js +++ b/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js @@ -14,6 +14,7 @@ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') require('chai').should() +const assert = require('chai').assert const modulePath = require('path').join( __dirname, '../../../app/js/ProjectPersistenceManager' @@ -26,7 +27,15 @@ describe('ProjectPersistenceManager', function() { requires: { './UrlCache': (this.UrlCache = {}), './CompileManager': (this.CompileManager = {}), - 'logger-sharelatex': (this.logger = { log: sinon.stub() }), + diskusage: (this.diskusage = { check: sinon.stub() }), + 'logger-sharelatex': (this.logger = { + log: sinon.stub(), + warn: sinon.stub(), + err: sinon.stub() + }), + 'settings-sharelatex': (this.settings = { + project_cache_length_ms: 1000 + }), './db': (this.db = {}) } }) @@ -35,6 +44,57 @@ describe('ProjectPersistenceManager', function() { return (this.user_id = '1234') }) + describe('refreshExpiryTimeout', function() { + it('should leave expiry alone if plenty of disk', function(done) { + this.diskusage.check.callsArgWith(1, null, { + available: 40, + total: 100 + }) + + this.ProjectPersistenceManager.refreshExpiryTimeout(() => { + this.ProjectPersistenceManager.EXPIRY_TIMEOUT.should.equal( + this.settings.project_cache_length_ms + ) + done() + }) + }) + + it('should drop EXPIRY_TIMEOUT 10% if low disk usage', function(done) { + this.diskusage.check.callsArgWith(1, null, { + available: 5, + total: 100 + }) + + this.ProjectPersistenceManager.refreshExpiryTimeout(() => { + this.ProjectPersistenceManager.EXPIRY_TIMEOUT.should.equal(900) + done() + }) + }) + + it('should not drop EXPIRY_TIMEOUT to below 50% of project_cache_length_ms', function(done) { + this.diskusage.check.callsArgWith(1, null, { + available: 5, + total: 100 + }) + this.ProjectPersistenceManager.EXPIRY_TIMEOUT = 500 + this.ProjectPersistenceManager.refreshExpiryTimeout(() => { + this.ProjectPersistenceManager.EXPIRY_TIMEOUT.should.equal(500) + done() + }) + }) + + it('should not modify EXPIRY_TIMEOUT if there is an error getting disk values', function(done) { + this.diskusage.check.callsArgWith(1, 'Error', { + available: 5, + total: 100 + }) + this.ProjectPersistenceManager.refreshExpiryTimeout(() => { + this.ProjectPersistenceManager.EXPIRY_TIMEOUT.should.equal(1000) + done() + }) + }) + }) + describe('clearExpiredProjects', function() { beforeEach(function() { this.project_ids = ['project-id-1', 'project-id-2'] From 2a3c2dd3d5b94cfa56d9189326f613efac1d66a5 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Thu, 7 May 2020 18:57:35 +0200 Subject: [PATCH 577/754] [misc] simplify the smoke test and process shutdown --- services/clsi/app.js | 61 ++++--------- services/clsi/package-lock.json | 100 ---------------------- services/clsi/package.json | 1 - services/clsi/test/smoke/js/SmokeTests.js | 92 ++++++++++---------- 4 files changed, 65 insertions(+), 189 deletions(-) diff --git a/services/clsi/app.js b/services/clsi/app.js index 6fd29b0adb..311fc31e6a 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -17,7 +17,7 @@ if ((Settings.sentry != null ? Settings.sentry.dsn : undefined) != null) { logger.initializeErrorReporting(Settings.sentry.dsn) } -const smokeTest = require('smoke-test-sharelatex') +const smokeTest = require('./test/smoke/js/SmokeTests') const ContentTypeMapper = require('./app/js/ContentTypeMapper') const Errors = require('./app/js/Errors') @@ -192,64 +192,39 @@ app.get('/oops', function(req, res, next) { app.get('/status', (req, res, next) => res.send('CLSI is alive\n')) -const resCacher = { - contentType(setContentType) { - this.setContentType = setContentType - }, - send(code, body) { - this.code = code - this.body = body - }, - - // default the server to be down - code: 500, - body: {}, - setContentType: 'application/json' -} - -let shutdownTime +let PROCESS_IS_TOO_OLD = false if (Settings.processLifespanLimitMs) { Settings.processLifespanLimitMs += Settings.processLifespanLimitMs * (Math.random() / 10) - shutdownTime = Date.now() + Settings.processLifespanLimitMs - logger.info('Lifespan limited to ', shutdownTime) -} + logger.info( + 'Lifespan limited to ', + Date.now() + Settings.processLifespanLimitMs + ) -const checkIfProcessIsTooOld = function(cont) { - if (shutdownTime && shutdownTime < Date.now()) { + setTimeout(() => { logger.log('shutting down, process is too old') - resCacher.send = function() {} - resCacher.code = 500 - resCacher.body = { processToOld: true } - } else { - cont() - } + PROCESS_IS_TOO_OLD = true + }, Settings.processLifespanLimitMs) } if (Settings.smokeTest) { - const runSmokeTest = function() { - checkIfProcessIsTooOld(function() { - logger.log('running smoke tests') - smokeTest.run( - require.resolve(__dirname + '/test/smoke/js/SmokeTests.js') - )({}, resCacher) - return setTimeout(runSmokeTest, 30 * 1000) + function runSmokeTest() { + if (PROCESS_IS_TOO_OLD) return + logger.log('running smoke tests') + smokeTest.triggerRun(err => { + logger.error({ err }, 'smoke tests failed') + setTimeout(runSmokeTest, 30 * 1000) }) } runSmokeTest() } app.get('/health_check', function(req, res) { - res.contentType(resCacher.setContentType) - return res.status(resCacher.code).send(resCacher.body) + if (PROCESS_IS_TOO_OLD) return res.status(500).json({ processToOld: true }) + smokeTest.sendLastResult(res) }) -app.get('/smoke_test_force', (req, res) => - smokeTest.run(require.resolve(__dirname + '/test/smoke/js/SmokeTests.js'))( - req, - res - ) -) +app.get('/smoke_test_force', (req, res) => smokeTest.sendNewResult(res)) app.use(function(error, req, res, next) { if (error instanceof Errors.NotFoundError) { diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index 2d27ff12ba..7f896457cd 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -1669,11 +1669,6 @@ "delayed-stream": "~1.0.0" } }, - "commander": { - "version": "2.0.0", - "resolved": "http://registry.npmjs.org/commander/-/commander-2.0.0.tgz", - "integrity": "sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg=" - }, "common-tags": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", @@ -1878,11 +1873,6 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, - "diff": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", - "integrity": "sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ=" - }, "diskusage": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/diskusage/-/diskusage-1.1.3.tgz", @@ -3066,11 +3056,6 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" }, - "growl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", - "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=" - }, "gtoken": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.1.0.tgz", @@ -3576,27 +3561,6 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, - "jade": { - "version": "0.26.3", - "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", - "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", - "requires": { - "commander": "0.6.1", - "mkdirp": "0.3.0" - }, - "dependencies": { - "commander": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", - "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=" - }, - "mkdirp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", - "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=" - } - } - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6062,11 +6026,6 @@ "object-inspect": "^1.7.0" } }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" - }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -6129,65 +6088,6 @@ } } }, - "smoke-test-sharelatex": { - "version": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#bc3e93d18ccee219c0d99e8b02c984ccdd842e1c", - "from": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", - "requires": { - "mocha": "~1.17.0" - }, - "dependencies": { - "glob": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", - "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", - "requires": { - "graceful-fs": "~2.0.0", - "inherits": "2", - "minimatch": "~0.2.11" - } - }, - "graceful-fs": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", - "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=" - }, - "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" - }, - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" - } - }, - "mocha": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.17.1.tgz", - "integrity": "sha1-f3Zx1oUm0HS3uuZgyQmfh+DqHMs=", - "requires": { - "commander": "2.0.0", - "debug": "*", - "diff": "1.0.7", - "glob": "3.2.3", - "growl": "1.7.x", - "jade": "0.26.3", - "mkdirp": "0.3.5" - }, - "dependencies": { - "mkdirp": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", - "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=" - } - } - } - } - }, "snakecase-keys": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/snakecase-keys/-/snakecase-keys-3.1.0.tgz", diff --git a/services/clsi/package.json b/services/clsi/package.json index 5c25fa1207..e57bfa5518 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -34,7 +34,6 @@ "request": "^2.88.2", "sequelize": "^5.21.5", "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.1.0", - "smoke-test-sharelatex": "git+https://github.com/sharelatex/smoke-test-sharelatex.git#v0.2.0", "sqlite3": "^4.1.1", "underscore": "^1.9.2", "v8-profiler-node8": "^6.1.1", diff --git a/services/clsi/test/smoke/js/SmokeTests.js b/services/clsi/test/smoke/js/SmokeTests.js index 851ea85079..fc52121ee0 100644 --- a/services/clsi/test/smoke/js/SmokeTests.js +++ b/services/clsi/test/smoke/js/SmokeTests.js @@ -1,20 +1,3 @@ -/* eslint-disable - no-unused-vars, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS101: Remove unnecessary use of Array.from - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -const chai = require('chai') -if (Object.prototype.should == null) { - chai.should() -} -const { expect } = chai const request = require('request') const Settings = require('settings-sharelatex') @@ -23,9 +6,35 @@ const buildUrl = path => const url = buildUrl(`project/smoketest-${process.pid}/compile`) -describe('Running a compile', function() { - before(function(done) { - return request.post( +module.exports = { + sendNewResult(res) { + this._run(error => this._sendResponse(res, error)) + }, + sendLastResult(res) { + this._sendResponse(res, this._lastError) + }, + triggerRun(cb) { + this._run(error => { + this._lastError = error + cb(error) + }) + }, + + _lastError: new Error('SmokeTestsPending'), + _sendResponse(res, error) { + let code, body + if (error) { + code = 500 + body = error.message + } else { + code = 200 + body = 'OK' + } + res.contentType('text/plain') + res.status(code).send(body) + }, + _run(done) { + request.post( { url, json: { @@ -50,7 +59,7 @@ describe('Running a compile', function() { \\pgfmathsetmacro{\\dy}{rand*0.1}% A random variance in the y coordinate, % gives a hight fill to the lipid \\pgfmathsetmacro{\\rot}{rand*0.1}% A random variance in the - % molecule orientation + % molecule orientation \\shade[ball color=red] ({\\i+\\dx+\\rot},{0.5*\\j+\\dy+0.4*sin(\\i*\\nuPi*10)}) circle(0.45); \\shade[ball color=gray] (\\i+\\dx,{0.5*\\j+\\dy+0.4*sin(\\i*\\nuPi*10)-0.9}) circle(0.45); \\shade[ball color=gray] (\\i+\\dx-\\rot,{0.5*\\j+\\dy+0.4*sin(\\i*\\nuPi*10)-1.8}) circle(0.45); @@ -72,29 +81,22 @@ describe('Running a compile', function() { } }, (error, response, body) => { - this.error = error - this.response = response - this.body = body - return done() + if (error) return done(error) + if (!body || !body.compile || !body.compile.outputFiles) { + return done(new Error('response payload incomplete')) + } + + let pdfFound = false + let logFound = false + for (const file of body.compile.outputFiles) { + if (file.type === 'pdf') pdfFound = true + if (file.type === 'log') logFound = true + } + + if (!pdfFound) return done(new Error('no pdf returned')) + if (!logFound) return done(new Error('no log returned')) + done() } ) - }) - - it('should return the pdf', function() { - for (const file of Array.from(this.body.compile.outputFiles)) { - if (file.type === 'pdf') { - return - } - } - throw new Error('no pdf returned') - }) - - return it('should return the log', function() { - for (const file of Array.from(this.body.compile.outputFiles)) { - if (file.type === 'log') { - return - } - } - throw new Error('no log returned') - }) -}) + } +} From 36e81cbe15a3599a6386c630ea950981d273e071 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Mon, 11 May 2020 13:08:13 +0200 Subject: [PATCH 578/754] [misc] apply review feedback --- services/clsi/app.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/services/clsi/app.js b/services/clsi/app.js index 311fc31e6a..add5f21720 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -192,7 +192,7 @@ app.get('/oops', function(req, res, next) { app.get('/status', (req, res, next) => res.send('CLSI is alive\n')) -let PROCESS_IS_TOO_OLD = false +Settings.processTooOld = false if (Settings.processLifespanLimitMs) { Settings.processLifespanLimitMs += Settings.processLifespanLimitMs * (Math.random() / 10) @@ -203,16 +203,16 @@ if (Settings.processLifespanLimitMs) { setTimeout(() => { logger.log('shutting down, process is too old') - PROCESS_IS_TOO_OLD = true + Settings.processTooOld = true }, Settings.processLifespanLimitMs) } if (Settings.smokeTest) { function runSmokeTest() { - if (PROCESS_IS_TOO_OLD) return + if (Settings.processTooOld) return logger.log('running smoke tests') smokeTest.triggerRun(err => { - logger.error({ err }, 'smoke tests failed') + if (err) logger.error({ err }, 'smoke tests failed') setTimeout(runSmokeTest, 30 * 1000) }) } @@ -220,7 +220,9 @@ if (Settings.smokeTest) { } app.get('/health_check', function(req, res) { - if (PROCESS_IS_TOO_OLD) return res.status(500).json({ processToOld: true }) + if (Settings.processTooOld) { + return res.status(500).json({ processTooOld: true }) + } smokeTest.sendLastResult(res) }) From 63770bf3905d7ed3e0009f501acd78c035df53e1 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Wed, 20 May 2020 11:45:29 +0100 Subject: [PATCH 579/754] clean up the stdout/stderr recording --- services/clsi/app/js/CompileManager.js | 5 ---- services/clsi/app/js/LatexRunner.js | 32 ++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js index 500adfcea0..3bf54bc75b 100644 --- a/services/clsi/app/js/CompileManager.js +++ b/services/clsi/app/js/CompileManager.js @@ -217,11 +217,6 @@ module.exports = CompileManager = { error = new Error('compilation') error.validate = 'fail' } - // make top-level output accesible to user, write in background for simplicity - if (output != null) { - fs.writeFile(Path.join(compileDir, "output.stdout"), output.stdout, () => { }) - fs.writeFile(Path.join(compileDir, "output.stderr"), output.stderr, () => { }) - } // compile was killed by user, was a validation, or a compile which failed validation if ( (error != null ? error.terminated : undefined) || diff --git a/services/clsi/app/js/LatexRunner.js b/services/clsi/app/js/LatexRunner.js index 972f1fe7c3..fe737c76c6 100644 --- a/services/clsi/app/js/LatexRunner.js +++ b/services/clsi/app/js/LatexRunner.js @@ -19,6 +19,7 @@ const Settings = require('settings-sharelatex') const logger = require('logger-sharelatex') const Metrics = require('./Metrics') const CommandRunner = require('./CommandRunner') +const fs = require('fs') const ProcessTable = {} // table of currently running jobs (pids or docker container names) @@ -127,9 +128,36 @@ module.exports = LatexRunner = { : undefined, x5 => x5[1] ) || 0 - return callback(error, output, stats, timings) + // record output files + LatexRunner.writeLogOutput(project_id, directory, output, () => { + return callback(error, output, stats, timings) + }) + })) + }, + + writeLogOutput(project_id, directory, output, callback) { + if (!output) { + return callback() + } + // internal method for writing non-empty log files + function _writeFile(file, content, cb) { + if (content && content.length > 0) { + fs.writeFile(file, content, (err) => { + if (err) { + logger.error({ project_id, file }, "error writing log file") // don't fail on error + } + cb() + }) + } else { + cb() } - )) + } + // write stdout and stderr, ignoring errors + _writeFile(Path.join(directory, "output.stdout"), output.stdout, () => { + _writeFile(Path.join(directory, "output.stderr"), output.stderr, () => { + callback() + }) + }) }, killLatex(project_id, callback) { From a684619bceada1a806e169f117b6d895de124948 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Wed, 20 May 2020 11:52:53 +0100 Subject: [PATCH 580/754] add unit tests --- .../clsi/test/unit/js/LatexRunnerTests.js | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/services/clsi/test/unit/js/LatexRunnerTests.js b/services/clsi/test/unit/js/LatexRunnerTests.js index b468b83183..15902e9b84 100644 --- a/services/clsi/test/unit/js/LatexRunnerTests.js +++ b/services/clsi/test/unit/js/LatexRunnerTests.js @@ -37,7 +37,10 @@ describe('LatexRunner', function() { done() {} }) }, - './CommandRunner': (this.CommandRunner = {}) + './CommandRunner': (this.CommandRunner = {}), + 'fs': (this.fs = { + writeFile: sinon.stub().callsArg(2) + }) } }) @@ -83,6 +86,21 @@ describe('LatexRunner', function() { ) .should.equal(true) }) + + it('should record the stdout and stderr', function () { + this.fs.writeFile + .calledWith( + this.directory + '/' + 'output.stdout', + "this is stdout" + ) + .should.equal(true) + this.fs.writeFile + .calledWith( + this.directory + '/' + 'output.stderr', + "this is stderr" + ) + .should.equal(true) + }) }) describe('with an .Rtex main file', function() { From 6d5dfb7758a2fc682c3f9578455c7f2fc71fac7f Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Wed, 20 May 2020 14:12:08 +0100 Subject: [PATCH 581/754] clean up log file deletion and add unit test --- services/clsi/app/js/ResourceWriter.js | 8 +++----- .../clsi/test/unit/js/ResourceWriterTests.js | 20 ++++++++++++++++++- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/services/clsi/app/js/ResourceWriter.js b/services/clsi/app/js/ResourceWriter.js index 750be323ba..97e971e1d5 100644 --- a/services/clsi/app/js/ResourceWriter.js +++ b/services/clsi/app/js/ResourceWriter.js @@ -201,10 +201,6 @@ module.exports = ResourceWriter = { // knitr cache should_delete = false } - if (path.match(/^output.(stdout|stderr)$/)) { - // latexmk output - should_delete = true - } if (path.match(/^output-.*/)) { // Tikz cached figures (default case) should_delete = false @@ -235,7 +231,9 @@ module.exports = ResourceWriter = { path === 'output.pdf' || path === 'output.dvi' || path === 'output.log' || - path === 'output.xdv' + path === 'output.xdv' || + path === 'output.stdout' || + path === 'output.stderr' ) { should_delete = true } diff --git a/services/clsi/test/unit/js/ResourceWriterTests.js b/services/clsi/test/unit/js/ResourceWriterTests.js index a632c1bdff..1080765b7b 100644 --- a/services/clsi/test/unit/js/ResourceWriterTests.js +++ b/services/clsi/test/unit/js/ResourceWriterTests.js @@ -230,6 +230,12 @@ describe('ResourceWriter', function() { { path: '_markdown_main/30893013dec5d869a415610079774c2f.md.tex', type: 'tex' + }, + { + path: 'output.stdout' + }, + { + path: 'output.stderr' } ] this.resources = 'mock-resources' @@ -256,7 +262,19 @@ describe('ResourceWriter', function() { .should.equal(true) }) - it('should delete the extra files', function() { + it('should delete the stdout log file', function () { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, 'output.stdout')) + .should.equal(true) + }) + + it('should delete the stderr log file', function () { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, 'output.stderr')) + .should.equal(true) + }) + + it('should delete the extra files', function () { return this.ResourceWriter._deleteFileIfNotDirectory .calledWith(path.join(this.basePath, 'extra/file.tex')) .should.equal(true) From 563a973388dd888b26452fbc1559fdb3b24d52d4 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <das7pad@outlook.com> Date: Wed, 27 Nov 2019 21:40:48 +0100 Subject: [PATCH 582/754] [DockerRunner] destroyOldContainers: normalize the container name The docker api returns each name with a `/` prefix. In order to not interfere with pending compiles, the deletion process has to acquire an internal lock on the container. The LockManager uses the plain container name without the slash: `project-xxx`. Signed-off-by: Jakob Ackermann <das7pad@outlook.com> --- services/clsi/app/js/DockerRunner.js | 3 +++ services/clsi/test/unit/js/DockerRunnerTests.js | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index fd7fc317ff..3f90c81123 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -632,6 +632,9 @@ module.exports = DockerRunner = { ttl ) { if (name.slice(0, 9) === '/project-' && ttl <= 0) { + // strip the / prefix + // the LockManager uses the plain container name + name = name.slice(1) return jobs.push(cb => DockerRunner.destroyContainer(name, id, false, () => cb()) ) diff --git a/services/clsi/test/unit/js/DockerRunnerTests.js b/services/clsi/test/unit/js/DockerRunnerTests.js index 83992833f3..d41ee3f7f1 100644 --- a/services/clsi/test/unit/js/DockerRunnerTests.js +++ b/services/clsi/test/unit/js/DockerRunnerTests.js @@ -630,19 +630,19 @@ describe('DockerRunner', function() { it('should destroy old containers', function() { this.DockerRunner.destroyContainer.callCount.should.equal(1) return this.DockerRunner.destroyContainer - .calledWith('/project-old-container-name', 'old-container-id') + .calledWith('project-old-container-name', 'old-container-id') .should.equal(true) }) it('should not destroy new containers', function() { return this.DockerRunner.destroyContainer - .calledWith('/project-new-container-name', 'new-container-id') + .calledWith('project-new-container-name', 'new-container-id') .should.equal(false) }) it('should not destroy non-project containers', function() { return this.DockerRunner.destroyContainer - .calledWith('/totally-not-a-project-container', 'some-random-id') + .calledWith('totally-not-a-project-container', 'some-random-id') .should.equal(false) }) From a3d27b63f16959fbba885383e774b4dd38a429c7 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Tue, 2 Jun 2020 09:18:38 +0100 Subject: [PATCH 583/754] fix formatting with make format_fix --- services/clsi/app/js/DockerRunner.js | 2 +- services/clsi/app/js/LatexRunner.js | 11 ++++++----- services/clsi/test/unit/js/LatexRunnerTests.js | 14 ++++---------- services/clsi/test/unit/js/ResourceWriterTests.js | 6 +++--- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index e7cb2a7333..c50087d018 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -412,7 +412,7 @@ module.exports = DockerRunner = { }) } ) - var inspectContainer = (isRetry) => + var inspectContainer = isRetry => container.inspect(function(error, stats) { if ((error != null ? error.statusCode : undefined) === 404) { return createAndStartContainer() diff --git a/services/clsi/app/js/LatexRunner.js b/services/clsi/app/js/LatexRunner.js index fe737c76c6..c3edb29e24 100644 --- a/services/clsi/app/js/LatexRunner.js +++ b/services/clsi/app/js/LatexRunner.js @@ -132,7 +132,8 @@ module.exports = LatexRunner = { LatexRunner.writeLogOutput(project_id, directory, output, () => { return callback(error, output, stats, timings) }) - })) + } + )) }, writeLogOutput(project_id, directory, output, callback) { @@ -142,9 +143,9 @@ module.exports = LatexRunner = { // internal method for writing non-empty log files function _writeFile(file, content, cb) { if (content && content.length > 0) { - fs.writeFile(file, content, (err) => { + fs.writeFile(file, content, err => { if (err) { - logger.error({ project_id, file }, "error writing log file") // don't fail on error + logger.error({ project_id, file }, 'error writing log file') // don't fail on error } cb() }) @@ -153,8 +154,8 @@ module.exports = LatexRunner = { } } // write stdout and stderr, ignoring errors - _writeFile(Path.join(directory, "output.stdout"), output.stdout, () => { - _writeFile(Path.join(directory, "output.stderr"), output.stderr, () => { + _writeFile(Path.join(directory, 'output.stdout'), output.stdout, () => { + _writeFile(Path.join(directory, 'output.stderr'), output.stderr, () => { callback() }) }) diff --git a/services/clsi/test/unit/js/LatexRunnerTests.js b/services/clsi/test/unit/js/LatexRunnerTests.js index 15902e9b84..42e816d2eb 100644 --- a/services/clsi/test/unit/js/LatexRunnerTests.js +++ b/services/clsi/test/unit/js/LatexRunnerTests.js @@ -38,7 +38,7 @@ describe('LatexRunner', function() { }) }, './CommandRunner': (this.CommandRunner = {}), - 'fs': (this.fs = { + fs: (this.fs = { writeFile: sinon.stub().callsArg(2) }) } @@ -87,18 +87,12 @@ describe('LatexRunner', function() { .should.equal(true) }) - it('should record the stdout and stderr', function () { + it('should record the stdout and stderr', function() { this.fs.writeFile - .calledWith( - this.directory + '/' + 'output.stdout', - "this is stdout" - ) + .calledWith(this.directory + '/' + 'output.stdout', 'this is stdout') .should.equal(true) this.fs.writeFile - .calledWith( - this.directory + '/' + 'output.stderr', - "this is stderr" - ) + .calledWith(this.directory + '/' + 'output.stderr', 'this is stderr') .should.equal(true) }) }) diff --git a/services/clsi/test/unit/js/ResourceWriterTests.js b/services/clsi/test/unit/js/ResourceWriterTests.js index 1080765b7b..36a951361e 100644 --- a/services/clsi/test/unit/js/ResourceWriterTests.js +++ b/services/clsi/test/unit/js/ResourceWriterTests.js @@ -262,19 +262,19 @@ describe('ResourceWriter', function() { .should.equal(true) }) - it('should delete the stdout log file', function () { + it('should delete the stdout log file', function() { return this.ResourceWriter._deleteFileIfNotDirectory .calledWith(path.join(this.basePath, 'output.stdout')) .should.equal(true) }) - it('should delete the stderr log file', function () { + it('should delete the stderr log file', function() { return this.ResourceWriter._deleteFileIfNotDirectory .calledWith(path.join(this.basePath, 'output.stderr')) .should.equal(true) }) - it('should delete the extra files', function () { + it('should delete the extra files', function() { return this.ResourceWriter._deleteFileIfNotDirectory .calledWith(path.join(this.basePath, 'extra/file.tex')) .should.equal(true) From 0de2933812b857940d0630eb1313fb31534ba9d5 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Tue, 2 Jun 2020 09:19:39 +0100 Subject: [PATCH 584/754] fix unreachable code lint error --- services/clsi/test/unit/js/LatexRunnerTests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/test/unit/js/LatexRunnerTests.js b/services/clsi/test/unit/js/LatexRunnerTests.js index 42e816d2eb..d9aa398d2a 100644 --- a/services/clsi/test/unit/js/LatexRunnerTests.js +++ b/services/clsi/test/unit/js/LatexRunnerTests.js @@ -74,7 +74,7 @@ describe('LatexRunner', function() { ) }) - return it('should run the latex command', function() { + it('should run the latex command', function() { return this.CommandRunner.run .calledWith( this.project_id, From aa44bae2226172629a98e7da5eab9ea948f8e956 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Tue, 2 Jun 2020 09:51:34 +0100 Subject: [PATCH 585/754] fix eslint errors --- services/clsi/app.js | 66 +++++++++---------- services/clsi/app/js/UrlFetcher.js | 1 + services/clsi/config/settings.defaults.js | 20 +++--- services/clsi/test/unit/js/UrlFetcherTests.js | 2 +- 4 files changed, 44 insertions(+), 45 deletions(-) diff --git a/services/clsi/app.js b/services/clsi/app.js index add5f21720..319799a7a6 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -5,7 +5,7 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let tenMinutes +const tenMinutes = 10 * 60 * 1000 const Metrics = require('metrics-sharelatex') Metrics.initialize('clsi') @@ -49,31 +49,29 @@ app.use(function(req, res, next) { return next() }) -app.param('project_id', function(req, res, next, project_id) { - if (project_id != null ? project_id.match(/^[a-zA-Z0-9_-]+$/) : undefined) { +app.param('project_id', function(req, res, next, projectId) { + if (projectId != null ? projectId.match(/^[a-zA-Z0-9_-]+$/) : undefined) { return next() } else { return next(new Error('invalid project id')) } }) -app.param('user_id', function(req, res, next, user_id) { - if (user_id != null ? user_id.match(/^[0-9a-f]{24}$/) : undefined) { +app.param('user_id', function(req, res, next, userId) { + if (userId != null ? userId.match(/^[0-9a-f]{24}$/) : undefined) { return next() } else { return next(new Error('invalid user id')) } }) -app.param('build_id', function(req, res, next, build_id) { +app.param('build_id', function(req, res, next, buildId) { if ( - build_id != null - ? build_id.match(OutputCacheManager.BUILD_REGEX) - : undefined + buildId != null ? buildId.match(OutputCacheManager.BUILD_REGEX) : undefined ) { return next() } else { - return next(new Error(`invalid build id ${build_id}`)) + return next(new Error(`invalid build id ${buildId}`)) } }) @@ -207,15 +205,15 @@ if (Settings.processLifespanLimitMs) { }, Settings.processLifespanLimitMs) } +function runSmokeTest() { + if (Settings.processTooOld) return + logger.log('running smoke tests') + smokeTest.triggerRun(err => { + if (err) logger.error({ err }, 'smoke tests failed') + setTimeout(runSmokeTest, 30 * 1000) + }) +} if (Settings.smokeTest) { - function runSmokeTest() { - if (Settings.processTooOld) return - logger.log('running smoke tests') - smokeTest.triggerRun(err => { - if (err) logger.error({ err }, 'smoke tests failed') - setTimeout(runSmokeTest, 30 * 1000) - }) - } runSmokeTest() } @@ -308,29 +306,31 @@ const host = x1 => x1.host ) || 'localhost' -const load_tcp_port = Settings.internal.load_balancer_agent.load_port -const load_http_port = Settings.internal.load_balancer_agent.local_port +const loadTcpPort = Settings.internal.load_balancer_agent.load_port +const loadHttpPort = Settings.internal.load_balancer_agent.local_port if (!module.parent) { // Called directly - app.listen(port, host, error => - logger.info(`CLSI starting up, listening on ${host}:${port}`) - ) - - loadTcpServer.listen(load_tcp_port, host, function(error) { - if (error != null) { - throw error + app.listen(port, host, error => { + if (error) { + logger.fatal({ error }, `Error starting CLSI on ${host}:${port}`) + } else { + logger.info(`CLSI starting up, listening on ${host}:${port}`) } - return logger.info(`Load tcp agent listening on load port ${load_tcp_port}`) }) - loadHttpServer.listen(load_http_port, host, function(error) { + loadTcpServer.listen(loadTcpPort, host, function(error) { if (error != null) { throw error } - return logger.info( - `Load http agent listening on load port ${load_http_port}` - ) + return logger.info(`Load tcp agent listening on load port ${loadTcpPort}`) + }) + + loadHttpServer.listen(loadHttpPort, host, function(error) { + if (error != null) { + throw error + } + return logger.info(`Load http agent listening on load port ${loadHttpPort}`) }) } @@ -339,7 +339,7 @@ module.exports = app setInterval(() => { ProjectPersistenceManager.refreshExpiryTimeout() ProjectPersistenceManager.clearExpiredProjects() -}, (tenMinutes = 10 * 60 * 1000)) +}, tenMinutes) function __guard__(value, transform) { return typeof value !== 'undefined' && value !== null diff --git a/services/clsi/app/js/UrlFetcher.js b/services/clsi/app/js/UrlFetcher.js index 6c7d83af0e..f9f993f363 100644 --- a/services/clsi/app/js/UrlFetcher.js +++ b/services/clsi/app/js/UrlFetcher.js @@ -12,6 +12,7 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ +let UrlFetcher const request = require('request').defaults({ jar: false }) const fs = require('fs') const logger = require('logger-sharelatex') diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index ba63e243b4..51cb624862 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -9,7 +9,7 @@ module.exports = { username: 'clsi', dialect: 'sqlite', storage: - process.env.SQLITE_PATH || Path.resolve(__dirname + '/../db/db.sqlite'), + process.env.SQLITE_PATH || Path.resolve(__dirname, '../db/db.sqlite'), pool: { max: 1, min: 1 @@ -26,10 +26,10 @@ module.exports = { parseInt(process.env.PROCESS_LIFE_SPAN_LIMIT_MS) || 60 * 60 * 24 * 1000 * 2, path: { - compilesDir: Path.resolve(__dirname + '/../compiles'), - clsiCacheDir: Path.resolve(__dirname + '/../cache'), - synctexBaseDir(project_id) { - return Path.join(this.compilesDir, project_id) + compilesDir: Path.resolve(__dirname, '../compiles'), + clsiCacheDir: Path.resolve(__dirname, '../cache'), + synctexBaseDir(projectId) { + return Path.join(this.compilesDir, projectId) } }, @@ -63,7 +63,7 @@ module.exports = { } if (process.env.DOCKER_RUNNER) { - let seccomp_profile_path + let seccompProfilePath module.exports.clsi = { dockerRunner: process.env.DOCKER_RUNNER === 'true', docker: { @@ -81,16 +81,14 @@ if (process.env.DOCKER_RUNNER) { } try { - seccomp_profile_path = Path.resolve( - __dirname + '/../seccomp/clsi-profile.json' - ) + seccompProfilePath = Path.resolve(__dirname, '../seccomp/clsi-profile.json') module.exports.clsi.docker.seccomp_profile = JSON.stringify( - JSON.parse(require('fs').readFileSync(seccomp_profile_path)) + JSON.parse(require('fs').readFileSync(seccompProfilePath)) ) } catch (error) { console.log( error, - `could not load seccom profile from ${seccomp_profile_path}` + `could not load seccom profile from ${seccompProfilePath}` ) } diff --git a/services/clsi/test/unit/js/UrlFetcherTests.js b/services/clsi/test/unit/js/UrlFetcherTests.js index 247920979f..57bee3a7cf 100644 --- a/services/clsi/test/unit/js/UrlFetcherTests.js +++ b/services/clsi/test/unit/js/UrlFetcherTests.js @@ -48,7 +48,7 @@ describe('UrlFetcher', function() { }) it('should call pipeUrlToFile multiple times on error', function(done) { - error = new Error("couldn't download file") + const error = new Error("couldn't download file") this.UrlFetcher.pipeUrlToFile.callsArgWith(2, error) this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, err => { expect(err).to.equal(error) From ce1f2e2bd45a5969ec0b24b08ad350f837985b4d Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Tue, 2 Jun 2020 11:12:57 +0100 Subject: [PATCH 586/754] fix broken unit test --- services/clsi/test/unit/js/LatexRunnerTests.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/clsi/test/unit/js/LatexRunnerTests.js b/services/clsi/test/unit/js/LatexRunnerTests.js index d9aa398d2a..b112b17006 100644 --- a/services/clsi/test/unit/js/LatexRunnerTests.js +++ b/services/clsi/test/unit/js/LatexRunnerTests.js @@ -55,7 +55,10 @@ describe('LatexRunner', function() { return describe('runLatex', function() { beforeEach(function() { - return (this.CommandRunner.run = sinon.stub().callsArg(6)) + return (this.CommandRunner.run = sinon.stub().callsArgWith(6, null, { + stdout: 'this is stdout', + stderr: 'this is stderr' + })) }) describe('normally', function() { From 8b8acfed4f62a465b961d16b79c14e5114d047f5 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Wed, 3 Jun 2020 10:22:31 +0100 Subject: [PATCH 587/754] update to node 10.21.0 --- services/clsi/.nvmrc | 2 +- services/clsi/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/.nvmrc b/services/clsi/.nvmrc index 5b7269c0a9..b61c07ffdd 100644 --- a/services/clsi/.nvmrc +++ b/services/clsi/.nvmrc @@ -1 +1 @@ -10.19.0 +10.21.0 diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 40615ad8c3..9b37883133 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -FROM node:10.19.0 as base +FROM node:10.21.0 as base WORKDIR /app COPY install_deps.sh /app From b64d68bc9f9393ef2ea293d2c2bdf8dda7fbc38a Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Wed, 3 Jun 2020 11:11:51 +0100 Subject: [PATCH 588/754] update buildscript.txt to node 10.21.0 --- services/clsi/buildscript.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index 81d65464f9..78ef1d04ab 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -6,6 +6,6 @@ clsi --env-add= --env-pass-through=TEXLIVE_IMAGE --language=es ---node-version=10.19.0 +--node-version=10.21.0 --public-repo=True --script-version=2.1.0 From 3d6b9112950b1ee68c03a513add9001aa58789fd Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Thu, 4 Jun 2020 11:47:22 +0100 Subject: [PATCH 589/754] add setting TEXLIVE_OPENOUT_ANY --- services/clsi/app/js/CompileManager.js | 4 ++++ services/clsi/config/settings.defaults.js | 1 + 2 files changed, 5 insertions(+) diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js index dd62f435ed..614c49aaf7 100644 --- a/services/clsi/app/js/CompileManager.js +++ b/services/clsi/app/js/CompileManager.js @@ -142,6 +142,10 @@ module.exports = CompileManager = { ) // set up environment variables for chktex const env = {} + if (Settings.texliveOpenoutAny && Settings.texliveOpenoutAny !== '') { + // override default texlive openout_any environment variable + env.openout_any = Settings.texliveOpenoutAny + } // only run chktex on LaTeX files (not knitr .Rtex files or any others) const isLaTeXFile = request.rootResourcePath != null diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index 51cb624862..3328afa92d 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -57,6 +57,7 @@ module.exports = { parallelSqlQueryLimit: process.env.FILESTORE_PARALLEL_SQL_QUERY_LIMIT || 1, filestoreDomainOveride: process.env.FILESTORE_DOMAIN_OVERRIDE, texliveImageNameOveride: process.env.TEX_LIVE_IMAGE_NAME_OVERRIDE, + texliveOpenoutAny: process.env.TEXLIVE_OPENOUT_ANY, sentry: { dsn: process.env.SENTRY_DSN } From 54d1cf7eeb60d3f5d32a1f8d20e397fcb3d6b1b0 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Tue, 9 Jun 2020 11:22:28 +0100 Subject: [PATCH 590/754] add missing setting for optimiseInDocker --- services/clsi/config/settings.defaults.js | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index 51cb624862..d051cea8f8 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -76,6 +76,7 @@ if (process.env.DOCKER_RUNNER) { socketPath: '/var/run/docker.sock', user: process.env.TEXLIVE_IMAGE_USER || 'tex' }, + optimiseInDocker: true, expireProjectAfterIdleMs: 24 * 60 * 60 * 1000, checkProjectsIntervalMs: 10 * 60 * 1000 } From 6c98c7af4a7d3b897459c0c5efb2d1b4cbfbd75e Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Wed, 10 Jun 2020 11:42:07 +0100 Subject: [PATCH 591/754] error on missing profile --- services/clsi/config/settings.defaults.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index 3328afa92d..2f74da0b93 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -87,10 +87,11 @@ if (process.env.DOCKER_RUNNER) { JSON.parse(require('fs').readFileSync(seccompProfilePath)) ) } catch (error) { - console.log( + console.error( error, - `could not load seccom profile from ${seccompProfilePath}` + `could not load seccomp profile from ${seccompProfilePath}` ) + process.exit(1) } module.exports.path.synctexBaseDir = () => '/compile' From f239fc3b5d83b92925b9ff6eccba5fda030a062f Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Thu, 11 Jun 2020 10:54:26 +0100 Subject: [PATCH 592/754] send 503 unavailable response on EPIPE --- services/clsi/app.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/clsi/app.js b/services/clsi/app.js index 319799a7a6..b22e0a016f 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -230,6 +230,9 @@ app.use(function(error, req, res, next) { if (error instanceof Errors.NotFoundError) { logger.warn({ err: error, url: req.url }, 'not found error') return res.sendStatus(404) + } else if (error.code === 'EPIPE') { + // inspect container returns EPIPE when shutting down + return res.sendStatus(503) // send 503 Unavailable response } else { logger.error({ err: error, url: req.url }, 'server error') return res.sendStatus((error != null ? error.statusCode : undefined) || 500) From d3ff214b887c2e9cdc63abddf6edeedfd9627d04 Mon Sep 17 00:00:00 2001 From: Tim Alby <timothee.alby@gmail.com> Date: Thu, 11 Jun 2020 12:50:43 +0200 Subject: [PATCH 593/754] partially revert "[DockerRunner] fix metric incrementing and error logging" This reverts commits: - 2b2fcca39ce8dee0fdc0c342aa0d6c822592bcec - 9e82ab0890c5cc8c7fb95362c3f7edbcaad0cf29 - e3da458b376871c3ce72d6984d14bf1ee668b04b --- services/clsi/app/js/DockerRunner.js | 36 +++++------- .../clsi/test/unit/js/DockerRunnerTests.js | 58 ------------------- 2 files changed, 13 insertions(+), 81 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index c50087d018..3f90c81123 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -26,7 +26,6 @@ const LockManager = require('./DockerLockManager') const fs = require('fs') const Path = require('path') const _ = require('underscore') -const metrics = require('metrics-sharelatex') logger.info('using docker runner') @@ -412,28 +411,19 @@ module.exports = DockerRunner = { }) } ) - var inspectContainer = isRetry => - container.inspect(function(error, stats) { - if ((error != null ? error.statusCode : undefined) === 404) { - return createAndStartContainer() - } else if (error != null) { - if (error.message.match(/EPIPE/)) { - if (!isRetry) { - metrics.inc('container-inspect-epipe-retry') - return inspectContainer(true) - } - metrics.inc('container-inspect-epipe-error') - } - logger.err( - { container_name: name, error }, - 'unable to inspect container to start' - ) - return callback(error) - } else { - return startExistingContainer() - } - }) - inspectContainer(false) + return container.inspect(function(error, stats) { + if ((error != null ? error.statusCode : undefined) === 404) { + return createAndStartContainer() + } else if (error != null) { + logger.err( + { container_name: name, error }, + 'unable to inspect container to start' + ) + return callback(error) + } else { + return startExistingContainer() + } + }) }, attachToContainer(containerId, attachStreamHandler, attachStartCallback) { diff --git a/services/clsi/test/unit/js/DockerRunnerTests.js b/services/clsi/test/unit/js/DockerRunnerTests.js index 1e44daffb4..aa45e88da1 100644 --- a/services/clsi/test/unit/js/DockerRunnerTests.js +++ b/services/clsi/test/unit/js/DockerRunnerTests.js @@ -36,7 +36,6 @@ describe('DockerRunner', function() { 'logger-sharelatex': (this.logger = { log: sinon.stub(), error: sinon.stub(), - err: sinon.stub(), info: sinon.stub(), warn: sinon.stub() }), @@ -388,63 +387,6 @@ describe('DockerRunner', function() { }) }) - describe('when inspect always fails with EPIPE error', function() { - beforeEach(function() { - this.error = new Error('write EPIPE') - this.container.inspect = sinon.stub().yields(this.error) - this.container.start = sinon.stub().yields() - - this.DockerRunner.startContainer( - this.options, - this.volumes, - () => {}, - this.callback - ) - }) - - it('should retry once', function() { - sinon.assert.callOrder( - this.container.inspect, - this.container.inspect, - this.callback - ) - }) - - it('should call back with error', function() { - sinon.assert.calledWith(this.callback, this.error) - }) - }) - - describe('when inspect fails once with EPIPE error', function() { - beforeEach(function() { - this.container.inspect = sinon.stub() - this.container.inspect.onFirstCall().yields(new Error('write EPIPE')) - this.container.inspect.onSecondCall().yields() - this.container.start = sinon.stub().yields() - - this.DockerRunner.startContainer( - this.options, - this.volumes, - () => {}, - this.callback - ) - }) - - it('should retry once and start container', function() { - sinon.assert.callOrder( - this.container.inspect, - this.container.inspect, - this.DockerRunner.attachToContainer, - this.container.start, - this.callback - ) - }) - - it('should call back without error', function() { - sinon.assert.calledWith(this.callback, null) - }) - }) - describe('when the container does not exist', function() { beforeEach(function() { const exists = false From 5368630754fbef79d3a3da540a1a3faa1c9fb384 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 12 Jun 2020 15:15:27 +0100 Subject: [PATCH 594/754] check output file exists before running synctex --- services/clsi/app/js/CompileManager.js | 119 ++++++++++--------------- 1 file changed, 49 insertions(+), 70 deletions(-) diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js index 614c49aaf7..4b0cff6323 100644 --- a/services/clsi/app/js/CompileManager.js +++ b/services/clsi/app/js/CompileManager.js @@ -430,30 +430,18 @@ module.exports = CompileManager = { const compileDir = getCompileDir(project_id, user_id) const synctex_path = `${base_dir}/output.pdf` const command = ['code', synctex_path, file_path, line, column] - return fse.ensureDir(compileDir, function(error) { + CompileManager._runSynctex(project_id, user_id, command, function( + error, + stdout + ) { if (error != null) { - logger.err( - { error, project_id, user_id, file_name }, - 'error ensuring dir for sync from code' - ) return callback(error) } - return CompileManager._runSynctex(project_id, user_id, command, function( - error, - stdout - ) { - if (error != null) { - return callback(error) - } - logger.log( - { project_id, user_id, file_name, line, column, command, stdout }, - 'synctex code output' - ) - return callback( - null, - CompileManager._parseSynctexFromCodeOutput(stdout) - ) - }) + logger.log( + { project_id, user_id, file_name, line, column, command, stdout }, + 'synctex code output' + ) + return callback(null, CompileManager._parseSynctexFromCodeOutput(stdout)) }) }, @@ -466,53 +454,39 @@ module.exports = CompileManager = { const base_dir = Settings.path.synctexBaseDir(compileName) const synctex_path = `${base_dir}/output.pdf` const command = ['pdf', synctex_path, page, h, v] - return fse.ensureDir(compileDir, function(error) { + CompileManager._runSynctex(project_id, user_id, command, function( + error, + stdout + ) { if (error != null) { - logger.err( - { error, project_id, user_id, file_name }, - 'error ensuring dir for sync to code' - ) return callback(error) } - return CompileManager._runSynctex(project_id, user_id, command, function( - error, - stdout - ) { - if (error != null) { - return callback(error) - } - logger.log( - { project_id, user_id, page, h, v, stdout }, - 'synctex pdf output' - ) - return callback( - null, - CompileManager._parseSynctexFromPdfOutput(stdout, base_dir) - ) - }) + logger.log( + { project_id, user_id, page, h, v, stdout }, + 'synctex pdf output' + ) + return callback( + null, + CompileManager._parseSynctexFromPdfOutput(stdout, base_dir) + ) }) }, - _checkFileExists(path, callback) { + _checkFileExists(dir, filename, callback) { if (callback == null) { callback = function(error) {} } - const synctexDir = Path.dirname(path) - const synctexFile = Path.join(synctexDir, 'output.synctex.gz') - return fs.stat(synctexDir, function(error, stats) { + const file = Path.join(dir, filename) + return fs.stat(dir, function(error, stats) { if ((error != null ? error.code : undefined) === 'ENOENT') { - return callback( - new Errors.NotFoundError('called synctex with no output directory') - ) + return callback(new Errors.NotFoundError('no output directory')) } if (error != null) { return callback(error) } - return fs.stat(synctexFile, function(error, stats) { + return fs.stat(file, function(error, stats) { if ((error != null ? error.code : undefined) === 'ENOENT') { - return callback( - new Errors.NotFoundError('called synctex with no output file') - ) + return callback(new Errors.NotFoundError('no output file')) } if (error != null) { return callback(error) @@ -536,24 +510,29 @@ module.exports = CompileManager = { const directory = getCompileDir(project_id, user_id) const timeout = 60 * 1000 // increased to allow for large projects const compileName = getCompileName(project_id, user_id) - return CommandRunner.run( - compileName, - command, - directory, - Settings.clsi != null ? Settings.clsi.docker.image : undefined, - timeout, - {}, - function(error, output) { - if (error != null) { - logger.err( - { err: error, command, project_id, user_id }, - 'error running synctex' - ) - return callback(error) - } - return callback(null, output.stdout) + CompileManager._checkFileExists(directory, 'output.synctex.gz', error => { + if (error) { + return callback(error) } - ) + return CommandRunner.run( + compileName, + command, + directory, + Settings.clsi != null ? Settings.clsi.docker.image : undefined, + timeout, + {}, + function(error, output) { + if (error != null) { + logger.err( + { err: error, command, project_id, user_id }, + 'error running synctex' + ) + return callback(error) + } + return callback(null, output.stdout) + } + ) + }) }, _parseSynctexFromCodeOutput(output) { From f99023320df362ec82ce7bb12f51e37f59610636 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 12 Jun 2020 15:15:51 +0100 Subject: [PATCH 595/754] use json parsing in request --- services/clsi/test/acceptance/js/helpers/Client.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/services/clsi/test/acceptance/js/helpers/Client.js b/services/clsi/test/acceptance/js/helpers/Client.js index 9f430e3535..d65941671a 100644 --- a/services/clsi/test/acceptance/js/helpers/Client.js +++ b/services/clsi/test/acceptance/js/helpers/Client.js @@ -81,13 +81,14 @@ module.exports = Client = { file, line, column - } + }, + json: true }, (error, response, body) => { if (error != null) { return callback(error) } - return callback(null, JSON.parse(body)) + return callback(null, body) } ) }, @@ -103,13 +104,14 @@ module.exports = Client = { page, h, v - } + }, + json: true }, (error, response, body) => { if (error != null) { return callback(error) } - return callback(null, JSON.parse(body)) + return callback(null, body) } ) }, From ede70b6f993240a48bcb667f0a96627c40f8a21d Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Mon, 15 Jun 2020 09:52:21 +0100 Subject: [PATCH 596/754] migrate from underscore to lodash --- services/clsi/app/js/DockerRunner.js | 2 +- services/clsi/app/js/OutputCacheManager.js | 2 +- services/clsi/app/js/OutputFileOptimiser.js | 2 +- services/clsi/app/js/db.js | 2 +- services/clsi/package-lock.json | 10 ---------- services/clsi/package.json | 2 +- services/clsi/test/load/js/loadTest.js | 2 +- services/clsi/test/unit/js/DockerRunnerTests.js | 3 ++- services/clsi/test/unit/js/OutputFileOptimiserTests.js | 3 ++- 9 files changed, 10 insertions(+), 18 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index c50087d018..cb6ec2d2a7 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -25,7 +25,7 @@ const async = require('async') const LockManager = require('./DockerLockManager') const fs = require('fs') const Path = require('path') -const _ = require('underscore') +const _ = require('lodash') const metrics = require('metrics-sharelatex') logger.info('using docker runner') diff --git a/services/clsi/app/js/OutputCacheManager.js b/services/clsi/app/js/OutputCacheManager.js index c2c962f1bc..c0b0d6ed17 100644 --- a/services/clsi/app/js/OutputCacheManager.js +++ b/services/clsi/app/js/OutputCacheManager.js @@ -19,7 +19,7 @@ const fs = require('fs') const fse = require('fs-extra') const Path = require('path') const logger = require('logger-sharelatex') -const _ = require('underscore') +const _ = require('lodash') const Settings = require('settings-sharelatex') const crypto = require('crypto') diff --git a/services/clsi/app/js/OutputFileOptimiser.js b/services/clsi/app/js/OutputFileOptimiser.js index 80dadab483..e3b3e60e4e 100644 --- a/services/clsi/app/js/OutputFileOptimiser.js +++ b/services/clsi/app/js/OutputFileOptimiser.js @@ -19,7 +19,7 @@ const Path = require('path') const { spawn } = require('child_process') const logger = require('logger-sharelatex') const Metrics = require('./Metrics') -const _ = require('underscore') +const _ = require('lodash') module.exports = OutputFileOptimiser = { optimiseFile(src, dst, callback) { diff --git a/services/clsi/app/js/db.js b/services/clsi/app/js/db.js index c749af253c..15510ae34c 100644 --- a/services/clsi/app/js/db.js +++ b/services/clsi/app/js/db.js @@ -10,7 +10,7 @@ */ const Sequelize = require('sequelize') const Settings = require('settings-sharelatex') -const _ = require('underscore') +const _ = require('lodash') const logger = require('logger-sharelatex') const options = _.extend({ logging: false }, Settings.mysql.clsi) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index 7f896457cd..63d9c1e768 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -6640,11 +6640,6 @@ "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", "dev": true }, - "underscore": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.2.tgz", - "integrity": "sha512-D39qtimx0c1fI3ya1Lnhk3E9nONswSKhnffBI0gME9C99fYOkNi04xs8K6pePLhvl1frbDemkaBQ5ikWllR2HQ==" - }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -6834,11 +6829,6 @@ "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==" }, - "when": { - "version": "3.7.8", - "resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz", - "integrity": "sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I=" - }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/services/clsi/package.json b/services/clsi/package.json index e57bfa5518..df30ef77df 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -27,6 +27,7 @@ "fs-extra": "^8.1.0", "heapdump": "^0.3.15", "lockfile": "^1.0.4", + "lodash": "^4.17.15", "logger-sharelatex": "^1.9.1", "lynx": "0.2.0", "metrics-sharelatex": "^2.6.0", @@ -35,7 +36,6 @@ "sequelize": "^5.21.5", "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.1.0", "sqlite3": "^4.1.1", - "underscore": "^1.9.2", "v8-profiler-node8": "^6.1.1", "wrench": "~1.5.9" }, diff --git a/services/clsi/test/load/js/loadTest.js b/services/clsi/test/load/js/loadTest.js index ff9850efd8..c4116a2a30 100644 --- a/services/clsi/test/load/js/loadTest.js +++ b/services/clsi/test/load/js/loadTest.js @@ -13,7 +13,7 @@ const request = require('request') const Settings = require('settings-sharelatex') const async = require('async') const fs = require('fs') -const _ = require('underscore') +const _ = require('lodash') const concurentCompiles = 5 const totalCompiles = 50 diff --git a/services/clsi/test/unit/js/DockerRunnerTests.js b/services/clsi/test/unit/js/DockerRunnerTests.js index 1e44daffb4..b761d20340 100644 --- a/services/clsi/test/unit/js/DockerRunnerTests.js +++ b/services/clsi/test/unit/js/DockerRunnerTests.js @@ -70,7 +70,8 @@ describe('DockerRunner', function() { return runner(callback) } } - } + }, + globals: { Math } // used by lodash }) this.Docker = Docker this.getContainer = Docker.prototype.getContainer diff --git a/services/clsi/test/unit/js/OutputFileOptimiserTests.js b/services/clsi/test/unit/js/OutputFileOptimiserTests.js index b4983bf0f8..669044142c 100644 --- a/services/clsi/test/unit/js/OutputFileOptimiserTests.js +++ b/services/clsi/test/unit/js/OutputFileOptimiserTests.js @@ -30,7 +30,8 @@ describe('OutputFileOptimiser', function() { child_process: { spawn: (this.spawn = sinon.stub()) }, 'logger-sharelatex': { log: sinon.stub(), warn: sinon.stub() }, './Metrics': {} - } + }, + globals: { Math } // used by lodash }) this.directory = '/test/dir' return (this.callback = sinon.stub()) From 262ea01911b375d2c25099aa2e99fff647171bb7 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 12 Jun 2020 15:16:14 +0100 Subject: [PATCH 597/754] add acceptance test for synctex when project/file does not exist --- .../clsi/test/acceptance/js/SynctexTests.js | 102 +++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/services/clsi/test/acceptance/js/SynctexTests.js b/services/clsi/test/acceptance/js/SynctexTests.js index 4860c6040c..1140f3fade 100644 --- a/services/clsi/test/acceptance/js/SynctexTests.js +++ b/services/clsi/test/acceptance/js/SynctexTests.js @@ -69,7 +69,7 @@ Hello world }) }) - return describe('from pdf to code', function() { + describe('from pdf to code', function() { return it('should return the correct location', function(done) { return Client.syncFromPdf( this.project_id, @@ -88,4 +88,104 @@ Hello world ) }) }) + + describe('when the project directory is not available', function() { + before(function() { + this.other_project_id = Client.randomId() + }) + describe('from code to pdf', function() { + it('should return a 404 response', function(done) { + return Client.syncFromCode( + this.other_project_id, + 'main.tex', + 3, + 5, + (error, body) => { + if (error != null) { + throw error + } + expect(body).to.equal('Not Found') + return done() + } + ) + }) + }) + describe('from pdf to code', function() { + it('should return a 404 response', function(done) { + return Client.syncFromPdf( + this.other_project_id, + 1, + 100, + 200, + (error, body) => { + if (error != null) { + throw error + } + expect(body).to.equal('Not Found') + return done() + } + ) + }) + }) + }) + + describe('when the synctex file is not available', function() { + before(function(done) { + this.broken_project_id = Client.randomId() + const content = 'this is not valid tex' // not a valid tex file + this.request = { + resources: [ + { + path: 'main.tex', + content + } + ] + } + Client.compile( + this.broken_project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + return done() + } + ) + }) + + describe('from code to pdf', function() { + it('should return a 404 response', function(done) { + return Client.syncFromCode( + this.broken_project_id, + 'main.tex', + 3, + 5, + (error, body) => { + if (error != null) { + throw error + } + expect(body).to.equal('Not Found') + return done() + } + ) + }) + }) + describe('from pdf to code', function() { + it('should return a 404 response', function(done) { + return Client.syncFromPdf( + this.broken_project_id, + 1, + 100, + 200, + (error, body) => { + if (error != null) { + throw error + } + expect(body).to.equal('Not Found') + return done() + } + ) + }) + }) + }) }) From 0914908676fb16e169a562a2535a091f53784553 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Mon, 15 Jun 2020 11:06:54 +0100 Subject: [PATCH 598/754] downgrade NotFoundError log-level --- services/clsi/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app.js b/services/clsi/app.js index b22e0a016f..ff5d70e7bd 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -228,7 +228,7 @@ app.get('/smoke_test_force', (req, res) => smokeTest.sendNewResult(res)) app.use(function(error, req, res, next) { if (error instanceof Errors.NotFoundError) { - logger.warn({ err: error, url: req.url }, 'not found error') + logger.log({ err: error, url: req.url }, 'not found error') return res.sendStatus(404) } else if (error.code === 'EPIPE') { // inspect container returns EPIPE when shutting down From 2ce03f055468e9133e7d4452c8a8caa5eabce2fd Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Thu, 11 Jun 2020 16:01:44 +0100 Subject: [PATCH 599/754] add initial compileGroup support --- services/clsi/app/js/CompileManager.js | 7 +- services/clsi/app/js/DockerRunner.js | 40 +++++++++++- services/clsi/app/js/LatexRunner.js | 14 +++- services/clsi/app/js/LocalCommandRunner.js | 11 +++- services/clsi/app/js/RequestParser.js | 12 +++- services/clsi/config/settings.defaults.js | 26 ++++++++ .../clsi/test/unit/js/CompileManagerTests.js | 18 ++++-- .../clsi/test/unit/js/DockerRunnerTests.js | 64 +++++++++++++++++++ .../clsi/test/unit/js/LatexRunnerTests.js | 9 ++- 9 files changed, 183 insertions(+), 18 deletions(-) diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js index 614c49aaf7..8ca80d8e8f 100644 --- a/services/clsi/app/js/CompileManager.js +++ b/services/clsi/app/js/CompileManager.js @@ -199,7 +199,8 @@ module.exports = CompileManager = { timeout: request.timeout, image: request.imageName, flags: request.flags, - environment: env + environment: env, + compileGroup: request.compileGroup }, function(error, output, stats, timings) { // request was for validation only @@ -536,6 +537,7 @@ module.exports = CompileManager = { const directory = getCompileDir(project_id, user_id) const timeout = 60 * 1000 // increased to allow for large projects const compileName = getCompileName(project_id, user_id) + const compileGroup = 'synctex' return CommandRunner.run( compileName, command, @@ -543,6 +545,7 @@ module.exports = CompileManager = { Settings.clsi != null ? Settings.clsi.docker.image : undefined, timeout, {}, + compileGroup, function(error, output) { if (error != null) { logger.err( @@ -606,6 +609,7 @@ module.exports = CompileManager = { const compileDir = getCompileDir(project_id, user_id) const timeout = 60 * 1000 const compileName = getCompileName(project_id, user_id) + const compileGroup = 'wordcount' return fse.ensureDir(compileDir, function(error) { if (error != null) { logger.err( @@ -621,6 +625,7 @@ module.exports = CompileManager = { image, timeout, {}, + compileGroup, function(error) { if (error != null) { return callback(error) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index cb6ec2d2a7..2a6330f866 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -45,7 +45,16 @@ module.exports = DockerRunner = { ERR_EXITED: new Error('exited'), ERR_TIMED_OUT: new Error('container timed out'), - run(project_id, command, directory, image, timeout, environment, callback) { + run( + project_id, + command, + directory, + image, + timeout, + environment, + compileGroup, + callback + ) { let name if (callback == null) { callback = function(error, output) {} @@ -88,7 +97,8 @@ module.exports = DockerRunner = { image, volumes, timeout, - environment + environment, + compileGroup ) const fingerprint = DockerRunner._fingerprintContainer(options) options.name = name = `project-${project_id}-${fingerprint}` @@ -224,7 +234,14 @@ module.exports = DockerRunner = { ) }, - _getContainerOptions(command, image, volumes, timeout, environment) { + _getContainerOptions( + command, + image, + volumes, + timeout, + environment, + compileGroup + ) { let m, year let key, value, hostVol, dockerVol const timeoutInSeconds = timeout / 1000 @@ -311,6 +328,23 @@ module.exports = DockerRunner = { options.HostConfig.Runtime = Settings.clsi.docker.runtime } + if (Settings.clsi.docker.Readonly) { + options.HostConfig.ReadonlyRootfs = true + options.HostConfig.Tmpfs = { '/tmp': 'rw,noexec,nosuid,size=65536k' } + } + + // Allow per-compile group overriding of individual settings + if ( + Settings.clsi.docker.compileGroupConfig && + Settings.clsi.docker.compileGroupConfig[compileGroup] + ) { + const override = Settings.clsi.docker.compileGroupConfig[compileGroup] + let key + for (key in override) { + _.set(options, key, override[key]) + } + } + return options }, diff --git a/services/clsi/app/js/LatexRunner.js b/services/clsi/app/js/LatexRunner.js index c3edb29e24..6d1591a213 100644 --- a/services/clsi/app/js/LatexRunner.js +++ b/services/clsi/app/js/LatexRunner.js @@ -36,7 +36,8 @@ module.exports = LatexRunner = { timeout, image, environment, - flags + flags, + compileGroup } = options if (!compiler) { compiler = 'pdflatex' @@ -46,7 +47,15 @@ module.exports = LatexRunner = { } // milliseconds logger.log( - { directory, compiler, timeout, mainFile, environment, flags }, + { + directory, + compiler, + timeout, + mainFile, + environment, + flags, + compileGroup + }, 'starting compile' ) @@ -79,6 +88,7 @@ module.exports = LatexRunner = { image, timeout, environment, + compileGroup, function(error, output) { delete ProcessTable[id] if (error != null) { diff --git a/services/clsi/app/js/LocalCommandRunner.js b/services/clsi/app/js/LocalCommandRunner.js index ccaf50784a..14a19986e2 100644 --- a/services/clsi/app/js/LocalCommandRunner.js +++ b/services/clsi/app/js/LocalCommandRunner.js @@ -20,7 +20,16 @@ const logger = require('logger-sharelatex') logger.info('using standard command runner') module.exports = CommandRunner = { - run(project_id, command, directory, image, timeout, environment, callback) { + run( + project_id, + command, + directory, + image, + timeout, + environment, + compileGroup, + callback + ) { let key, value if (callback == null) { callback = function(error) {} diff --git a/services/clsi/app/js/RequestParser.js b/services/clsi/app/js/RequestParser.js index acfdc6689d..f2be556af1 100644 --- a/services/clsi/app/js/RequestParser.js +++ b/services/clsi/app/js/RequestParser.js @@ -74,7 +74,17 @@ module.exports = RequestParser = { default: [], type: 'object' }) - + if (settings.allowedCompileGroups) { + response.compileGroup = this._parseAttribute( + 'compileGroup', + compile.options.compileGroup, + { + validValues: settings.allowedCompileGroups, + default: '', + type: 'string' + } + ) + } // The syncType specifies whether the request contains all // resources (full) or only those resources to be updated // in-place (incremental). diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index 9e44e1491d..f3bb73b007 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -63,6 +63,17 @@ module.exports = { } } +if (process.env.ALLOWED_COMPILE_GROUPS) { + try { + module.exports.allowedCompileGroups = process.env.ALLOWED_COMPILE_GROUPS.split( + ' ' + ) + } catch (error) { + console.error(error, 'could not apply allowed compile group setting') + process.exit(1) + } +} + if (process.env.DOCKER_RUNNER) { let seccompProfilePath module.exports.clsi = { @@ -82,6 +93,21 @@ if (process.env.DOCKER_RUNNER) { checkProjectsIntervalMs: 10 * 60 * 1000 } + try { + // Override individual docker settings using path-based keys, e.g.: + // compileGroupDockerConfigs = { + // priority: { 'HostConfig.CpuShares': 100 } + // beta: { 'dotted.path.here', 'value'} + // } + const compileGroupConfig = JSON.parse( + process.env.COMPILE_GROUP_DOCKER_CONFIGS || '{}' + ) + module.exports.clsi.docker.compileGroupConfig = compileGroupConfig + } catch (error) { + console.error(error, 'could not apply compile group docker configs') + process.exit(1) + } + try { seccompProfilePath = Path.resolve(__dirname, '../seccomp/clsi-profile.json') module.exports.clsi.docker.seccomp_profile = JSON.stringify( diff --git a/services/clsi/test/unit/js/CompileManagerTests.js b/services/clsi/test/unit/js/CompileManagerTests.js index 74a0a47fff..90d572b2a2 100644 --- a/services/clsi/test/unit/js/CompileManagerTests.js +++ b/services/clsi/test/unit/js/CompileManagerTests.js @@ -160,7 +160,8 @@ describe('CompileManager', function() { compiler: (this.compiler = 'pdflatex'), timeout: (this.timeout = 42000), imageName: (this.image = 'example.com/image'), - flags: (this.flags = ['-file-line-error']) + flags: (this.flags = ['-file-line-error']), + compileGroup: (this.compileGroup = 'compile-group') } this.env = {} this.Settings.compileDir = 'compiles' @@ -199,7 +200,8 @@ describe('CompileManager', function() { timeout: this.timeout, image: this.image, flags: this.flags, - environment: this.env + environment: this.env, + compileGroup: this.compileGroup }) .should.equal(true) }) @@ -253,7 +255,8 @@ describe('CompileManager', function() { CHKTEX_OPTIONS: '-nall -e9 -e10 -w15 -w16', CHKTEX_EXIT_ON_ERROR: 1, CHKTEX_ULIMIT_OPTIONS: '-t 5 -v 64000' - } + }, + compileGroup: this.compileGroup }) .should.equal(true) }) @@ -275,7 +278,8 @@ describe('CompileManager', function() { timeout: this.timeout, image: this.image, flags: this.flags, - environment: this.env + environment: this.env, + compileGroup: this.compileGroup }) .should.equal(true) }) @@ -384,7 +388,7 @@ describe('CompileManager', function() { this.stdout = `NODE\t${this.page}\t${this.h}\t${this.v}\t${this.width}\t${this.height}\n` this.CommandRunner.run = sinon .stub() - .callsArgWith(6, null, { stdout: this.stdout }) + .callsArgWith(7, null, { stdout: this.stdout }) return this.CompileManager.syncFromCode( this.project_id, this.user_id, @@ -443,7 +447,7 @@ describe('CompileManager', function() { this.stdout = `NODE\t${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/${this.file_name}\t${this.line}\t${this.column}\n` this.CommandRunner.run = sinon .stub() - .callsArgWith(6, null, { stdout: this.stdout }) + .callsArgWith(7, null, { stdout: this.stdout }) return this.CompileManager.syncFromPdf( this.project_id, this.user_id, @@ -485,7 +489,7 @@ describe('CompileManager', function() { return describe('wordcount', function() { beforeEach(function() { - this.CommandRunner.run = sinon.stub().callsArg(6) + this.CommandRunner.run = sinon.stub().callsArg(7) this.fs.readFile = sinon .stub() .callsArgWith( diff --git a/services/clsi/test/unit/js/DockerRunnerTests.js b/services/clsi/test/unit/js/DockerRunnerTests.js index b761d20340..2e2ffec638 100644 --- a/services/clsi/test/unit/js/DockerRunnerTests.js +++ b/services/clsi/test/unit/js/DockerRunnerTests.js @@ -87,6 +87,7 @@ describe('DockerRunner', function() { this.project_id = 'project-id-123' this.volumes = { '/local/compile/directory': '/compile' } this.Settings.clsi.docker.image = this.defaultImage = 'default-image' + this.compileGroup = 'compile-group' return (this.Settings.clsi.docker.env = { PATH: 'mock-path' }) }) @@ -123,6 +124,7 @@ describe('DockerRunner', function() { this.image, this.timeout, this.env, + this.compileGroup, (err, output) => { this.callback(err, output) return done() @@ -172,6 +174,7 @@ describe('DockerRunner', function() { this.image, this.timeout, this.env, + this.compileGroup, this.callback ) }) @@ -220,6 +223,7 @@ describe('DockerRunner', function() { this.image, this.timeout, this.env, + this.compileGroup, this.callback ) }) @@ -253,6 +257,7 @@ describe('DockerRunner', function() { null, this.timeout, this.env, + this.compileGroup, this.callback ) }) @@ -282,6 +287,7 @@ describe('DockerRunner', function() { this.image, this.timeout, this.env, + this.compileGroup, this.callback ) }) @@ -293,6 +299,64 @@ describe('DockerRunner', function() { }) }) + describe('run with _getOptions', function() { + beforeEach(function(done) { + // this.DockerRunner._getContainerOptions = sinon + // .stub() + // .returns((this.options = { mockoptions: 'foo' })) + this.DockerRunner._fingerprintContainer = sinon + .stub() + .returns((this.fingerprint = 'fingerprint')) + + this.name = `project-${this.project_id}-${this.fingerprint}` + + this.command = ['mock', 'command', '--outdir=$COMPILE_DIR'] + this.command_with_dir = ['mock', 'command', '--outdir=/compile'] + this.timeout = 42000 + return done() + }) + + describe('when a compile group config is set', function() { + beforeEach(function() { + this.Settings.clsi.docker.compileGroupConfig = { + 'compile-group': { + 'HostConfig.newProperty': 'new-property' + }, + 'other-group': { otherProperty: 'other-property' } + } + this.DockerRunner._runAndWaitForContainer = sinon + .stub() + .callsArgWith(3, null, (this.output = 'mock-output')) + return this.DockerRunner.run( + this.project_id, + this.command, + this.directory, + this.image, + this.timeout, + this.env, + this.compileGroup, + this.callback + ) + }) + + it('should set the docker options for the compile group', function() { + const options = this.DockerRunner._runAndWaitForContainer.lastCall + .args[0] + return expect(options.HostConfig).to.deep.include({ + Binds: ['/local/compile/directory:/compile:rw'], + LogConfig: { Type: 'none', Config: {} }, + CapDrop: 'ALL', + SecurityOpt: ['no-new-privileges'], + newProperty: 'new-property' + }) + }) + + return it('should call the callback', function() { + return this.callback.calledWith(null, this.output).should.equal(true) + }) + }) + }) + describe('_runAndWaitForContainer', function() { beforeEach(function() { this.options = { mockoptions: 'foo', name: (this.name = 'mock-name') } diff --git a/services/clsi/test/unit/js/LatexRunnerTests.js b/services/clsi/test/unit/js/LatexRunnerTests.js index b112b17006..f480bc8245 100644 --- a/services/clsi/test/unit/js/LatexRunnerTests.js +++ b/services/clsi/test/unit/js/LatexRunnerTests.js @@ -48,6 +48,7 @@ describe('LatexRunner', function() { this.mainFile = 'main-file.tex' this.compiler = 'pdflatex' this.image = 'example.com/image' + this.compileGroup = 'compile-group' this.callback = sinon.stub() this.project_id = 'project-id-123' return (this.env = { foo: '123' }) @@ -55,7 +56,7 @@ describe('LatexRunner', function() { return describe('runLatex', function() { beforeEach(function() { - return (this.CommandRunner.run = sinon.stub().callsArgWith(6, null, { + return (this.CommandRunner.run = sinon.stub().callsArgWith(7, null, { stdout: 'this is stdout', stderr: 'this is stderr' })) @@ -71,7 +72,8 @@ describe('LatexRunner', function() { compiler: this.compiler, timeout: (this.timeout = 42000), image: this.image, - environment: this.env + environment: this.env, + compileGroup: this.compileGroup }, this.callback ) @@ -85,7 +87,8 @@ describe('LatexRunner', function() { this.directory, this.image, this.timeout, - this.env + this.env, + this.compileGroup ) .should.equal(true) }) From 85f4f348dd3605f9aab9f7a7d79d6aa27175ba5a Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Mon, 15 Jun 2020 15:49:38 +0100 Subject: [PATCH 600/754] add default settings to remove wordcount and synctex containers --- services/clsi/config/settings.defaults.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index f3bb73b007..3e115e8bf5 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -102,7 +102,15 @@ if (process.env.DOCKER_RUNNER) { const compileGroupConfig = JSON.parse( process.env.COMPILE_GROUP_DOCKER_CONFIGS || '{}' ) - module.exports.clsi.docker.compileGroupConfig = compileGroupConfig + // Automatically clean up wordcount and synctex containers + const defaultCompileGroupConfig = { + wordcount: { 'HostConfig.AutoRemove': true }, + synctex: { 'HostConfig.AutoRemove': true } + } + module.exports.clsi.docker.compileGroupConfig = Object.assign( + defaultCompileGroupConfig, + compileGroupConfig + ) } catch (error) { console.error(error, 'could not apply compile group docker configs') process.exit(1) From 2eb43272565c246f47af933c75e90ad36679e446 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Tue, 16 Jun 2020 08:45:53 +0100 Subject: [PATCH 601/754] fix format --- services/clsi/app/js/DockerRunner.js | 1 - 1 file changed, 1 deletion(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index e75b997d21..21ea97f0b2 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -27,7 +27,6 @@ const fs = require('fs') const Path = require('path') const _ = require('lodash') - logger.info('using docker runner') const usingSiblingContainers = () => From 22480f1a52bce7eed41b95ea13dc4da18206ab93 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Thu, 18 Jun 2020 09:54:18 +0100 Subject: [PATCH 602/754] handle EPIPE errors in CompileController --- services/clsi/app/js/CompileController.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/clsi/app/js/CompileController.js b/services/clsi/app/js/CompileController.js index e146b62c0a..c76d0d50e2 100644 --- a/services/clsi/app/js/CompileController.js +++ b/services/clsi/app/js/CompileController.js @@ -55,6 +55,10 @@ module.exports = CompileController = { } else if (error instanceof Errors.FilesOutOfSyncError) { code = 409 // Http 409 Conflict status = 'retry' + } else if (error && error.code === 'EPIPE') { + // docker returns EPIPE when shutting down + code = 503 // send 503 Unavailable response + status = 'unavailable' } else if (error != null ? error.terminated : undefined) { status = 'terminated' } else if (error != null ? error.validate : undefined) { From f8ca90639359e5a4ffa9dd804cc450f4118a9739 Mon Sep 17 00:00:00 2001 From: Miguel Serrano <mserranom@gmail.com> Date: Thu, 25 Jun 2020 12:31:10 +0200 Subject: [PATCH 603/754] Fixed NPE when Settings.clsi is defined but Settings.clsi.docker is not --- services/clsi/app/js/CompileManager.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js index 8cd7797a98..73db649948 100644 --- a/services/clsi/app/js/CompileManager.js +++ b/services/clsi/app/js/CompileManager.js @@ -520,7 +520,9 @@ module.exports = CompileManager = { compileName, command, directory, - Settings.clsi != null ? Settings.clsi.docker.image : undefined, + Settings.clsi && Settings.clsi.docker + ? Settings.clsi.docker.image + : undefined, timeout, {}, compileGroup, From 8846efe7ce08d39906db6b7a54321b998bc77a89 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Fri, 26 Jun 2020 12:29:49 +0100 Subject: [PATCH 604/754] [misc] RequestParser: restrict imageName to an allow list and add tests --- services/clsi/app/js/RequestParser.js | 2 +- services/clsi/config/settings.defaults.js | 10 +++ services/clsi/docker-compose-config.yml | 2 + .../test/acceptance/js/AllowedImageNames.js | 73 +++++++++++++++++++ .../clsi/test/unit/js/RequestParserTests.js | 38 ++++++++++ 5 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 services/clsi/test/acceptance/js/AllowedImageNames.js diff --git a/services/clsi/app/js/RequestParser.js b/services/clsi/app/js/RequestParser.js index f2be556af1..ecc92c0836 100644 --- a/services/clsi/app/js/RequestParser.js +++ b/services/clsi/app/js/RequestParser.js @@ -61,7 +61,7 @@ module.exports = RequestParser = { response.imageName = this._parseAttribute( 'imageName', compile.options.imageName, - { type: 'string' } + { type: 'string', validValues: settings.allowedImageNamesFlat } ) response.draft = this._parseAttribute('draft', compile.options.draft, { default: false, diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index 3e115e8bf5..fd2c28af1e 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -73,6 +73,16 @@ if (process.env.ALLOWED_COMPILE_GROUPS) { process.exit(1) } } +if (process.env.ALLOWED_IMAGE_NAMES_FLAT) { + try { + module.exports.allowedImageNamesFlat = process.env.ALLOWED_IMAGE_NAMES_FLAT.split( + ' ' + ) + } catch (error) { + console.error(error, 'could not apply allowed image names setting') + process.exit(1) + } +} if (process.env.DOCKER_RUNNER) { let seccompProfilePath diff --git a/services/clsi/docker-compose-config.yml b/services/clsi/docker-compose-config.yml index 392f8feae9..d1b72ee216 100644 --- a/services/clsi/docker-compose-config.yml +++ b/services/clsi/docker-compose-config.yml @@ -3,6 +3,7 @@ version: "2.3" services: dev: environment: + ALLOWED_IMAGE_NAMES_FLAT: "quay.io/sharelatex/texlive-full:2017.1" TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2017.1 TEXLIVE_IMAGE_USER: "tex" SHARELATEX_CONFIG: /app/config/settings.defaults.coffee @@ -18,6 +19,7 @@ services: ci: environment: + ALLOWED_IMAGE_NAMES_FLAT: ${TEXLIVE_IMAGE} TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2017.1 TEXLIVE_IMAGE_USER: "tex" SHARELATEX_CONFIG: /app/config/settings.defaults.coffee diff --git a/services/clsi/test/acceptance/js/AllowedImageNames.js b/services/clsi/test/acceptance/js/AllowedImageNames.js new file mode 100644 index 0000000000..1ea0a36b64 --- /dev/null +++ b/services/clsi/test/acceptance/js/AllowedImageNames.js @@ -0,0 +1,73 @@ +const Client = require('./helpers/Client') +const ClsiApp = require('./helpers/ClsiApp') +const { expect } = require('chai') + +describe('AllowedImageNames', function() { + beforeEach(function(done) { + this.project_id = Client.randomId() + this.request = { + options: { + imageName: undefined + }, + resources: [ + { + path: 'main.tex', + content: `\ +\\documentclass{article} +\\begin{document} +Hello world +\\end{document}\ +` + } + ] + } + ClsiApp.ensureRunning(done) + }) + + describe('with a valid name', function() { + beforeEach(function(done) { + this.request.options.imageName = process.env.TEXLIVE_IMAGE + + Client.compile(this.project_id, this.request, (error, res, body) => { + this.error = error + this.res = res + this.body = body + done(error) + }) + }) + it('should return success', function() { + expect(this.res.statusCode).to.equal(200) + }) + + it('should return a PDF', function() { + let pdf + try { + pdf = Client.getOutputFile(this.body, 'pdf') + } catch (e) {} + expect(pdf).to.exist + }) + }) + + describe('with an invalid name', function() { + beforeEach(function(done) { + this.request.options.imageName = 'something/evil:1337' + Client.compile(this.project_id, this.request, (error, res, body) => { + this.error = error + this.res = res + this.body = body + done(error) + }) + }) + it('should return non success', function() { + expect(this.res.statusCode).to.not.equal(200) + }) + + it('should not return a PDF', function() { + let pdf + try { + pdf = Client.getOutputFile(this.body, 'pdf') + } catch (e) {} + expect(pdf).to.not.exist + }) + }) +}) diff --git a/services/clsi/test/unit/js/RequestParserTests.js b/services/clsi/test/unit/js/RequestParserTests.js index e2d8b02610..16955bb670 100644 --- a/services/clsi/test/unit/js/RequestParserTests.js +++ b/services/clsi/test/unit/js/RequestParserTests.js @@ -114,6 +114,44 @@ describe('RequestParser', function() { }) }) + describe('when image restrictions are present', function() { + beforeEach(function() { + this.settings.allowedImageNamesFlat = ['repo/name:tag1', 'repo/name:tag2'] + }) + + describe('with imageName set to something invalid', function() { + beforeEach(function() { + const request = this.validRequest + request.compile.options.imageName = 'something/different:latest' + this.RequestParser.parse(request, (error, data) => { + this.error = error + this.data = data + }) + }) + + it('should throw an error for imageName', function() { + expect(String(this.error)).to.include( + 'imageName attribute should be one of' + ) + }) + }) + + describe('with imageName set to something valid', function() { + beforeEach(function() { + const request = this.validRequest + request.compile.options.imageName = 'repo/name:tag1' + this.RequestParser.parse(request, (error, data) => { + this.error = error + this.data = data + }) + }) + + it('should set the imageName', function() { + this.data.imageName.should.equal('repo/name:tag1') + }) + }) + }) + describe('with flags set', function() { beforeEach(function() { this.validRequest.compile.options.flags = ['-file-line-error'] From c857371fede8936a63be2f6d2cef7f3de496b1b6 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Fri, 26 Jun 2020 13:17:45 +0100 Subject: [PATCH 605/754] [misc] wordcount: restrict image to an allow list and add tests --- services/clsi/app/js/CompileController.js | 7 +++ .../test/acceptance/js/AllowedImageNames.js | 29 +++++++++++++ .../clsi/test/acceptance/js/helpers/Client.js | 9 ++++ .../test/unit/js/CompileControllerTests.js | 43 ++++++++++++++++++- 4 files changed, 86 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/js/CompileController.js b/services/clsi/app/js/CompileController.js index c76d0d50e2..857d0504a3 100644 --- a/services/clsi/app/js/CompileController.js +++ b/services/clsi/app/js/CompileController.js @@ -218,6 +218,13 @@ module.exports = CompileController = { const { project_id } = req.params const { user_id } = req.params const { image } = req.query + if ( + image && + Settings.allowedImageNamesFlat && + Settings.allowedImageNamesFlat.indexOf(image) === -1 + ) { + return res.status(400).send('invalid image') + } logger.log({ image, file, project_id }, 'word count request') return CompileManager.wordcount(project_id, user_id, file, image, function( diff --git a/services/clsi/test/acceptance/js/AllowedImageNames.js b/services/clsi/test/acceptance/js/AllowedImageNames.js index 1ea0a36b64..a9b3996e1e 100644 --- a/services/clsi/test/acceptance/js/AllowedImageNames.js +++ b/services/clsi/test/acceptance/js/AllowedImageNames.js @@ -70,4 +70,33 @@ Hello world expect(pdf).to.not.exist }) }) + + describe('wordcount', function() { + beforeEach(function(done) { + Client.compile(this.project_id, this.request, done) + }) + it('should error out with an invalid imageName', function() { + Client.wordcountWithImage( + this.project_id, + 'main.tex', + 'something/evil:1337', + (error, result) => { + expect(String(error)).to.include('statusCode=400') + } + ) + }) + + it('should produce a texcout a valid imageName', function() { + Client.wordcountWithImage( + this.project_id, + 'main.tex', + process.env.TEXLIVE_IMAGE, + (error, result) => { + expect(error).to.not.exist + expect(result).to.exist + expect(result.texcount).to.exist + } + ) + }) + }) }) diff --git a/services/clsi/test/acceptance/js/helpers/Client.js b/services/clsi/test/acceptance/js/helpers/Client.js index d65941671a..c940e305f8 100644 --- a/services/clsi/test/acceptance/js/helpers/Client.js +++ b/services/clsi/test/acceptance/js/helpers/Client.js @@ -189,6 +189,11 @@ module.exports = Client = { }, wordcount(project_id, file, callback) { + const image = undefined + Client.wordcountWithImage(project_id, file, image, callback) + }, + + wordcountWithImage(project_id, file, image, callback) { if (callback == null) { callback = function(error, pdfPositions) {} } @@ -196,6 +201,7 @@ module.exports = Client = { { url: `${this.host}/project/${project_id}/wordcount`, qs: { + image, file } }, @@ -203,6 +209,9 @@ module.exports = Client = { if (error != null) { return callback(error) } + if (response.statusCode !== 200) { + return callback(new Error(`statusCode=${response.statusCode}`)) + } return callback(null, JSON.parse(body)) } ) diff --git a/services/clsi/test/unit/js/CompileControllerTests.js b/services/clsi/test/unit/js/CompileControllerTests.js index 4480c8804e..f3f3fa4ad8 100644 --- a/services/clsi/test/unit/js/CompileControllerTests.js +++ b/services/clsi/test/unit/js/CompileControllerTests.js @@ -12,6 +12,7 @@ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') require('chai').should() +const { expect } = require('chai') const modulePath = require('path').join( __dirname, '../../../app/js/CompileController' @@ -287,21 +288,59 @@ describe('CompileController', function() { this.CompileManager.wordcount = sinon .stub() .callsArgWith(4, null, (this.texcount = ['mock-texcount'])) - return this.CompileController.wordcount(this.req, this.res, this.next) }) it('should return the word count of a file', function() { + this.CompileController.wordcount(this.req, this.res, this.next) return this.CompileManager.wordcount .calledWith(this.project_id, undefined, this.file, this.image) .should.equal(true) }) - return it('should return the texcount info', function() { + it('should return the texcount info', function() { + this.CompileController.wordcount(this.req, this.res, this.next) return this.res.json .calledWith({ texcount: this.texcount }) .should.equal(true) }) + + describe('when allowedImageNamesFlat is set', function() { + beforeEach(function() { + this.Settings.allowedImageNamesFlat = [ + 'repo/image:tag1', + 'repo/image:tag2' + ] + this.res.send = sinon.stub() + this.res.status = sinon.stub().returns({ send: this.res.send }) + }) + + describe('with an invalid image', function() { + beforeEach(function() { + this.req.query.image = 'something/evil:1337' + this.CompileController.wordcount(this.req, this.res, this.next) + }) + it('should return a 400', function() { + expect(this.res.status.calledWith(400)).to.equal(true) + }) + it('should not run the query', function() { + expect(this.CompileManager.wordcount.called).to.equal(false) + }) + }) + + describe('with a valid image', function() { + beforeEach(function() { + this.req.query.image = 'repo/image:tag1' + this.CompileController.wordcount(this.req, this.res, this.next) + }) + it('should not return a 400', function() { + expect(this.res.status.calledWith(400)).to.equal(false) + }) + it('should run the query', function() { + expect(this.CompileManager.wordcount.called).to.equal(true) + }) + }) + }) }) }) From aa02df7b81b2b761bf85cbb54a05f883d726f503 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 30 Jun 2020 12:00:18 +0100 Subject: [PATCH 606/754] [misc] apply review feedback - move setting into clsi.docker namespace - rename the variable for images to allowedImages / ALLOWED_IMAGES - add an additional check for the image name into the DockerRunner Co-Authored-By: Brian Gough <brian.gough@overleaf.com> --- services/clsi/app/js/CompileController.js | 6 +- services/clsi/app/js/DockerRunner.js | 7 +++ services/clsi/app/js/RequestParser.js | 8 ++- services/clsi/config/settings.defaults.js | 21 +++---- services/clsi/docker-compose-config.yml | 4 +- .../test/unit/js/CompileControllerTests.js | 5 +- .../clsi/test/unit/js/DockerRunnerTests.js | 58 ++++++++++++++++++- .../clsi/test/unit/js/RequestParserTests.js | 6 +- 8 files changed, 96 insertions(+), 19 deletions(-) diff --git a/services/clsi/app/js/CompileController.js b/services/clsi/app/js/CompileController.js index 857d0504a3..9c18830de8 100644 --- a/services/clsi/app/js/CompileController.js +++ b/services/clsi/app/js/CompileController.js @@ -220,8 +220,10 @@ module.exports = CompileController = { const { image } = req.query if ( image && - Settings.allowedImageNamesFlat && - Settings.allowedImageNamesFlat.indexOf(image) === -1 + Settings.clsi && + Settings.clsi.docker && + Settings.clsi.docker.allowedImages && + !Settings.clsi.docker.allowedImages.includes(image) ) { return res.status(400).send('invalid image') } diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index 5874a51fb4..5f04fe0c59 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -91,6 +91,13 @@ module.exports = DockerRunner = { image = `${Settings.texliveImageNameOveride}/${img[2]}` } + if ( + Settings.clsi.docker.allowedImages && + !Settings.clsi.docker.allowedImages.includes(image) + ) { + return callback(new Error('image not allowed')) + } + const options = DockerRunner._getContainerOptions( command, image, diff --git a/services/clsi/app/js/RequestParser.js b/services/clsi/app/js/RequestParser.js index ecc92c0836..342dbc86b8 100644 --- a/services/clsi/app/js/RequestParser.js +++ b/services/clsi/app/js/RequestParser.js @@ -61,7 +61,13 @@ module.exports = RequestParser = { response.imageName = this._parseAttribute( 'imageName', compile.options.imageName, - { type: 'string', validValues: settings.allowedImageNamesFlat } + { + type: 'string', + validValues: + settings.clsi && + settings.clsi.docker && + settings.clsi.docker.allowedImages + } ) response.draft = this._parseAttribute('draft', compile.options.draft, { default: false, diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index fd2c28af1e..823f1f737f 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -73,16 +73,6 @@ if (process.env.ALLOWED_COMPILE_GROUPS) { process.exit(1) } } -if (process.env.ALLOWED_IMAGE_NAMES_FLAT) { - try { - module.exports.allowedImageNamesFlat = process.env.ALLOWED_IMAGE_NAMES_FLAT.split( - ' ' - ) - } catch (error) { - console.error(error, 'could not apply allowed image names setting') - process.exit(1) - } -} if (process.env.DOCKER_RUNNER) { let seccompProfilePath @@ -139,6 +129,17 @@ if (process.env.DOCKER_RUNNER) { process.exit(1) } + if (process.env.ALLOWED_IMAGES) { + try { + module.exports.clsi.docker.allowedImages = process.env.ALLOWED_IMAGES.split( + ' ' + ) + } catch (error) { + console.error(error, 'could not apply allowed images setting') + process.exit(1) + } + } + module.exports.path.synctexBaseDir = () => '/compile' module.exports.path.sandboxedCompilesHostDir = process.env.COMPILES_HOST_DIR diff --git a/services/clsi/docker-compose-config.yml b/services/clsi/docker-compose-config.yml index d1b72ee216..afe56bbec5 100644 --- a/services/clsi/docker-compose-config.yml +++ b/services/clsi/docker-compose-config.yml @@ -3,7 +3,7 @@ version: "2.3" services: dev: environment: - ALLOWED_IMAGE_NAMES_FLAT: "quay.io/sharelatex/texlive-full:2017.1" + ALLOWED_IMAGES: "quay.io/sharelatex/texlive-full:2017.1" TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2017.1 TEXLIVE_IMAGE_USER: "tex" SHARELATEX_CONFIG: /app/config/settings.defaults.coffee @@ -19,7 +19,7 @@ services: ci: environment: - ALLOWED_IMAGE_NAMES_FLAT: ${TEXLIVE_IMAGE} + ALLOWED_IMAGES: ${TEXLIVE_IMAGE} TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2017.1 TEXLIVE_IMAGE_USER: "tex" SHARELATEX_CONFIG: /app/config/settings.defaults.coffee diff --git a/services/clsi/test/unit/js/CompileControllerTests.js b/services/clsi/test/unit/js/CompileControllerTests.js index f3f3fa4ad8..8bb83e66af 100644 --- a/services/clsi/test/unit/js/CompileControllerTests.js +++ b/services/clsi/test/unit/js/CompileControllerTests.js @@ -306,9 +306,10 @@ describe('CompileController', function() { .should.equal(true) }) - describe('when allowedImageNamesFlat is set', function() { + describe('when allowedImages is set', function() { beforeEach(function() { - this.Settings.allowedImageNamesFlat = [ + this.Settings.clsi = { docker: {} } + this.Settings.clsi.docker.allowedImages = [ 'repo/image:tag1', 'repo/image:tag2' ] diff --git a/services/clsi/test/unit/js/DockerRunnerTests.js b/services/clsi/test/unit/js/DockerRunnerTests.js index 16ecbbc51e..9c1731a809 100644 --- a/services/clsi/test/unit/js/DockerRunnerTests.js +++ b/services/clsi/test/unit/js/DockerRunnerTests.js @@ -273,7 +273,7 @@ describe('DockerRunner', function() { }) }) - return describe('with image override', function() { + describe('with image override', function() { beforeEach(function() { this.Settings.texliveImageNameOveride = 'overrideimage.com/something' this.DockerRunner._runAndWaitForContainer = sinon @@ -296,6 +296,62 @@ describe('DockerRunner', function() { return image.should.equal('overrideimage.com/something/image:2016.2') }) }) + + describe('with image restriction', function() { + beforeEach(function() { + this.Settings.clsi.docker.allowedImages = [ + 'repo/image:tag1', + 'repo/image:tag2' + ] + this.DockerRunner._runAndWaitForContainer = sinon + .stub() + .callsArgWith(3, null, (this.output = 'mock-output')) + }) + + describe('with a valid image', function() { + beforeEach(function() { + this.DockerRunner.run( + this.project_id, + this.command, + this.directory, + 'repo/image:tag1', + this.timeout, + this.env, + this.compileGroup, + this.callback + ) + }) + + it('should setup the container', function() { + this.DockerRunner._getContainerOptions.called.should.equal(true) + }) + }) + + describe('with a invalid image', function() { + beforeEach(function() { + this.DockerRunner.run( + this.project_id, + this.command, + this.directory, + 'something/different:evil', + this.timeout, + this.env, + this.compileGroup, + this.callback + ) + }) + + it('should call the callback with an error', function() { + const err = new Error('image not allowed') + this.callback.called.should.equal(true) + this.callback.args[0][0].message.should.equal(err.message) + }) + + it('should not setup the container', function() { + this.DockerRunner._getContainerOptions.called.should.equal(false) + }) + }) + }) }) describe('run with _getOptions', function() { diff --git a/services/clsi/test/unit/js/RequestParserTests.js b/services/clsi/test/unit/js/RequestParserTests.js index 16955bb670..25b6b29349 100644 --- a/services/clsi/test/unit/js/RequestParserTests.js +++ b/services/clsi/test/unit/js/RequestParserTests.js @@ -116,7 +116,11 @@ describe('RequestParser', function() { describe('when image restrictions are present', function() { beforeEach(function() { - this.settings.allowedImageNamesFlat = ['repo/name:tag1', 'repo/name:tag2'] + this.settings.clsi = { docker: {} } + this.settings.clsi.docker.allowedImages = [ + 'repo/name:tag1', + 'repo/name:tag2' + ] }) describe('with imageName set to something invalid', function() { From f703b2ca415b3011dcf16479b92d4b7e8b80b3eb Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Wed, 1 Jul 2020 10:01:25 +0100 Subject: [PATCH 607/754] [misc] move the image check prior to the base image override --- services/clsi/app/js/DockerRunner.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index 5f04fe0c59..49c7f40249 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -86,11 +86,6 @@ module.exports = DockerRunner = { ;({ image } = Settings.clsi.docker) } - if (Settings.texliveImageNameOveride != null) { - const img = image.split('/') - image = `${Settings.texliveImageNameOveride}/${img[2]}` - } - if ( Settings.clsi.docker.allowedImages && !Settings.clsi.docker.allowedImages.includes(image) @@ -98,6 +93,11 @@ module.exports = DockerRunner = { return callback(new Error('image not allowed')) } + if (Settings.texliveImageNameOveride != null) { + const img = image.split('/') + image = `${Settings.texliveImageNameOveride}/${img[2]}` + } + const options = DockerRunner._getContainerOptions( command, image, From a5c3bad7f17fbc4c1495601386d1b0c4d269139d Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <das7pad@outlook.com> Date: Tue, 23 Apr 2019 01:00:46 +0200 Subject: [PATCH 608/754] [ExampleDocumentTests] drop out in case of an error during compilation Signed-off-by: Jakob Ackermann <das7pad@outlook.com> --- services/clsi/test/acceptance/js/ExampleDocumentTests.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/clsi/test/acceptance/js/ExampleDocumentTests.js b/services/clsi/test/acceptance/js/ExampleDocumentTests.js index 0134c0e106..0a5447951c 100644 --- a/services/clsi/test/acceptance/js/ExampleDocumentTests.js +++ b/services/clsi/test/acceptance/js/ExampleDocumentTests.js @@ -235,6 +235,7 @@ describe('Example Documents', function() { ) === 'failure' ) { console.log('DEBUG: error', error, 'body', JSON.stringify(body)) + return done(new Error("Compile failed")) } const pdf = Client.getOutputFile(body, 'pdf') return downloadAndComparePdf( @@ -263,6 +264,7 @@ describe('Example Documents', function() { ) === 'failure' ) { console.log('DEBUG: error', error, 'body', JSON.stringify(body)) + return done(new Error("Compile failed")) } const pdf = Client.getOutputFile(body, 'pdf') return downloadAndComparePdf( From cd87d3018e3bff569e07fe38834d97dc53332ac3 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Fri, 3 Jul 2020 11:47:53 +0100 Subject: [PATCH 609/754] [misc] fix formatting --- services/clsi/test/acceptance/js/ExampleDocumentTests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/test/acceptance/js/ExampleDocumentTests.js b/services/clsi/test/acceptance/js/ExampleDocumentTests.js index 0a5447951c..5435a787c9 100644 --- a/services/clsi/test/acceptance/js/ExampleDocumentTests.js +++ b/services/clsi/test/acceptance/js/ExampleDocumentTests.js @@ -235,7 +235,7 @@ describe('Example Documents', function() { ) === 'failure' ) { console.log('DEBUG: error', error, 'body', JSON.stringify(body)) - return done(new Error("Compile failed")) + return done(new Error('Compile failed')) } const pdf = Client.getOutputFile(body, 'pdf') return downloadAndComparePdf( @@ -264,7 +264,7 @@ describe('Example Documents', function() { ) === 'failure' ) { console.log('DEBUG: error', error, 'body', JSON.stringify(body)) - return done(new Error("Compile failed")) + return done(new Error('Compile failed')) } const pdf = Client.getOutputFile(body, 'pdf') return downloadAndComparePdf( From 1d1b9ebebc387b811ff550b0daa2be80d52b4e94 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Mon, 10 Aug 2020 17:01:11 +0100 Subject: [PATCH 610/754] [misc] bump the dev-env to 3.3.2 --- services/clsi/.eslintrc | 2 +- services/clsi/.github/dependabot.yml | 17 ++ services/clsi/.gitignore | 3 + services/clsi/Dockerfile | 4 +- services/clsi/Jenkinsfile | 131 --------- services/clsi/Makefile | 6 +- services/clsi/app.js | 63 ++-- services/clsi/app/js/CompileController.js | 52 ++-- services/clsi/app/js/CompileManager.js | 92 +++--- services/clsi/app/js/DockerLockManager.js | 14 +- services/clsi/app/js/DockerRunner.js | 106 +++---- services/clsi/app/js/DraftModeManager.js | 4 +- services/clsi/app/js/Errors.js | 6 +- services/clsi/app/js/LatexRunner.js | 22 +- services/clsi/app/js/LocalCommandRunner.js | 12 +- services/clsi/app/js/LockManager.js | 8 +- services/clsi/app/js/OutputCacheManager.js | 69 ++--- services/clsi/app/js/OutputFileFinder.js | 18 +- services/clsi/app/js/OutputFileOptimiser.js | 18 +- .../clsi/app/js/ProjectPersistenceManager.js | 46 +-- services/clsi/app/js/RequestParser.js | 2 +- services/clsi/app/js/ResourceStateManager.js | 24 +- services/clsi/app/js/ResourceWriter.js | 68 ++--- services/clsi/app/js/SafeReader.js | 8 +- .../clsi/app/js/StaticServerForbidSymlinks.js | 8 +- services/clsi/app/js/TikzManager.js | 15 +- services/clsi/app/js/UrlCache.js | 73 +++-- services/clsi/app/js/UrlFetcher.js | 20 +- services/clsi/app/js/db.js | 2 +- services/clsi/buildscript.txt | 4 +- services/clsi/docker-compose.ci.yml | 2 + services/clsi/docker-compose.yml | 6 +- services/clsi/nodemon.json | 1 - services/clsi/package-lock.json | 12 +- services/clsi/package.json | 4 +- .../test/acceptance/js/AllowedImageNames.js | 28 +- .../acceptance/js/BrokenLatexFileTests.js | 16 +- .../test/acceptance/js/DeleteOldFilesTest.js | 16 +- .../acceptance/js/ExampleDocumentTests.js | 194 +++++++------ .../acceptance/js/SimpleLatexFileTests.js | 12 +- .../clsi/test/acceptance/js/SynctexTests.js | 36 +-- .../clsi/test/acceptance/js/TimeoutTests.js | 12 +- .../test/acceptance/js/UrlCachingTests.js | 56 ++-- .../clsi/test/acceptance/js/WordcountTests.js | 8 +- .../clsi/test/acceptance/js/helpers/Client.js | 20 +- .../test/acceptance/js/helpers/ClsiApp.js | 6 +- services/clsi/test/load/js/loadTest.js | 10 +- services/clsi/test/smoke/js/SmokeTests.js | 6 +- .../test/unit/js/CompileControllerTests.js | 84 +++--- .../clsi/test/unit/js/CompileManagerTests.js | 112 ++++---- .../test/unit/js/ContentTypeMapperTests.js | 30 +- .../test/unit/js/DockerLockManagerTests.js | 68 ++--- .../clsi/test/unit/js/DockerRunnerTests.js | 272 +++++++++--------- .../test/unit/js/DraftModeManagerTests.js | 20 +- .../clsi/test/unit/js/LatexRunnerTests.js | 30 +- .../clsi/test/unit/js/LockManagerTests.js | 24 +- .../test/unit/js/OutputFileFinderTests.js | 26 +- .../test/unit/js/OutputFileOptimiserTests.js | 65 ++--- .../unit/js/ProjectPersistenceManagerTests.js | 36 +-- .../clsi/test/unit/js/RequestParserTests.js | 180 ++++++------ .../test/unit/js/ResourceStateManagerTests.js | 62 ++-- .../clsi/test/unit/js/ResourceWriterTests.js | 134 ++++----- .../js/StaticServerForbidSymlinksTests.js | 92 +++--- services/clsi/test/unit/js/TikzManager.js | 54 ++-- services/clsi/test/unit/js/UrlCacheTests.js | 112 ++++---- services/clsi/test/unit/js/UrlFetcherTests.js | 66 ++--- 66 files changed, 1371 insertions(+), 1458 deletions(-) create mode 100644 services/clsi/.github/dependabot.yml delete mode 100644 services/clsi/Jenkinsfile diff --git a/services/clsi/.eslintrc b/services/clsi/.eslintrc index 2e945d6ffb..76dad1561d 100644 --- a/services/clsi/.eslintrc +++ b/services/clsi/.eslintrc @@ -8,7 +8,7 @@ "prettier/standard" ], "parserOptions": { - "ecmaVersion": 2017 + "ecmaVersion": 2018 }, "plugins": [ "mocha", diff --git a/services/clsi/.github/dependabot.yml b/services/clsi/.github/dependabot.yml new file mode 100644 index 0000000000..c6f98d843d --- /dev/null +++ b/services/clsi/.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/clsi/.gitignore b/services/clsi/.gitignore index 912e380116..b32ea20954 100644 --- a/services/clsi/.gitignore +++ b/services/clsi/.gitignore @@ -11,3 +11,6 @@ db.sqlite-wal db.sqlite-shm config/* npm-debug.log + +# managed by dev-environment$ bin/update_build_scripts +.npmrc diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 9b37883133..bbf1efd072 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -15,12 +15,10 @@ FROM base as app #wildcard as some files may not be in all repos COPY package*.json npm-shrink*.json /app/ -RUN npm install --quiet +RUN npm ci --quiet COPY . /app - - FROM base COPY --from=app /app /app diff --git a/services/clsi/Jenkinsfile b/services/clsi/Jenkinsfile deleted file mode 100644 index c7b961eb19..0000000000 --- a/services/clsi/Jenkinsfile +++ /dev/null @@ -1,131 +0,0 @@ -String cron_string = BRANCH_NAME == "master" ? "@daily" : "" - -pipeline { - agent any - - environment { - GIT_PROJECT = "clsi" - JENKINS_WORKFLOW = "clsi-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/clsi/Makefile b/services/clsi/Makefile index c938f87add..040a9315e3 100644 --- a/services/clsi/Makefile +++ b/services/clsi/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/clsi/app.js b/services/clsi/app.js index ff5d70e7bd..7ebca04e3b 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -42,14 +42,14 @@ app.use(Metrics.http.monitor(logger)) // minutes (including file download time), so bump up the // timeout a bit. const TIMEOUT = 10 * 60 * 1000 -app.use(function(req, res, next) { +app.use(function (req, res, next) { req.setTimeout(TIMEOUT) res.setTimeout(TIMEOUT) res.removeHeader('X-Powered-By') return next() }) -app.param('project_id', function(req, res, next, projectId) { +app.param('project_id', function (req, res, next, projectId) { if (projectId != null ? projectId.match(/^[a-zA-Z0-9_-]+$/) : undefined) { return next() } else { @@ -57,7 +57,7 @@ app.param('project_id', function(req, res, next, projectId) { } }) -app.param('user_id', function(req, res, next, userId) { +app.param('user_id', function (req, res, next, userId) { if (userId != null ? userId.match(/^[0-9a-f]{24}$/) : undefined) { return next() } else { @@ -65,7 +65,7 @@ app.param('user_id', function(req, res, next, userId) { } }) -app.param('build_id', function(req, res, next, buildId) { +app.param('build_id', function (req, res, next, buildId) { if ( buildId != null ? buildId.match(OutputCacheManager.BUILD_REGEX) : undefined ) { @@ -134,19 +134,18 @@ const staticServer = ForbidSymlinks(express.static, Settings.path.compilesDir, { } }) -app.get('/project/:project_id/user/:user_id/build/:build_id/output/*', function( - req, - res, - next -) { - // for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) - req.url = - `/${req.params.project_id}-${req.params.user_id}/` + - OutputCacheManager.path(req.params.build_id, `/${req.params[0]}`) - return staticServer(req, res, next) -}) +app.get( + '/project/:project_id/user/:user_id/build/:build_id/output/*', + function (req, res, next) { + // for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) + req.url = + `/${req.params.project_id}-${req.params.user_id}/` + + OutputCacheManager.path(req.params.build_id, `/${req.params[0]}`) + return staticServer(req, res, next) + } +) -app.get('/project/:project_id/build/:build_id/output/*', function( +app.get('/project/:project_id/build/:build_id/output/*', function ( req, res, next @@ -158,7 +157,7 @@ app.get('/project/:project_id/build/:build_id/output/*', function( return staticServer(req, res, next) }) -app.get('/project/:project_id/user/:user_id/output/*', function( +app.get('/project/:project_id/user/:user_id/output/*', function ( req, res, next @@ -168,7 +167,7 @@ app.get('/project/:project_id/user/:user_id/output/*', function( return staticServer(req, res, next) }) -app.get('/project/:project_id/output/*', function(req, res, next) { +app.get('/project/:project_id/output/*', function (req, res, next) { if ( (req.query != null ? req.query.build : undefined) != null && req.query.build.match(OutputCacheManager.BUILD_REGEX) @@ -183,7 +182,7 @@ app.get('/project/:project_id/output/*', function(req, res, next) { return staticServer(req, res, next) }) -app.get('/oops', function(req, res, next) { +app.get('/oops', function (req, res, next) { logger.error({ err: 'hello' }, 'test error') return res.send('error\n') }) @@ -208,7 +207,7 @@ if (Settings.processLifespanLimitMs) { function runSmokeTest() { if (Settings.processTooOld) return logger.log('running smoke tests') - smokeTest.triggerRun(err => { + smokeTest.triggerRun((err) => { if (err) logger.error({ err }, 'smoke tests failed') setTimeout(runSmokeTest, 30 * 1000) }) @@ -217,7 +216,7 @@ if (Settings.smokeTest) { runSmokeTest() } -app.get('/health_check', function(req, res) { +app.get('/health_check', function (req, res) { if (Settings.processTooOld) { return res.status(500).json({ processTooOld: true }) } @@ -226,7 +225,7 @@ app.get('/health_check', function(req, res) { app.get('/smoke_test_force', (req, res) => smokeTest.sendNewResult(res)) -app.use(function(error, req, res, next) { +app.use(function (error, req, res, next) { if (error instanceof Errors.NotFoundError) { logger.log({ err: error, url: req.url }, 'not found error') return res.sendStatus(404) @@ -244,8 +243,8 @@ const os = require('os') let STATE = 'up' -const loadTcpServer = net.createServer(function(socket) { - socket.on('error', function(err) { +const loadTcpServer = net.createServer(function (socket) { + socket.on('error', function (err) { if (err.code === 'ECONNRESET') { // this always comes up, we don't know why return @@ -280,19 +279,19 @@ const loadTcpServer = net.createServer(function(socket) { const loadHttpServer = express() -loadHttpServer.post('/state/up', function(req, res, next) { +loadHttpServer.post('/state/up', function (req, res, next) { STATE = 'up' logger.info('getting message to set server to down') return res.sendStatus(204) }) -loadHttpServer.post('/state/down', function(req, res, next) { +loadHttpServer.post('/state/down', function (req, res, next) { STATE = 'down' logger.info('getting message to set server to down') return res.sendStatus(204) }) -loadHttpServer.post('/state/maint', function(req, res, next) { +loadHttpServer.post('/state/maint', function (req, res, next) { STATE = 'maint' logger.info('getting message to set server to maint') return res.sendStatus(204) @@ -301,12 +300,12 @@ loadHttpServer.post('/state/maint', function(req, res, next) { const port = __guard__( Settings.internal != null ? Settings.internal.clsi : undefined, - x => x.port + (x) => x.port ) || 3013 const host = __guard__( Settings.internal != null ? Settings.internal.clsi : undefined, - x1 => x1.host + (x1) => x1.host ) || 'localhost' const loadTcpPort = Settings.internal.load_balancer_agent.load_port @@ -314,7 +313,7 @@ const loadHttpPort = Settings.internal.load_balancer_agent.local_port if (!module.parent) { // Called directly - app.listen(port, host, error => { + app.listen(port, host, (error) => { if (error) { logger.fatal({ error }, `Error starting CLSI on ${host}:${port}`) } else { @@ -322,14 +321,14 @@ if (!module.parent) { } }) - loadTcpServer.listen(loadTcpPort, host, function(error) { + loadTcpServer.listen(loadTcpPort, host, function (error) { if (error != null) { throw error } return logger.info(`Load tcp agent listening on load port ${loadTcpPort}`) }) - loadHttpServer.listen(loadHttpPort, host, function(error) { + loadHttpServer.listen(loadHttpPort, host, function (error) { if (error != null) { throw error } diff --git a/services/clsi/app/js/CompileController.js b/services/clsi/app/js/CompileController.js index 9c18830de8..fb7367cef3 100644 --- a/services/clsi/app/js/CompileController.js +++ b/services/clsi/app/js/CompileController.js @@ -24,10 +24,10 @@ const Errors = require('./Errors') module.exports = CompileController = { compile(req, res, next) { if (next == null) { - next = function(error) {} + next = function (error) {} } const timer = new Metrics.Timer('compile-request') - return RequestParser.parse(req.body, function(error, request) { + return RequestParser.parse(req.body, function (error, request) { if (error != null) { return next(error) } @@ -37,11 +37,11 @@ module.exports = CompileController = { } return ProjectPersistenceManager.markProjectAsJustAccessed( request.project_id, - function(error) { + function (error) { if (error != null) { return next(error) } - return CompileManager.doCompileWithLock(request, function( + return CompileManager.doCompileWithLock(request, function ( error, outputFiles ) { @@ -116,7 +116,7 @@ module.exports = CompileController = { compile: { status, error: (error != null ? error.message : undefined) || error, - outputFiles: outputFiles.map(file => ({ + outputFiles: outputFiles.map((file) => ({ url: `${Settings.apis.clsi.url}/project/${request.project_id}` + (request.user_id != null @@ -138,7 +138,7 @@ module.exports = CompileController = { stopCompile(req, res, next) { const { project_id, user_id } = req.params - return CompileManager.stopCompile(project_id, user_id, function(error) { + return CompileManager.stopCompile(project_id, user_id, function (error) { if (error != null) { return next(error) } @@ -148,12 +148,12 @@ module.exports = CompileController = { clearCache(req, res, next) { if (next == null) { - next = function(error) {} + next = function (error) {} } return ProjectPersistenceManager.clearProject( req.params.project_id, req.params.user_id, - function(error) { + function (error) { if (error != null) { return next(error) } @@ -164,7 +164,7 @@ module.exports = CompileController = { syncFromCode(req, res, next) { if (next == null) { - next = function(error) {} + next = function (error) {} } const { file } = req.query const line = parseInt(req.query.line, 10) @@ -177,7 +177,7 @@ module.exports = CompileController = { file, line, column, - function(error, pdfPositions) { + function (error, pdfPositions) { if (error != null) { return next(error) } @@ -190,29 +190,33 @@ module.exports = CompileController = { syncFromPdf(req, res, next) { if (next == null) { - next = function(error) {} + next = function (error) {} } const page = parseInt(req.query.page, 10) const h = parseFloat(req.query.h) const v = parseFloat(req.query.v) const { project_id } = req.params const { user_id } = req.params - return CompileManager.syncFromPdf(project_id, user_id, page, h, v, function( - error, - codePositions - ) { - if (error != null) { - return next(error) + return CompileManager.syncFromPdf( + project_id, + user_id, + page, + h, + v, + function (error, codePositions) { + if (error != null) { + return next(error) + } + return res.json({ + code: codePositions + }) } - return res.json({ - code: codePositions - }) - }) + ) }, wordcount(req, res, next) { if (next == null) { - next = function(error) {} + next = function (error) {} } const file = req.query.file || 'main.tex' const { project_id } = req.params @@ -229,7 +233,7 @@ module.exports = CompileController = { } logger.log({ image, file, project_id }, 'word count request') - return CompileManager.wordcount(project_id, user_id, file, image, function( + return CompileManager.wordcount(project_id, user_id, file, image, function ( error, result ) { @@ -244,7 +248,7 @@ module.exports = CompileController = { status(req, res, next) { if (next == null) { - next = function(error) {} + next = function (error) {} } return res.send('OK') } diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js index 73db649948..68edde49b0 100644 --- a/services/clsi/app/js/CompileManager.js +++ b/services/clsi/app/js/CompileManager.js @@ -35,7 +35,7 @@ const async = require('async') const Errors = require('./Errors') const CommandRunner = require('./CommandRunner') -const getCompileName = function(project_id, user_id) { +const getCompileName = function (project_id, user_id) { if (user_id != null) { return `${project_id}-${user_id}` } else { @@ -49,19 +49,19 @@ const getCompileDir = (project_id, user_id) => module.exports = CompileManager = { doCompileWithLock(request, callback) { if (callback == null) { - callback = function(error, outputFiles) {} + callback = function (error, outputFiles) {} } const compileDir = getCompileDir(request.project_id, request.user_id) const lockFile = Path.join(compileDir, '.project-lock') // use a .project-lock file in the compile directory to prevent // simultaneous compiles - return fse.ensureDir(compileDir, function(error) { + return fse.ensureDir(compileDir, function (error) { if (error != null) { return callback(error) } return LockManager.runWithLock( lockFile, - releaseLock => CompileManager.doCompile(request, releaseLock), + (releaseLock) => CompileManager.doCompile(request, releaseLock), callback ) }) @@ -69,7 +69,7 @@ module.exports = CompileManager = { doCompile(request, callback) { if (callback == null) { - callback = function(error, outputFiles) {} + callback = function (error, outputFiles) {} } const compileDir = getCompileDir(request.project_id, request.user_id) let timer = new Metrics.Timer('write-to-disk') @@ -77,7 +77,7 @@ module.exports = CompileManager = { { project_id: request.project_id, user_id: request.user_id }, 'syncing resources to disk' ) - return ResourceWriter.syncResourcesToDisk(request, compileDir, function( + return ResourceWriter.syncResourcesToDisk(request, compileDir, function ( error, resourceList ) { @@ -109,7 +109,7 @@ module.exports = CompileManager = { ) timer.done() - const injectDraftModeIfRequired = function(callback) { + const injectDraftModeIfRequired = function (callback) { if (request.draft) { return DraftModeManager.injectDraftMode( Path.join(compileDir, request.rootResourcePath), @@ -120,12 +120,12 @@ module.exports = CompileManager = { } } - const createTikzFileIfRequired = callback => + const createTikzFileIfRequired = (callback) => TikzManager.checkMainFile( compileDir, request.rootResourcePath, resourceList, - function(error, needsMainFile) { + function (error, needsMainFile) { if (error != null) { return callback(error) } @@ -165,7 +165,7 @@ module.exports = CompileManager = { // apply a series of file modifications/creations for draft mode and tikz return async.series( [injectDraftModeIfRequired, createTikzFileIfRequired], - function(error) { + function (error) { if (error != null) { return callback(error) } @@ -177,9 +177,9 @@ module.exports = CompileManager = { request.imageName != null ? request.imageName.match(/:(.*)/) : undefined, - x1 => x1[1] + (x1) => x1[1] ), - x => x.replace(/\./g, '-') + (x) => x.replace(/\./g, '-') ) || 'default' if (!request.project_id.match(/^[0-9a-f]{24}$/)) { tag = 'other' @@ -202,13 +202,11 @@ module.exports = CompileManager = { environment: env, compileGroup: request.compileGroup }, - function(error, output, stats, timings) { + function (error, output, stats, timings) { // request was for validation only let metric_key, metric_value if (request.check === 'validate') { - const result = (error != null - ? error.code - : undefined) + const result = (error != null ? error.code : undefined) ? 'fail' : 'pass' error = new Error('validation') @@ -231,7 +229,7 @@ module.exports = CompileManager = { OutputFileFinder.findOutputFiles( resourceList, compileDir, - function(err, outputFiles) { + function (err, outputFiles) { if (err != null) { return callback(err) } @@ -289,7 +287,7 @@ module.exports = CompileManager = { return OutputFileFinder.findOutputFiles( resourceList, compileDir, - function(error, outputFiles) { + function (error, outputFiles) { if (error != null) { return callback(error) } @@ -309,7 +307,7 @@ module.exports = CompileManager = { stopCompile(project_id, user_id, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } const compileName = getCompileName(project_id, user_id) return LatexRunner.killLatex(compileName, callback) @@ -317,16 +315,16 @@ module.exports = CompileManager = { clearProject(project_id, user_id, _callback) { if (_callback == null) { - _callback = function(error) {} + _callback = function (error) {} } - const callback = function(error) { + const callback = function (error) { _callback(error) - return (_callback = function() {}) + return (_callback = function () {}) } const compileDir = getCompileDir(project_id, user_id) - return CompileManager._checkDirectory(compileDir, function(err, exists) { + return CompileManager._checkDirectory(compileDir, function (err, exists) { if (err != null) { return callback(err) } @@ -339,9 +337,9 @@ module.exports = CompileManager = { proc.on('error', callback) let stderr = '' - proc.stderr.setEncoding('utf8').on('data', chunk => (stderr += chunk)) + proc.stderr.setEncoding('utf8').on('data', (chunk) => (stderr += chunk)) - return proc.on('close', function(code) { + return proc.on('close', function (code) { if (code === 0) { return callback(null) } else { @@ -353,26 +351,26 @@ module.exports = CompileManager = { _findAllDirs(callback) { if (callback == null) { - callback = function(error, allDirs) {} + callback = function (error, allDirs) {} } const root = Settings.path.compilesDir - return fs.readdir(root, function(err, files) { + return fs.readdir(root, function (err, files) { if (err != null) { return callback(err) } - const allDirs = Array.from(files).map(file => Path.join(root, file)) + const allDirs = Array.from(files).map((file) => Path.join(root, file)) return callback(null, allDirs) }) }, clearExpiredProjects(max_cache_age_ms, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } const now = Date.now() // action for each directory const expireIfNeeded = (checkDir, cb) => - fs.stat(checkDir, function(err, stats) { + fs.stat(checkDir, function (err, stats) { if (err != null) { return cb() } // ignore errors checking directory @@ -385,7 +383,7 @@ module.exports = CompileManager = { } }) // iterate over all project directories - return CompileManager._findAllDirs(function(error, allDirs) { + return CompileManager._findAllDirs(function (error, allDirs) { if (error != null) { return callback() } @@ -395,9 +393,9 @@ module.exports = CompileManager = { _checkDirectory(compileDir, callback) { if (callback == null) { - callback = function(error, exists) {} + callback = function (error, exists) {} } - return fs.lstat(compileDir, function(err, stats) { + return fs.lstat(compileDir, function (err, stats) { if ((err != null ? err.code : undefined) === 'ENOENT') { return callback(null, false) // directory does not exist } else if (err != null) { @@ -423,7 +421,7 @@ module.exports = CompileManager = { // might not match the file path on the host. The .synctex.gz file however, will be accessed // wherever it is on the host. if (callback == null) { - callback = function(error, pdfPositions) {} + callback = function (error, pdfPositions) {} } const compileName = getCompileName(project_id, user_id) const base_dir = Settings.path.synctexBaseDir(compileName) @@ -431,7 +429,7 @@ module.exports = CompileManager = { const compileDir = getCompileDir(project_id, user_id) const synctex_path = `${base_dir}/output.pdf` const command = ['code', synctex_path, file_path, line, column] - CompileManager._runSynctex(project_id, user_id, command, function( + CompileManager._runSynctex(project_id, user_id, command, function ( error, stdout ) { @@ -448,14 +446,14 @@ module.exports = CompileManager = { syncFromPdf(project_id, user_id, page, h, v, callback) { if (callback == null) { - callback = function(error, filePositions) {} + callback = function (error, filePositions) {} } const compileName = getCompileName(project_id, user_id) const compileDir = getCompileDir(project_id, user_id) const base_dir = Settings.path.synctexBaseDir(compileName) const synctex_path = `${base_dir}/output.pdf` const command = ['pdf', synctex_path, page, h, v] - CompileManager._runSynctex(project_id, user_id, command, function( + CompileManager._runSynctex(project_id, user_id, command, function ( error, stdout ) { @@ -475,17 +473,17 @@ module.exports = CompileManager = { _checkFileExists(dir, filename, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } const file = Path.join(dir, filename) - return fs.stat(dir, function(error, stats) { + return fs.stat(dir, function (error, stats) { if ((error != null ? error.code : undefined) === 'ENOENT') { return callback(new Errors.NotFoundError('no output directory')) } if (error != null) { return callback(error) } - return fs.stat(file, function(error, stats) { + return fs.stat(file, function (error, stats) { if ((error != null ? error.code : undefined) === 'ENOENT') { return callback(new Errors.NotFoundError('no output file')) } @@ -502,7 +500,7 @@ module.exports = CompileManager = { _runSynctex(project_id, user_id, command, callback) { if (callback == null) { - callback = function(error, stdout) {} + callback = function (error, stdout) {} } const seconds = 1000 @@ -512,7 +510,7 @@ module.exports = CompileManager = { const timeout = 60 * 1000 // increased to allow for large projects const compileName = getCompileName(project_id, user_id) const compileGroup = 'synctex' - CompileManager._checkFileExists(directory, 'output.synctex.gz', error => { + CompileManager._checkFileExists(directory, 'output.synctex.gz', (error) => { if (error) { return callback(error) } @@ -526,7 +524,7 @@ module.exports = CompileManager = { timeout, {}, compileGroup, - function(error, output) { + function (error, output) { if (error != null) { logger.err( { err: error, command, project_id, user_id }, @@ -576,7 +574,7 @@ module.exports = CompileManager = { wordcount(project_id, user_id, file_name, image, callback) { if (callback == null) { - callback = function(error, pdfPositions) {} + callback = function (error, pdfPositions) {} } logger.log({ project_id, user_id, file_name, image }, 'running wordcount') const file_path = `$COMPILE_DIR/${file_name}` @@ -591,7 +589,7 @@ module.exports = CompileManager = { const timeout = 60 * 1000 const compileName = getCompileName(project_id, user_id) const compileGroup = 'wordcount' - return fse.ensureDir(compileDir, function(error) { + return fse.ensureDir(compileDir, function (error) { if (error != null) { logger.err( { error, project_id, user_id, file_name }, @@ -607,14 +605,14 @@ module.exports = CompileManager = { timeout, {}, compileGroup, - function(error) { + function (error) { if (error != null) { return callback(error) } return fs.readFile( compileDir + '/' + file_name + '.wc', 'utf-8', - function(err, stdout) { + function (err, stdout) { if (err != null) { // call it node_err so sentry doesn't use random path error as unique id so it can't be ignored logger.err( diff --git a/services/clsi/app/js/DockerLockManager.js b/services/clsi/app/js/DockerLockManager.js index 2685b42dc8..0ed098565d 100644 --- a/services/clsi/app/js/DockerLockManager.js +++ b/services/clsi/app/js/DockerLockManager.js @@ -23,7 +23,7 @@ module.exports = LockManager = { tryLock(key, callback) { let lockValue if (callback == null) { - callback = function(err, gotLock) {} + callback = function (err, gotLock) {} } const existingLock = LockState[key] if (existingLock != null) { @@ -46,11 +46,11 @@ module.exports = LockManager = { getLock(key, callback) { let attempt if (callback == null) { - callback = function(error, lockValue) {} + callback = function (error, lockValue) {} } const startTime = Date.now() return (attempt = () => - LockManager.tryLock(key, function(error, gotLock, lockValue) { + LockManager.tryLock(key, function (error, gotLock, lockValue) { if (error != null) { return callback(error) } @@ -68,7 +68,7 @@ module.exports = LockManager = { releaseLock(key, lockValue, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } const existingLock = LockState[key] if (existingLock === lockValue) { @@ -93,14 +93,14 @@ module.exports = LockManager = { runWithLock(key, runner, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } - return LockManager.getLock(key, function(error, lockValue) { + return LockManager.getLock(key, function (error, lockValue) { if (error != null) { return callback(error) } return runner((error1, ...args) => - LockManager.releaseLock(key, lockValue, function(error2) { + LockManager.releaseLock(key, lockValue, function (error2) { error = error1 || error2 if (error != null) { return callback(error) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index 49c7f40249..723453922f 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -32,7 +32,7 @@ logger.info('using docker runner') const usingSiblingContainers = () => __guard__( Settings != null ? Settings.path : undefined, - x => x.sandboxedCompilesHostDir + (x) => x.sandboxedCompilesHostDir ) != null let containerMonitorTimeout @@ -56,7 +56,7 @@ module.exports = DockerRunner = { ) { let name if (callback == null) { - callback = function(error, output) {} + callback = function (error, output) {} } if (usingSiblingContainers()) { const _newPath = Settings.path.sandboxedCompilesHostDir @@ -77,8 +77,8 @@ module.exports = DockerRunner = { const volumes = {} volumes[directory] = '/compile' - command = Array.from(command).map(arg => - __guardMethod__(arg.toString(), 'replace', o => + command = Array.from(command).map((arg) => + __guardMethod__(arg.toString(), 'replace', (o) => o.replace('$COMPILE_DIR', '/compile') ) ) @@ -112,7 +112,7 @@ module.exports = DockerRunner = { // logOptions = _.clone(options) // logOptions?.HostConfig?.SecurityOpt = "secomp used, removed in logging" logger.log({ project_id }, 'running docker container') - DockerRunner._runAndWaitForContainer(options, volumes, timeout, function( + DockerRunner._runAndWaitForContainer(options, volumes, timeout, function ( error, output ) { @@ -121,7 +121,9 @@ module.exports = DockerRunner = { { err: error, project_id }, 'error running container so destroying and retrying' ) - return DockerRunner.destroyContainer(name, null, true, function(error) { + return DockerRunner.destroyContainer(name, null, true, function ( + error + ) { if (error != null) { return callback(error) } @@ -142,15 +144,17 @@ module.exports = DockerRunner = { kill(container_id, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } logger.log({ container_id }, 'sending kill signal to container') const container = dockerode.getContainer(container_id) - return container.kill(function(error) { + return container.kill(function (error) { if ( error != null && - __guardMethod__(error != null ? error.message : undefined, 'match', o => - o.match(/Cannot kill container .* is not running/) + __guardMethod__( + error != null ? error.message : undefined, + 'match', + (o) => o.match(/Cannot kill container .* is not running/) ) ) { logger.warn( @@ -170,12 +174,12 @@ module.exports = DockerRunner = { _runAndWaitForContainer(options, volumes, timeout, _callback) { if (_callback == null) { - _callback = function(error, output) {} + _callback = function (error, output) {} } - const callback = function(...args) { + const callback = function (...args) { _callback(...Array.from(args || [])) // Only call the callback once - return (_callback = function() {}) + return (_callback = function () {}) } const { name } = options @@ -184,13 +188,13 @@ module.exports = DockerRunner = { let containerReturned = false let output = {} - const callbackIfFinished = function() { + const callbackIfFinished = function () { if (streamEnded && containerReturned) { return callback(null, output) } } - const attachStreamHandler = function(error, _output) { + const attachStreamHandler = function (error, _output) { if (error != null) { return callback(error) } @@ -203,12 +207,12 @@ module.exports = DockerRunner = { options, volumes, attachStreamHandler, - function(error, containerId) { + function (error, containerId) { if (error != null) { return callback(error) } - return DockerRunner.waitForContainer(name, timeout, function( + return DockerRunner.waitForContainer(name, timeout, function ( error, exitCode ) { @@ -231,7 +235,7 @@ module.exports = DockerRunner = { containerReturned = true __guard__( options != null ? options.HostConfig : undefined, - x => (x.SecurityOpt = null) + (x) => (x.SecurityOpt = null) ) // small log line logger.log({ err, exitCode, options }, 'docker container has exited') return callbackIfFinished() @@ -357,21 +361,18 @@ module.exports = DockerRunner = { _fingerprintContainer(containerOptions) { // Yay, Hashing! const json = JSON.stringify(containerOptions) - return crypto - .createHash('md5') - .update(json) - .digest('hex') + return crypto.createHash('md5').update(json).digest('hex') }, startContainer(options, volumes, attachStreamHandler, callback) { return LockManager.runWithLock( options.name, - releaseLock => + (releaseLock) => // Check that volumes exist before starting the container. // When a container is started with volume pointing to a // non-existent directory then docker creates the directory but // with root ownership. - DockerRunner._checkVolumes(options, volumes, function(err) { + DockerRunner._checkVolumes(options, volumes, function (err) { if (err != null) { return releaseLock(err) } @@ -390,7 +391,7 @@ module.exports = DockerRunner = { // Check that volumes exist and are directories _checkVolumes(options, volumes, callback) { if (callback == null) { - callback = function(error, containerName) {} + callback = function (error, containerName) {} } if (usingSiblingContainers()) { // Server Pro, with sibling-containers active, skip checks @@ -398,7 +399,7 @@ module.exports = DockerRunner = { } const checkVolume = (path, cb) => - fs.stat(path, function(err, stats) { + fs.stat(path, function (err, stats) { if (err != null) { return cb(err) } @@ -409,14 +410,14 @@ module.exports = DockerRunner = { }) const jobs = [] for (const vol in volumes) { - ;(vol => jobs.push(cb => checkVolume(vol, cb)))(vol) + ;((vol) => jobs.push((cb) => checkVolume(vol, cb)))(vol) } return async.series(jobs, callback) }, _startContainer(options, volumes, attachStreamHandler, callback) { if (callback == null) { - callback = function(error, output) {} + callback = function (error, output) {} } callback = _.once(callback) const { name } = options @@ -425,7 +426,7 @@ module.exports = DockerRunner = { const container = dockerode.getContainer(name) const createAndStartContainer = () => - dockerode.createContainer(options, function(error, container) { + dockerode.createContainer(options, function (error, container) { if (error != null) { return callback(error) } @@ -435,11 +436,11 @@ module.exports = DockerRunner = { DockerRunner.attachToContainer( options.name, attachStreamHandler, - function(error) { + function (error) { if (error != null) { return callback(error) } - return container.start(function(error) { + return container.start(function (error) { if ( error != null && (error != null ? error.statusCode : undefined) !== 304 @@ -452,7 +453,7 @@ module.exports = DockerRunner = { }) } ) - return container.inspect(function(error, stats) { + return container.inspect(function (error, stats) { if ((error != null ? error.statusCode : undefined) === 404) { return createAndStartContainer() } else if (error != null) { @@ -469,7 +470,7 @@ module.exports = DockerRunner = { attachToContainer(containerId, attachStreamHandler, attachStartCallback) { const container = dockerode.getContainer(containerId) - return container.attach({ stdout: 1, stderr: 1, stream: 1 }, function( + return container.attach({ stdout: 1, stderr: 1, stream: 1 }, function ( error, stream ) { @@ -486,7 +487,7 @@ module.exports = DockerRunner = { logger.log({ container_id: containerId }, 'attached to container') const MAX_OUTPUT = 1024 * 1024 // limit output to 1MB - const createStringOutputStream = function(name) { + const createStringOutputStream = function (name) { return { data: '', overflowed: false, @@ -519,7 +520,7 @@ module.exports = DockerRunner = { container.modem.demuxStream(stream, stdout, stderr) - stream.on('error', err => + stream.on('error', (err) => logger.error( { err, container_id: containerId }, 'error reading from container stream' @@ -534,28 +535,28 @@ module.exports = DockerRunner = { waitForContainer(containerId, timeout, _callback) { if (_callback == null) { - _callback = function(error, exitCode) {} + _callback = function (error, exitCode) {} } - const callback = function(...args) { + const callback = function (...args) { _callback(...Array.from(args || [])) // Only call the callback once - return (_callback = function() {}) + return (_callback = function () {}) } const container = dockerode.getContainer(containerId) let timedOut = false - const timeoutId = setTimeout(function() { + const timeoutId = setTimeout(function () { timedOut = true logger.log( { container_id: containerId }, 'timeout reached, killing container' ) - return container.kill(function() {}) + return container.kill(function () {}) }, timeout) logger.log({ container_id: containerId }, 'waiting for docker container') - return container.wait(function(error, res) { + return container.wait(function (error, res) { if (error != null) { clearTimeout(timeoutId) logger.error( @@ -588,11 +589,11 @@ module.exports = DockerRunner = { // error callback. We fall back to deleting by name if no id is // supplied. if (callback == null) { - callback = function(error) {} + callback = function (error) {} } return LockManager.runWithLock( containerName, - releaseLock => + (releaseLock) => DockerRunner._destroyContainer( containerId || containerName, shouldForce, @@ -604,11 +605,11 @@ module.exports = DockerRunner = { _destroyContainer(containerId, shouldForce, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } logger.log({ container_id: containerId }, 'destroying docker container') const container = dockerode.getContainer(containerId) - return container.remove({ force: shouldForce === true }, function(error) { + return container.remove({ force: shouldForce === true }, function (error) { if ( error != null && (error != null ? error.statusCode : undefined) === 404 @@ -638,7 +639,7 @@ module.exports = DockerRunner = { examineOldContainer(container, callback) { if (callback == null) { - callback = function(error, name, id, ttl) {} + callback = function (error, name, id, ttl) {} } const name = container.Name || @@ -657,16 +658,19 @@ module.exports = DockerRunner = { destroyOldContainers(callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } - return dockerode.listContainers({ all: true }, function(error, containers) { + return dockerode.listContainers({ all: true }, function ( + error, + containers + ) { if (error != null) { return callback(error) } const jobs = [] for (const container of Array.from(containers || [])) { - ;(container => - DockerRunner.examineOldContainer(container, function( + ;((container) => + DockerRunner.examineOldContainer(container, function ( err, name, id, @@ -676,7 +680,7 @@ module.exports = DockerRunner = { // strip the / prefix // the LockManager uses the plain container name name = name.slice(1) - return jobs.push(cb => + return jobs.push((cb) => DockerRunner.destroyContainer(name, id, false, () => cb()) ) } diff --git a/services/clsi/app/js/DraftModeManager.js b/services/clsi/app/js/DraftModeManager.js index c8f59aa613..0bdd40f047 100644 --- a/services/clsi/app/js/DraftModeManager.js +++ b/services/clsi/app/js/DraftModeManager.js @@ -18,9 +18,9 @@ const logger = require('logger-sharelatex') module.exports = DraftModeManager = { injectDraftMode(filename, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } - return fs.readFile(filename, 'utf8', function(error, content) { + return fs.readFile(filename, 'utf8', function (error, content) { if (error != null) { return callback(error) } diff --git a/services/clsi/app/js/Errors.js b/services/clsi/app/js/Errors.js index d3a5f5a066..9b014f8be0 100644 --- a/services/clsi/app/js/Errors.js +++ b/services/clsi/app/js/Errors.js @@ -5,7 +5,7 @@ // TODO: This file was created by bulk-decaffeinate. // Fix any style issues and re-enable lint. let Errors -var NotFoundError = function(message) { +var NotFoundError = function (message) { const error = new Error(message) error.name = 'NotFoundError' error.__proto__ = NotFoundError.prototype @@ -13,7 +13,7 @@ var NotFoundError = function(message) { } NotFoundError.prototype.__proto__ = Error.prototype -var FilesOutOfSyncError = function(message) { +var FilesOutOfSyncError = function (message) { const error = new Error(message) error.name = 'FilesOutOfSyncError' error.__proto__ = FilesOutOfSyncError.prototype @@ -21,7 +21,7 @@ var FilesOutOfSyncError = function(message) { } FilesOutOfSyncError.prototype.__proto__ = Error.prototype -var AlreadyCompilingError = function(message) { +var AlreadyCompilingError = function (message) { const error = new Error(message) error.name = 'AlreadyCompilingError' error.__proto__ = AlreadyCompilingError.prototype diff --git a/services/clsi/app/js/LatexRunner.js b/services/clsi/app/js/LatexRunner.js index 6d1591a213..f8799d216c 100644 --- a/services/clsi/app/js/LatexRunner.js +++ b/services/clsi/app/js/LatexRunner.js @@ -27,7 +27,7 @@ module.exports = LatexRunner = { runLatex(project_id, options, callback) { let command if (callback == null) { - callback = function(error) {} + callback = function (error) {} } let { directory, @@ -89,20 +89,20 @@ module.exports = LatexRunner = { timeout, environment, compileGroup, - function(error, output) { + function (error, output) { delete ProcessTable[id] if (error != null) { return callback(error) } const runs = __guard__( - __guard__(output != null ? output.stderr : undefined, x1 => + __guard__(output != null ? output.stderr : undefined, (x1) => x1.match(/^Run number \d+ of .*latex/gm) ), - x => x.length + (x) => x.length ) || 0 const failed = - __guard__(output != null ? output.stdout : undefined, x2 => + __guard__(output != null ? output.stdout : undefined, (x2) => x2.match(/^Latexmk: Errors/m) ) != null ? 1 @@ -122,21 +122,21 @@ module.exports = LatexRunner = { stderr != null ? stderr.match(/Percent of CPU this job got: (\d+)/m) : undefined, - x3 => x3[1] + (x3) => x3[1] ) || 0 timings['cpu-time'] = __guard__( stderr != null ? stderr.match(/User time.*: (\d+.\d+)/m) : undefined, - x4 => x4[1] + (x4) => x4[1] ) || 0 timings['sys-time'] = __guard__( stderr != null ? stderr.match(/System time.*: (\d+.\d+)/m) : undefined, - x5 => x5[1] + (x5) => x5[1] ) || 0 // record output files LatexRunner.writeLogOutput(project_id, directory, output, () => { @@ -153,7 +153,7 @@ module.exports = LatexRunner = { // internal method for writing non-empty log files function _writeFile(file, content, cb) { if (content && content.length > 0) { - fs.writeFile(file, content, err => { + fs.writeFile(file, content, (err) => { if (err) { logger.error({ project_id, file }, 'error writing log file') // don't fail on error } @@ -173,7 +173,7 @@ module.exports = LatexRunner = { killLatex(project_id, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } const id = `${project_id}` logger.log({ id }, 'killing running compile') @@ -202,7 +202,7 @@ module.exports = LatexRunner = { return ( __guard__( Settings != null ? Settings.clsi : undefined, - x => x.latexmkCommandPrefix + (x) => x.latexmkCommandPrefix ) || [] ).concat(args) }, diff --git a/services/clsi/app/js/LocalCommandRunner.js b/services/clsi/app/js/LocalCommandRunner.js index f16fa16bd9..6f57731c9c 100644 --- a/services/clsi/app/js/LocalCommandRunner.js +++ b/services/clsi/app/js/LocalCommandRunner.js @@ -33,11 +33,11 @@ module.exports = CommandRunner = { ) { let key, value if (callback == null) { - callback = function(error) {} + callback = function (error) {} } else { callback = _.once(callback) } - command = Array.from(command).map(arg => + command = Array.from(command).map((arg) => arg.toString().replace('$COMPILE_DIR', directory) ) logger.log({ project_id, command, directory }, 'running command') @@ -58,9 +58,9 @@ module.exports = CommandRunner = { const proc = spawn(command[0], command.slice(1), { cwd: directory, env }) let stdout = '' - proc.stdout.setEncoding('utf8').on('data', data => (stdout += data)) + proc.stdout.setEncoding('utf8').on('data', (data) => (stdout += data)) - proc.on('error', function(err) { + proc.on('error', function (err) { logger.err( { err, project_id, command, directory }, 'error running command' @@ -68,7 +68,7 @@ module.exports = CommandRunner = { return callback(err) }) - proc.on('close', function(code, signal) { + proc.on('close', function (code, signal) { let err logger.info({ code, signal, project_id }, 'command exited') if (signal === 'SIGTERM') { @@ -91,7 +91,7 @@ module.exports = CommandRunner = { kill(pid, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } try { process.kill(-pid) // kill all processes in group diff --git a/services/clsi/app/js/LockManager.js b/services/clsi/app/js/LockManager.js index 2da7da109f..1246cc9848 100644 --- a/services/clsi/app/js/LockManager.js +++ b/services/clsi/app/js/LockManager.js @@ -25,20 +25,20 @@ module.exports = LockManager = { runWithLock(path, runner, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } const lockOpts = { wait: this.MAX_LOCK_WAIT_TIME, pollPeriod: this.LOCK_TEST_INTERVAL, stale: this.LOCK_STALE } - return Lockfile.lock(path, lockOpts, function(error) { + return Lockfile.lock(path, lockOpts, function (error) { if ((error != null ? error.code : undefined) === 'EEXIST') { return callback(new Errors.AlreadyCompilingError('compile in progress')) } else if (error != null) { return fs.lstat(path, (statLockErr, statLock) => fs.lstat(Path.dirname(path), (statDirErr, statDir) => - fs.readdir(Path.dirname(path), function(readdirErr, readdirDir) { + fs.readdir(Path.dirname(path), function (readdirErr, readdirDir) { logger.err( { error, @@ -58,7 +58,7 @@ module.exports = LockManager = { ) } else { return runner((error1, ...args) => - Lockfile.unlock(path, function(error2) { + Lockfile.unlock(path, function (error2) { error = error1 || error2 if (error != null) { return callback(error) diff --git a/services/clsi/app/js/OutputCacheManager.js b/services/clsi/app/js/OutputCacheManager.js index c0b0d6ed17..b34dea7994 100644 --- a/services/clsi/app/js/OutputCacheManager.js +++ b/services/clsi/app/js/OutputCacheManager.js @@ -47,9 +47,9 @@ module.exports = OutputCacheManager = { generateBuildId(callback) { // generate a secure build id from Date.now() and 8 random bytes in hex if (callback == null) { - callback = function(error, buildId) {} + callback = function (error, buildId) {} } - return crypto.randomBytes(8, function(err, buf) { + return crypto.randomBytes(8, function (err, buf) { if (err != null) { return callback(err) } @@ -61,9 +61,9 @@ module.exports = OutputCacheManager = { saveOutputFiles(outputFiles, compileDir, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } - return OutputCacheManager.generateBuildId(function(err, buildId) { + return OutputCacheManager.generateBuildId(function (err, buildId) { if (err != null) { return callback(err) } @@ -80,7 +80,7 @@ module.exports = OutputCacheManager = { // make a compileDir/CACHE_SUBDIR/build_id directory and // copy all the output files into it if (callback == null) { - callback = function(error) {} + callback = function (error) {} } const cacheRoot = Path.join(compileDir, OutputCacheManager.CACHE_SUBDIR) // Put the files into a new cache subdirectory @@ -99,17 +99,20 @@ module.exports = OutputCacheManager = { (Settings.clsi != null ? Settings.clsi.archive_logs : undefined) || (Settings.clsi != null ? Settings.clsi.strace : undefined) ) { - OutputCacheManager.archiveLogs(outputFiles, compileDir, buildId, function( - err - ) { - if (err != null) { - return logger.warn({ err }, 'erroring archiving log files') + OutputCacheManager.archiveLogs( + outputFiles, + compileDir, + buildId, + function (err) { + if (err != null) { + return logger.warn({ err }, 'erroring archiving log files') + } } - }) + ) } // make the new cache directory - return fse.ensureDir(cacheDir, function(err) { + return fse.ensureDir(cacheDir, function (err) { if (err != null) { logger.error( { err, directory: cacheDir }, @@ -121,7 +124,7 @@ module.exports = OutputCacheManager = { const results = [] return async.mapSeries( outputFiles, - function(file, cb) { + function (file, cb) { // don't send dot files as output, express doesn't serve them if (OutputCacheManager._fileIsHidden(file.path)) { logger.debug( @@ -136,7 +139,7 @@ module.exports = OutputCacheManager = { Path.join(compileDir, file.path), Path.join(cacheDir, file.path) ]) - return OutputCacheManager._checkFileIsSafe(src, function( + return OutputCacheManager._checkFileIsSafe(src, function ( err, isSafe ) { @@ -146,7 +149,7 @@ module.exports = OutputCacheManager = { if (!isSafe) { return cb() } - return OutputCacheManager._checkIfShouldCopy(src, function( + return OutputCacheManager._checkIfShouldCopy(src, function ( err, shouldCopy ) { @@ -156,7 +159,7 @@ module.exports = OutputCacheManager = { if (!shouldCopy) { return cb() } - return OutputCacheManager._copyFile(src, dst, function(err) { + return OutputCacheManager._copyFile(src, dst, function (err) { if (err != null) { return cb(err) } @@ -167,12 +170,12 @@ module.exports = OutputCacheManager = { }) }) }, - function(err) { + function (err) { if (err != null) { // pass back the original files if we encountered *any* error callback(err, outputFiles) // clean up the directory we just created - return fse.remove(cacheDir, function(err) { + return fse.remove(cacheDir, function (err) { if (err != null) { return logger.error( { err, dir: cacheDir }, @@ -197,7 +200,7 @@ module.exports = OutputCacheManager = { archiveLogs(outputFiles, compileDir, buildId, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } const archiveDir = Path.join( compileDir, @@ -205,18 +208,18 @@ module.exports = OutputCacheManager = { buildId ) logger.log({ dir: archiveDir }, 'archiving log files for project') - return fse.ensureDir(archiveDir, function(err) { + return fse.ensureDir(archiveDir, function (err) { if (err != null) { return callback(err) } return async.mapSeries( outputFiles, - function(file, cb) { + function (file, cb) { const [src, dst] = Array.from([ Path.join(compileDir, file.path), Path.join(archiveDir, file.path) ]) - return OutputCacheManager._checkFileIsSafe(src, function( + return OutputCacheManager._checkFileIsSafe(src, function ( err, isSafe ) { @@ -226,7 +229,7 @@ module.exports = OutputCacheManager = { if (!isSafe) { return cb() } - return OutputCacheManager._checkIfShouldArchive(src, function( + return OutputCacheManager._checkIfShouldArchive(src, function ( err, shouldArchive ) { @@ -248,9 +251,9 @@ module.exports = OutputCacheManager = { expireOutputFiles(cacheRoot, options, callback) { // look in compileDir for build dirs and delete if > N or age of mod time > T if (callback == null) { - callback = function(error) {} + callback = function (error) {} } - return fs.readdir(cacheRoot, function(err, results) { + return fs.readdir(cacheRoot, function (err, results) { if (err != null) { if (err.code === 'ENOENT') { return callback(null) @@ -262,7 +265,7 @@ module.exports = OutputCacheManager = { const dirs = results.sort().reverse() const currentTime = Date.now() - const isExpired = function(dir, index) { + const isExpired = function (dir, index) { if ((options != null ? options.keep : undefined) === dir) { return false } @@ -280,7 +283,7 @@ module.exports = OutputCacheManager = { // we can get the build time from the first part of the directory name DDDD-RRRR // DDDD is date and RRRR is random bytes const dirTime = parseInt( - __guard__(dir.split('-'), x => x[0]), + __guard__(dir.split('-'), (x) => x[0]), 16 ) const age = currentTime - dirTime @@ -290,7 +293,7 @@ module.exports = OutputCacheManager = { const toRemove = _.filter(dirs, isExpired) const removeDir = (dir, cb) => - fse.remove(Path.join(cacheRoot, dir), function(err, result) { + fse.remove(Path.join(cacheRoot, dir), function (err, result) { logger.log({ cache: cacheRoot, dir }, 'removed expired cache dir') if (err != null) { logger.error({ err, dir }, 'cache remove error') @@ -312,9 +315,9 @@ module.exports = OutputCacheManager = { _checkFileIsSafe(src, callback) { // check if we have a valid file to copy into the cache if (callback == null) { - callback = function(error, isSafe) {} + callback = function (error, isSafe) {} } - return fs.stat(src, function(err, stats) { + return fs.stat(src, function (err, stats) { if ((err != null ? err.code : undefined) === 'ENOENT') { logger.warn( { err, file: src }, @@ -341,7 +344,7 @@ module.exports = OutputCacheManager = { _copyFile(src, dst, callback) { // copy output file into the cache - return fse.copy(src, dst, function(err) { + return fse.copy(src, dst, function (err) { if ((err != null ? err.code : undefined) === 'ENOENT') { logger.warn( { err, file: src }, @@ -368,7 +371,7 @@ module.exports = OutputCacheManager = { _checkIfShouldCopy(src, callback) { if (callback == null) { - callback = function(err, shouldCopy) {} + callback = function (err, shouldCopy) {} } return callback(null, !Path.basename(src).match(/^strace/)) }, @@ -376,7 +379,7 @@ module.exports = OutputCacheManager = { _checkIfShouldArchive(src, callback) { let needle if (callback == null) { - callback = function(err, shouldCopy) {} + callback = function (err, shouldCopy) {} } if (Path.basename(src).match(/^strace/)) { return callback(null, true) diff --git a/services/clsi/app/js/OutputFileFinder.js b/services/clsi/app/js/OutputFileFinder.js index 831997811b..f863e0c1ed 100644 --- a/services/clsi/app/js/OutputFileFinder.js +++ b/services/clsi/app/js/OutputFileFinder.js @@ -24,14 +24,14 @@ const logger = require('logger-sharelatex') module.exports = OutputFileFinder = { findOutputFiles(resources, directory, callback) { if (callback == null) { - callback = function(error, outputFiles, allFiles) {} + callback = function (error, outputFiles, allFiles) {} } const incomingResources = {} for (const resource of Array.from(resources)) { incomingResources[resource.path] = true } - return OutputFileFinder._getAllFiles(directory, function(error, allFiles) { + return OutputFileFinder._getAllFiles(directory, function (error, allFiles) { if (allFiles == null) { allFiles = [] } @@ -44,7 +44,7 @@ module.exports = OutputFileFinder = { if (!incomingResources[file]) { outputFiles.push({ path: file, - type: __guard__(file.match(/\.([^\.]+)$/), x => x[1]) + type: __guard__(file.match(/\.([^\.]+)$/), (x) => x[1]) }) } } @@ -54,11 +54,11 @@ module.exports = OutputFileFinder = { _getAllFiles(directory, _callback) { if (_callback == null) { - _callback = function(error, fileList) {} + _callback = function (error, fileList) {} } - const callback = function(error, fileList) { + const callback = function (error, fileList) { _callback(error, fileList) - return (_callback = function() {}) + return (_callback = function () {}) } // don't include clsi-specific files/directories in the output list @@ -87,9 +87,9 @@ module.exports = OutputFileFinder = { const proc = spawn('find', args) let stdout = '' - proc.stdout.setEncoding('utf8').on('data', chunk => (stdout += chunk)) + proc.stdout.setEncoding('utf8').on('data', (chunk) => (stdout += chunk)) proc.on('error', callback) - return proc.on('close', function(code) { + return proc.on('close', function (code) { if (code !== 0) { logger.warn( { directory, code }, @@ -98,7 +98,7 @@ module.exports = OutputFileFinder = { return callback(null, []) } let fileList = stdout.trim().split('\n') - fileList = fileList.map(function(file) { + fileList = fileList.map(function (file) { // Strip leading directory let path return (path = Path.relative(directory, file)) diff --git a/services/clsi/app/js/OutputFileOptimiser.js b/services/clsi/app/js/OutputFileOptimiser.js index e3b3e60e4e..852737620f 100644 --- a/services/clsi/app/js/OutputFileOptimiser.js +++ b/services/clsi/app/js/OutputFileOptimiser.js @@ -26,10 +26,10 @@ module.exports = OutputFileOptimiser = { // check output file (src) and see if we can optimise it, storing // the result in the build directory (dst) if (callback == null) { - callback = function(error) {} + callback = function (error) {} } if (src.match(/\/output\.pdf$/)) { - return OutputFileOptimiser.checkIfPDFIsOptimised(src, function( + return OutputFileOptimiser.checkIfPDFIsOptimised(src, function ( err, isOptimised ) { @@ -46,12 +46,12 @@ module.exports = OutputFileOptimiser = { checkIfPDFIsOptimised(file, callback) { const SIZE = 16 * 1024 // check the header of the pdf const result = Buffer.alloc(SIZE) // fills with zeroes by default - return fs.open(file, 'r', function(err, fd) { + return fs.open(file, 'r', function (err, fd) { if (err != null) { return callback(err) } return fs.read(fd, result, 0, SIZE, 0, (errRead, bytesRead, buffer) => - fs.close(fd, function(errClose) { + fs.close(fd, function (errClose) { if (errRead != null) { return callback(errRead) } @@ -68,7 +68,7 @@ module.exports = OutputFileOptimiser = { optimisePDF(src, dst, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } const tmpOutput = dst + '.opt' const args = ['--linearize', src, tmpOutput] @@ -77,19 +77,19 @@ module.exports = OutputFileOptimiser = { const timer = new Metrics.Timer('qpdf') const proc = spawn('qpdf', args) let stdout = '' - proc.stdout.setEncoding('utf8').on('data', chunk => (stdout += chunk)) + proc.stdout.setEncoding('utf8').on('data', (chunk) => (stdout += chunk)) callback = _.once(callback) // avoid double call back for error and close event - proc.on('error', function(err) { + proc.on('error', function (err) { logger.warn({ err, args }, 'qpdf failed') return callback(null) }) // ignore the error - return proc.on('close', function(code) { + return proc.on('close', function (code) { timer.done() if (code !== 0) { logger.warn({ code, args }, 'qpdf returned error') return callback(null) // ignore the error } - return fs.rename(tmpOutput, dst, function(err) { + return fs.rename(tmpOutput, dst, function (err) { if (err != null) { logger.warn( { tmpOutput, dst }, diff --git a/services/clsi/app/js/ProjectPersistenceManager.js b/services/clsi/app/js/ProjectPersistenceManager.js index 2c89f13f81..536630a8e0 100644 --- a/services/clsi/app/js/ProjectPersistenceManager.js +++ b/services/clsi/app/js/ProjectPersistenceManager.js @@ -27,9 +27,9 @@ module.exports = ProjectPersistenceManager = { refreshExpiryTimeout(callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } - diskusage.check('/', function(err, stats) { + diskusage.check('/', function (err, stats) { if (err) { logger.err({ err: err }, 'error getting disk usage') return callback(err) @@ -48,9 +48,9 @@ module.exports = ProjectPersistenceManager = { }, markProjectAsJustAccessed(project_id, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } - const job = cb => + const job = (cb) => db.Project.findOrCreate({ where: { project_id } }) .spread((project, created) => project @@ -64,9 +64,9 @@ module.exports = ProjectPersistenceManager = { clearExpiredProjects(callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } - return ProjectPersistenceManager._findExpiredProjectIds(function( + return ProjectPersistenceManager._findExpiredProjectIds(function ( error, project_ids ) { @@ -74,9 +74,9 @@ module.exports = ProjectPersistenceManager = { return callback(error) } logger.log({ project_ids }, 'clearing expired projects') - const jobs = Array.from(project_ids || []).map(project_id => - (project_id => callback => - ProjectPersistenceManager.clearProjectFromCache(project_id, function( + const jobs = Array.from(project_ids || []).map((project_id) => + ((project_id) => (callback) => + ProjectPersistenceManager.clearProjectFromCache(project_id, function ( err ) { if (err != null) { @@ -85,13 +85,13 @@ module.exports = ProjectPersistenceManager = { return callback() }))(project_id) ) - return async.series(jobs, function(error) { + return async.series(jobs, function (error) { if (error != null) { return callback(error) } return CompileManager.clearExpiredProjects( ProjectPersistenceManager.EXPIRY_TIMEOUT, - error => callback() + (error) => callback() ) }) }) @@ -99,16 +99,16 @@ module.exports = ProjectPersistenceManager = { clearProject(project_id, user_id, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } logger.log({ project_id, user_id }, 'clearing project for user') - return CompileManager.clearProject(project_id, user_id, function(error) { + return CompileManager.clearProject(project_id, user_id, function (error) { if (error != null) { return callback(error) } return ProjectPersistenceManager.clearProjectFromCache( project_id, - function(error) { + function (error) { if (error != null) { return callback(error) } @@ -120,17 +120,17 @@ module.exports = ProjectPersistenceManager = { clearProjectFromCache(project_id, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } logger.log({ project_id }, 'clearing project from cache') - return UrlCache.clearProject(project_id, function(error) { + return UrlCache.clearProject(project_id, function (error) { if (error != null) { logger.err({ error, project_id }, 'error clearing project from cache') return callback(error) } return ProjectPersistenceManager._clearProjectFromDatabase( project_id, - function(error) { + function (error) { if (error != null) { logger.err( { error, project_id }, @@ -145,10 +145,10 @@ module.exports = ProjectPersistenceManager = { _clearProjectFromDatabase(project_id, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } logger.log({ project_id }, 'clearing project from database') - const job = cb => + const job = (cb) => db.Project.destroy({ where: { project_id } }) .then(() => cb()) .error(cb) @@ -157,19 +157,19 @@ module.exports = ProjectPersistenceManager = { _findExpiredProjectIds(callback) { if (callback == null) { - callback = function(error, project_ids) {} + callback = function (error, project_ids) {} } - const job = function(cb) { + const job = function (cb) { const keepProjectsFrom = new Date( Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT ) const q = {} q[db.op.lt] = keepProjectsFrom return db.Project.findAll({ where: { lastAccessed: q } }) - .then(projects => + .then((projects) => cb( null, - projects.map(project => project.project_id) + projects.map((project) => project.project_id) ) ) .error(cb) diff --git a/services/clsi/app/js/RequestParser.js b/services/clsi/app/js/RequestParser.js index 342dbc86b8..4377204428 100644 --- a/services/clsi/app/js/RequestParser.js +++ b/services/clsi/app/js/RequestParser.js @@ -27,7 +27,7 @@ module.exports = RequestParser = { parse(body, callback) { let resource if (callback == null) { - callback = function(error, data) {} + callback = function (error, data) {} } const response = {} diff --git a/services/clsi/app/js/ResourceStateManager.js b/services/clsi/app/js/ResourceStateManager.js index 5a5d811c18..07cc78571e 100644 --- a/services/clsi/app/js/ResourceStateManager.js +++ b/services/clsi/app/js/ResourceStateManager.js @@ -41,13 +41,13 @@ module.exports = ResourceStateManager = { saveProjectState(state, resources, basePath, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } const stateFile = Path.join(basePath, this.SYNC_STATE_FILE) if (state == null) { // remove the file if no state passed in logger.log({ state, basePath }, 'clearing sync state') - return fs.unlink(stateFile, function(err) { + return fs.unlink(stateFile, function (err) { if (err != null && err.code !== 'ENOENT') { return callback(err) } else { @@ -56,7 +56,9 @@ module.exports = ResourceStateManager = { }) } else { logger.log({ state, basePath }, 'writing sync state') - const resourceList = Array.from(resources).map(resource => resource.path) + const resourceList = Array.from(resources).map( + (resource) => resource.path + ) return fs.writeFile( stateFile, [...Array.from(resourceList), `stateHash:${state}`].join('\n'), @@ -67,11 +69,11 @@ module.exports = ResourceStateManager = { checkProjectStateMatches(state, basePath, callback) { if (callback == null) { - callback = function(error, resources) {} + callback = function (error, resources) {} } const stateFile = Path.join(basePath, this.SYNC_STATE_FILE) const size = this.SYNC_STATE_MAX_SIZE - return SafeReader.readFile(stateFile, size, 'utf8', function( + return SafeReader.readFile(stateFile, size, 'utf8', function ( err, result, bytesRead @@ -86,7 +88,7 @@ module.exports = ResourceStateManager = { ) } const array = - __guard__(result != null ? result.toString() : undefined, x => + __guard__(result != null ? result.toString() : undefined, (x) => x.split('\n') ) || [] const adjustedLength = Math.max(array.length, 1) @@ -102,7 +104,7 @@ module.exports = ResourceStateManager = { new Errors.FilesOutOfSyncError('invalid state for incremental update') ) } else { - const resources = Array.from(resourceList).map(path => ({ path })) + const resources = Array.from(resourceList).map((path) => ({ path })) return callback(null, resources) } }) @@ -112,11 +114,11 @@ module.exports = ResourceStateManager = { // check the paths are all relative to current directory let file if (callback == null) { - callback = function(error) {} + callback = function (error) {} } for (file of Array.from(resources || [])) { for (const dir of Array.from( - __guard__(file != null ? file.path : undefined, x => x.split('/')) + __guard__(file != null ? file.path : undefined, (x) => x.split('/')) )) { if (dir === '..') { return callback(new Error('relative path in resource file list')) @@ -129,8 +131,8 @@ module.exports = ResourceStateManager = { seenFile[file] = true } const missingFiles = Array.from(resources) - .filter(resource => !seenFile[resource.path]) - .map(resource => resource.path) + .filter((resource) => !seenFile[resource.path]) + .map((resource) => resource.path) if ((missingFiles != null ? missingFiles.length : undefined) > 0) { logger.err( { missingFiles, basePath, allFiles, resources }, diff --git a/services/clsi/app/js/ResourceWriter.js b/services/clsi/app/js/ResourceWriter.js index 97e971e1d5..1625ee15ab 100644 --- a/services/clsi/app/js/ResourceWriter.js +++ b/services/clsi/app/js/ResourceWriter.js @@ -30,7 +30,7 @@ const parallelFileDownloads = settings.parallelFileDownloads || 1 module.exports = ResourceWriter = { syncResourcesToDisk(request, basePath, callback) { if (callback == null) { - callback = function(error, resourceList) {} + callback = function (error, resourceList) {} } if (request.syncType === 'incremental') { logger.log( @@ -40,14 +40,14 @@ module.exports = ResourceWriter = { return ResourceStateManager.checkProjectStateMatches( request.syncState, basePath, - function(error, resourceList) { + function (error, resourceList) { if (error != null) { return callback(error) } return ResourceWriter._removeExtraneousFiles( resourceList, basePath, - function(error, outputFiles, allFiles) { + function (error, outputFiles, allFiles) { if (error != null) { return callback(error) } @@ -55,7 +55,7 @@ module.exports = ResourceWriter = { resourceList, allFiles, basePath, - function(error) { + function (error) { if (error != null) { return callback(error) } @@ -63,7 +63,7 @@ module.exports = ResourceWriter = { request.project_id, request.resources, basePath, - function(error) { + function (error) { if (error != null) { return callback(error) } @@ -85,7 +85,7 @@ module.exports = ResourceWriter = { request.project_id, request.resources, basePath, - function(error) { + function (error) { if (error != null) { return callback(error) } @@ -93,7 +93,7 @@ module.exports = ResourceWriter = { request.syncState, request.resources, basePath, - function(error) { + function (error) { if (error != null) { return callback(error) } @@ -107,15 +107,15 @@ module.exports = ResourceWriter = { saveIncrementalResourcesToDisk(project_id, resources, basePath, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } - return this._createDirectory(basePath, error => { + return this._createDirectory(basePath, (error) => { if (error != null) { return callback(error) } - const jobs = Array.from(resources).map(resource => - (resource => { - return callback => + const jobs = Array.from(resources).map((resource) => + ((resource) => { + return (callback) => this._writeResourceToDisk(project_id, resource, basePath, callback) })(resource) ) @@ -125,19 +125,19 @@ module.exports = ResourceWriter = { saveAllResourcesToDisk(project_id, resources, basePath, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } - return this._createDirectory(basePath, error => { + return this._createDirectory(basePath, (error) => { if (error != null) { return callback(error) } - return this._removeExtraneousFiles(resources, basePath, error => { + return this._removeExtraneousFiles(resources, basePath, (error) => { if (error != null) { return callback(error) } - const jobs = Array.from(resources).map(resource => - (resource => { - return callback => + const jobs = Array.from(resources).map((resource) => + ((resource) => { + return (callback) => this._writeResourceToDisk( project_id, resource, @@ -153,9 +153,9 @@ module.exports = ResourceWriter = { _createDirectory(basePath, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } - return fs.mkdir(basePath, function(err) { + return fs.mkdir(basePath, function (err) { if (err != null) { if (err.code === 'EEXIST') { return callback() @@ -171,15 +171,15 @@ module.exports = ResourceWriter = { _removeExtraneousFiles(resources, basePath, _callback) { if (_callback == null) { - _callback = function(error, outputFiles, allFiles) {} + _callback = function (error, outputFiles, allFiles) {} } const timer = new Metrics.Timer('unlink-output-files') - const callback = function(error, ...result) { + const callback = function (error, ...result) { timer.done() return _callback(error, ...Array.from(result)) } - return OutputFileFinder.findOutputFiles(resources, basePath, function( + return OutputFileFinder.findOutputFiles(resources, basePath, function ( error, outputFiles, allFiles @@ -190,7 +190,7 @@ module.exports = ResourceWriter = { const jobs = [] for (const file of Array.from(outputFiles || [])) { - ;(function(file) { + ;(function (file) { const { path } = file let should_delete = true if ( @@ -242,7 +242,7 @@ module.exports = ResourceWriter = { should_delete = true } if (should_delete) { - return jobs.push(callback => + return jobs.push((callback) => ResourceWriter._deleteFileIfNotDirectory( Path.join(basePath, path), callback @@ -252,7 +252,7 @@ module.exports = ResourceWriter = { })(file) } - return async.series(jobs, function(error) { + return async.series(jobs, function (error) { if (error != null) { return callback(error) } @@ -263,9 +263,9 @@ module.exports = ResourceWriter = { _deleteFileIfNotDirectory(path, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } - return fs.stat(path, function(error, stat) { + return fs.stat(path, function (error, stat) { if (error != null && error.code === 'ENOENT') { return callback() } else if (error != null) { @@ -275,7 +275,7 @@ module.exports = ResourceWriter = { ) return callback(error) } else if (stat.isFile()) { - return fs.unlink(path, function(error) { + return fs.unlink(path, function (error) { if (error != null) { logger.err( { err: error, path }, @@ -294,16 +294,18 @@ module.exports = ResourceWriter = { _writeResourceToDisk(project_id, resource, basePath, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } - return ResourceWriter.checkPath(basePath, resource.path, function( + return ResourceWriter.checkPath(basePath, resource.path, function ( error, path ) { if (error != null) { return callback(error) } - return fs.mkdir(Path.dirname(path), { recursive: true }, function(error) { + return fs.mkdir(Path.dirname(path), { recursive: true }, function ( + error + ) { if (error != null) { return callback(error) } @@ -314,7 +316,7 @@ module.exports = ResourceWriter = { resource.url, path, resource.modified, - function(err) { + function (err) { if (err != null) { logger.err( { diff --git a/services/clsi/app/js/SafeReader.js b/services/clsi/app/js/SafeReader.js index 900826756a..f3188791b1 100644 --- a/services/clsi/app/js/SafeReader.js +++ b/services/clsi/app/js/SafeReader.js @@ -22,9 +22,9 @@ module.exports = SafeReader = { readFile(file, size, encoding, callback) { if (callback == null) { - callback = function(error, result) {} + callback = function (error, result) {} } - return fs.open(file, 'r', function(err, fd) { + return fs.open(file, 'r', function (err, fd) { if (err != null && err.code === 'ENOENT') { return callback() } @@ -34,7 +34,7 @@ module.exports = SafeReader = { // safely return always closing the file const callbackWithClose = (err, ...result) => - fs.close(fd, function(err1) { + fs.close(fd, function (err1) { if (err != null) { return callback(err) } @@ -44,7 +44,7 @@ module.exports = SafeReader = { return callback(null, ...Array.from(result)) }) const buff = Buffer.alloc(size) // fills with zeroes by default - return fs.read(fd, buff, 0, buff.length, 0, function( + return fs.read(fd, buff, 0, buff.length, 0, function ( err, bytesRead, buffer diff --git a/services/clsi/app/js/StaticServerForbidSymlinks.js b/services/clsi/app/js/StaticServerForbidSymlinks.js index 999ae2065f..edde77742c 100644 --- a/services/clsi/app/js/StaticServerForbidSymlinks.js +++ b/services/clsi/app/js/StaticServerForbidSymlinks.js @@ -21,12 +21,12 @@ const Settings = require('settings-sharelatex') const logger = require('logger-sharelatex') const url = require('url') -module.exports = ForbidSymlinks = function(staticFn, root, options) { +module.exports = ForbidSymlinks = function (staticFn, root, options) { const expressStatic = staticFn(root, options) const basePath = Path.resolve(root) - return function(req, res, next) { + return function (req, res, next) { let file, project_id, result - const path = __guard__(url.parse(req.url), x => x.pathname) + const path = __guard__(url.parse(req.url), (x) => x.pathname) // check that the path is of the form /project_id_or_name/path/to/file.log if ((result = path.match(/^\/?([a-zA-Z0-9_-]+)\/(.*)/))) { project_id = result[1] @@ -52,7 +52,7 @@ module.exports = ForbidSymlinks = function(staticFn, root, options) { return res.sendStatus(404) } // check that the requested path is not a symlink - return fs.realpath(requestedFsPath, function(err, realFsPath) { + return fs.realpath(requestedFsPath, function (err, realFsPath) { if (err != null) { if (err.code === 'ENOENT') { return res.sendStatus(404) diff --git a/services/clsi/app/js/TikzManager.js b/services/clsi/app/js/TikzManager.js index 3c57873553..0e39897d60 100644 --- a/services/clsi/app/js/TikzManager.js +++ b/services/clsi/app/js/TikzManager.js @@ -26,7 +26,7 @@ module.exports = TikzManager = { checkMainFile(compileDir, mainFile, resources, callback) { // if there's already an output.tex file, we don't want to touch it if (callback == null) { - callback = function(error, needsMainFile) {} + callback = function (error, needsMainFile) {} } for (const resource of Array.from(resources)) { if (resource.path === 'output.tex') { @@ -35,14 +35,17 @@ module.exports = TikzManager = { } } // if there's no output.tex, see if we are using tikz/pgf or pstool in the main file - return ResourceWriter.checkPath(compileDir, mainFile, function( + return ResourceWriter.checkPath(compileDir, mainFile, function ( error, path ) { if (error != null) { return callback(error) } - return SafeReader.readFile(path, 65536, 'utf8', function(error, content) { + return SafeReader.readFile(path, 65536, 'utf8', function ( + error, + content + ) { if (error != null) { return callback(error) } @@ -64,16 +67,16 @@ module.exports = TikzManager = { injectOutputFile(compileDir, mainFile, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } - return ResourceWriter.checkPath(compileDir, mainFile, function( + return ResourceWriter.checkPath(compileDir, mainFile, function ( error, path ) { if (error != null) { return callback(error) } - return fs.readFile(path, 'utf8', function(error, content) { + return fs.readFile(path, 'utf8', function (error, content) { if (error != null) { return callback(error) } diff --git a/services/clsi/app/js/UrlCache.js b/services/clsi/app/js/UrlCache.js index df9c175a63..e8ee10dc67 100644 --- a/services/clsi/app/js/UrlCache.js +++ b/services/clsi/app/js/UrlCache.js @@ -25,7 +25,7 @@ const async = require('async') module.exports = UrlCache = { downloadUrlToFile(project_id, url, destPath, lastModified, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } return UrlCache._ensureUrlIsInCache( project_id, @@ -35,7 +35,7 @@ module.exports = UrlCache = { if (error != null) { return callback(error) } - return UrlCache._copyFile(pathToCachedUrl, destPath, function(error) { + return UrlCache._copyFile(pathToCachedUrl, destPath, function (error) { if (error != null) { return UrlCache._clearUrlDetails(project_id, url, () => callback(error) @@ -50,9 +50,9 @@ module.exports = UrlCache = { clearProject(project_id, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } - return UrlCache._findAllUrlsInProject(project_id, function(error, urls) { + return UrlCache._findAllUrlsInProject(project_id, function (error, urls) { logger.log( { project_id, url_count: urls.length }, 'clearing project URLs' @@ -60,9 +60,9 @@ module.exports = UrlCache = { if (error != null) { return callback(error) } - const jobs = Array.from(urls || []).map(url => - (url => callback => - UrlCache._clearUrlFromCache(project_id, url, function(error) { + const jobs = Array.from(urls || []).map((url) => + ((url) => (callback) => + UrlCache._clearUrlFromCache(project_id, url, function (error) { if (error != null) { logger.error( { err: error, project_id, url }, @@ -78,7 +78,7 @@ module.exports = UrlCache = { _ensureUrlIsInCache(project_id, url, lastModified, callback) { if (callback == null) { - callback = function(error, pathOnDisk) {} + callback = function (error, pathOnDisk) {} } if (lastModified != null) { // MYSQL only stores dates to an accuracy of a second but the incoming lastModified might have milliseconds. @@ -98,7 +98,7 @@ module.exports = UrlCache = { return UrlFetcher.pipeUrlToFileWithRetry( url, UrlCache._cacheFilePathForUrl(project_id, url), - error => { + (error) => { if (error != null) { return callback(error) } @@ -106,7 +106,7 @@ module.exports = UrlCache = { project_id, url, lastModified, - error => { + (error) => { if (error != null) { return callback(error) } @@ -128,12 +128,12 @@ module.exports = UrlCache = { _doesUrlNeedDownloading(project_id, url, lastModified, callback) { if (callback == null) { - callback = function(error, needsDownloading) {} + callback = function (error, needsDownloading) {} } if (lastModified == null) { return callback(null, true) } - return UrlCache._findUrlDetails(project_id, url, function( + return UrlCache._findUrlDetails(project_id, url, function ( error, urlDetails ) { @@ -153,14 +153,7 @@ module.exports = UrlCache = { }, _cacheFileNameForUrl(project_id, url) { - return ( - project_id + - ':' + - crypto - .createHash('md5') - .update(url) - .digest('hex') - ) + return project_id + ':' + crypto.createHash('md5').update(url).digest('hex') }, _cacheFilePathForUrl(project_id, url) { @@ -172,14 +165,14 @@ module.exports = UrlCache = { _copyFile(from, to, _callback) { if (_callback == null) { - _callback = function(error) {} + _callback = function (error) {} } - const callbackOnce = function(error) { + const callbackOnce = function (error) { if (error != null) { logger.error({ err: error, from, to }, 'error copying file from cache') } _callback(error) - return (_callback = function() {}) + return (_callback = function () {}) } const writeStream = fs.createWriteStream(to) const readStream = fs.createReadStream(from) @@ -191,13 +184,15 @@ module.exports = UrlCache = { _clearUrlFromCache(project_id, url, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } - return UrlCache._clearUrlDetails(project_id, url, function(error) { + return UrlCache._clearUrlDetails(project_id, url, function (error) { if (error != null) { return callback(error) } - return UrlCache._deleteUrlCacheFromDisk(project_id, url, function(error) { + return UrlCache._deleteUrlCacheFromDisk(project_id, url, function ( + error + ) { if (error != null) { return callback(error) } @@ -208,9 +203,9 @@ module.exports = UrlCache = { _deleteUrlCacheFromDisk(project_id, url, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } - return fs.unlink(UrlCache._cacheFilePathForUrl(project_id, url), function( + return fs.unlink(UrlCache._cacheFilePathForUrl(project_id, url), function ( error ) { if (error != null && error.code !== 'ENOENT') { @@ -224,20 +219,20 @@ module.exports = UrlCache = { _findUrlDetails(project_id, url, callback) { if (callback == null) { - callback = function(error, urlDetails) {} + callback = function (error, urlDetails) {} } - const job = cb => + const job = (cb) => db.UrlCache.findOne({ where: { url, project_id } }) - .then(urlDetails => cb(null, urlDetails)) + .then((urlDetails) => cb(null, urlDetails)) .error(cb) return dbQueue.queue.push(job, callback) }, _updateOrCreateUrlDetails(project_id, url, lastModified, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } - const job = cb => + const job = (cb) => db.UrlCache.findOrCreate({ where: { url, project_id } }) .spread((urlDetails, created) => urlDetails @@ -251,9 +246,9 @@ module.exports = UrlCache = { _clearUrlDetails(project_id, url, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } - const job = cb => + const job = (cb) => db.UrlCache.destroy({ where: { url, project_id } }) .then(() => cb(null)) .error(cb) @@ -262,14 +257,14 @@ module.exports = UrlCache = { _findAllUrlsInProject(project_id, callback) { if (callback == null) { - callback = function(error, urls) {} + callback = function (error, urls) {} } - const job = cb => + const job = (cb) => db.UrlCache.findAll({ where: { project_id } }) - .then(urlEntries => + .then((urlEntries) => cb( null, - urlEntries.map(entry => entry.url) + urlEntries.map((entry) => entry.url) ) ) .error(cb) diff --git a/services/clsi/app/js/UrlFetcher.js b/services/clsi/app/js/UrlFetcher.js index f9f993f363..b3ed8c465d 100644 --- a/services/clsi/app/js/UrlFetcher.js +++ b/services/clsi/app/js/UrlFetcher.js @@ -24,7 +24,7 @@ const oneMinute = 60 * 1000 module.exports = UrlFetcher = { pipeUrlToFileWithRetry(url, filePath, callback) { - const doDownload = function(cb) { + const doDownload = function (cb) { UrlFetcher.pipeUrlToFile(url, filePath, cb) } async.retry(3, doDownload, callback) @@ -32,14 +32,14 @@ module.exports = UrlFetcher = { pipeUrlToFile(url, filePath, _callback) { if (_callback == null) { - _callback = function(error) {} + _callback = function (error) {} } - const callbackOnce = function(error) { + const callbackOnce = function (error) { if (timeoutHandler != null) { clearTimeout(timeoutHandler) } _callback(error) - return (_callback = function() {}) + return (_callback = function () {}) } if (settings.filestoreDomainOveride != null) { @@ -47,7 +47,7 @@ module.exports = UrlFetcher = { url = `${settings.filestoreDomainOveride}${p}` } var timeoutHandler = setTimeout( - function() { + function () { timeoutHandler = null logger.error({ url, filePath }, 'Timed out downloading file to cache') return callbackOnce( @@ -63,7 +63,7 @@ module.exports = UrlFetcher = { urlStream.pause() // stop data flowing until we are ready // attach handlers before setting up pipes - urlStream.on('error', function(error) { + urlStream.on('error', function (error) { logger.error({ err: error, url, filePath }, 'error downloading url') return callbackOnce( error || new Error(`Something went wrong downloading the URL ${url}`) @@ -74,17 +74,17 @@ module.exports = UrlFetcher = { logger.log({ url, filePath }, 'finished downloading file into cache') ) - return urlStream.on('response', function(res) { + return urlStream.on('response', function (res) { if (res.statusCode >= 200 && res.statusCode < 300) { const fileStream = fs.createWriteStream(filePath) // attach handlers before setting up pipes - fileStream.on('error', function(error) { + fileStream.on('error', function (error) { logger.error( { err: error, url, filePath }, 'error writing file into cache' ) - return fs.unlink(filePath, function(err) { + return fs.unlink(filePath, function (err) { if (err != null) { logger.err({ err, filePath }, 'error deleting file from cache') } @@ -92,7 +92,7 @@ module.exports = UrlFetcher = { }) }) - fileStream.on('finish', function() { + fileStream.on('finish', function () { logger.log({ url, filePath }, 'finished writing file into cache') return callbackOnce() }) diff --git a/services/clsi/app/js/db.js b/services/clsi/app/js/db.js index 15510ae34c..6b7e50ec9c 100644 --- a/services/clsi/app/js/db.js +++ b/services/clsi/app/js/db.js @@ -62,6 +62,6 @@ module.exports = { return sequelize .sync() .then(() => logger.log('db sync complete')) - .catch(err => console.log(err, 'error syncing')) + .catch((err) => console.log(err, 'error syncing')) } } diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index 78ef1d04ab..b2866578d9 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -1,11 +1,9 @@ clsi ---acceptance-creds=None --data-dirs=cache,compiles,db --dependencies= --docker-repos=gcr.io/overleaf-ops --env-add= --env-pass-through=TEXLIVE_IMAGE ---language=es --node-version=10.21.0 --public-repo=True ---script-version=2.1.0 +--script-version=3.3.2 diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index facbd5fc92..35f4878004 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -10,6 +10,7 @@ services: command: npm run test:unit:_run environment: NODE_ENV: test + NODE_OPTIONS: "--unhandled-rejections=strict" test_acceptance: @@ -25,6 +26,7 @@ services: POSTGRES_HOST: postgres MOCHA_GREP: ${MOCHA_GREP} NODE_ENV: test + NODE_OPTIONS: "--unhandled-rejections=strict" TEXLIVE_IMAGE: command: npm run test:acceptance:_run diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 6fc9eab26b..052d909832 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -15,7 +15,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 test_acceptance: build: @@ -35,5 +36,6 @@ services: MOCHA_GREP: ${MOCHA_GREP} LOG_LEVEL: ERROR NODE_ENV: test - command: npm run test:acceptance + NODE_OPTIONS: "--unhandled-rejections=strict" + command: npm run --silent test:acceptance diff --git a/services/clsi/nodemon.json b/services/clsi/nodemon.json index 5826281b84..e3e8817d90 100644 --- a/services/clsi/nodemon.json +++ b/services/clsi/nodemon.json @@ -8,7 +8,6 @@ "execMap": { "js": "npm run start" }, - "watch": [ "app/js/", "app.js", diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index 63d9c1e768..cc358212d1 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -4770,9 +4770,9 @@ "dev": true }, "prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", + "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", "dev": true }, "prettier-eslint": { @@ -5004,6 +5004,12 @@ "mimic-fn": "^1.0.0" } }, + "prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "dev": true + }, "restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", diff --git a/services/clsi/package.json b/services/clsi/package.json index df30ef77df..a526075955 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -13,7 +13,7 @@ "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" }, @@ -59,7 +59,7 @@ "eslint-plugin-react": "^7.19.0", "eslint-plugin-standard": "^4.0.1", "mocha": "^7.1.0", - "prettier": "^1.19.1", + "prettier": "^2.0.0", "prettier-eslint-cli": "^5.0.0", "sandboxed-module": "^2.0.3", "sinon": "~9.0.1", diff --git a/services/clsi/test/acceptance/js/AllowedImageNames.js b/services/clsi/test/acceptance/js/AllowedImageNames.js index a9b3996e1e..7e38954e9c 100644 --- a/services/clsi/test/acceptance/js/AllowedImageNames.js +++ b/services/clsi/test/acceptance/js/AllowedImageNames.js @@ -2,8 +2,8 @@ const Client = require('./helpers/Client') const ClsiApp = require('./helpers/ClsiApp') const { expect } = require('chai') -describe('AllowedImageNames', function() { - beforeEach(function(done) { +describe('AllowedImageNames', function () { + beforeEach(function (done) { this.project_id = Client.randomId() this.request = { options: { @@ -24,8 +24,8 @@ Hello world ClsiApp.ensureRunning(done) }) - describe('with a valid name', function() { - beforeEach(function(done) { + describe('with a valid name', function () { + beforeEach(function (done) { this.request.options.imageName = process.env.TEXLIVE_IMAGE Client.compile(this.project_id, this.request, (error, res, body) => { @@ -35,11 +35,11 @@ Hello world done(error) }) }) - it('should return success', function() { + it('should return success', function () { expect(this.res.statusCode).to.equal(200) }) - it('should return a PDF', function() { + it('should return a PDF', function () { let pdf try { pdf = Client.getOutputFile(this.body, 'pdf') @@ -48,8 +48,8 @@ Hello world }) }) - describe('with an invalid name', function() { - beforeEach(function(done) { + describe('with an invalid name', function () { + beforeEach(function (done) { this.request.options.imageName = 'something/evil:1337' Client.compile(this.project_id, this.request, (error, res, body) => { this.error = error @@ -58,11 +58,11 @@ Hello world done(error) }) }) - it('should return non success', function() { + it('should return non success', function () { expect(this.res.statusCode).to.not.equal(200) }) - it('should not return a PDF', function() { + it('should not return a PDF', function () { let pdf try { pdf = Client.getOutputFile(this.body, 'pdf') @@ -71,11 +71,11 @@ Hello world }) }) - describe('wordcount', function() { - beforeEach(function(done) { + describe('wordcount', function () { + beforeEach(function (done) { Client.compile(this.project_id, this.request, done) }) - it('should error out with an invalid imageName', function() { + it('should error out with an invalid imageName', function () { Client.wordcountWithImage( this.project_id, 'main.tex', @@ -86,7 +86,7 @@ Hello world ) }) - it('should produce a texcout a valid imageName', function() { + it('should produce a texcout a valid imageName', function () { Client.wordcountWithImage( this.project_id, 'main.tex', diff --git a/services/clsi/test/acceptance/js/BrokenLatexFileTests.js b/services/clsi/test/acceptance/js/BrokenLatexFileTests.js index b34d23cad5..c26e0c4018 100644 --- a/services/clsi/test/acceptance/js/BrokenLatexFileTests.js +++ b/services/clsi/test/acceptance/js/BrokenLatexFileTests.js @@ -13,8 +13,8 @@ const request = require('request') require('chai').should() const ClsiApp = require('./helpers/ClsiApp') -describe('Broken LaTeX file', function() { - before(function(done) { +describe('Broken LaTeX file', function () { + before(function (done) { this.broken_request = { resources: [ { @@ -44,8 +44,8 @@ Hello world return ClsiApp.ensureRunning(done) }) - describe('on first run', function() { - before(function(done) { + describe('on first run', function () { + before(function (done) { this.project_id = Client.randomId() return Client.compile( this.project_id, @@ -59,13 +59,13 @@ Hello world ) }) - return it('should return a failure status', function() { + return it('should return a failure status', function () { return this.body.compile.status.should.equal('failure') }) }) - return describe('on second run', function() { - before(function(done) { + return describe('on second run', function () { + before(function (done) { this.project_id = Client.randomId() return Client.compile(this.project_id, this.correct_request, () => { return Client.compile( @@ -81,7 +81,7 @@ Hello world }) }) - return it('should return a failure status', function() { + return it('should return a failure status', function () { return this.body.compile.status.should.equal('failure') }) }) diff --git a/services/clsi/test/acceptance/js/DeleteOldFilesTest.js b/services/clsi/test/acceptance/js/DeleteOldFilesTest.js index 83d7c96d0d..ae2d7c791e 100644 --- a/services/clsi/test/acceptance/js/DeleteOldFilesTest.js +++ b/services/clsi/test/acceptance/js/DeleteOldFilesTest.js @@ -13,8 +13,8 @@ const request = require('request') require('chai').should() const ClsiApp = require('./helpers/ClsiApp') -describe('Deleting Old Files', function() { - before(function(done) { +describe('Deleting Old Files', function () { + before(function (done) { this.request = { resources: [ { @@ -31,8 +31,8 @@ Hello world return ClsiApp.ensureRunning(done) }) - return describe('on first run', function() { - before(function(done) { + return describe('on first run', function () { + before(function (done) { this.project_id = Client.randomId() return Client.compile( this.project_id, @@ -46,12 +46,12 @@ Hello world ) }) - it('should return a success status', function() { + it('should return a success status', function () { return this.body.compile.status.should.equal('success') }) - return describe('after file has been deleted', function() { - before(function(done) { + return describe('after file has been deleted', function () { + before(function (done) { this.request.resources = [] return Client.compile( this.project_id, @@ -65,7 +65,7 @@ Hello world ) }) - return it('should return a failure status', function() { + return it('should return a failure status', function () { return this.body.compile.status.should.equal('failure') }) }) diff --git a/services/clsi/test/acceptance/js/ExampleDocumentTests.js b/services/clsi/test/acceptance/js/ExampleDocumentTests.js index 5435a787c9..5a67a0febc 100644 --- a/services/clsi/test/acceptance/js/ExampleDocumentTests.js +++ b/services/clsi/test/acceptance/js/ExampleDocumentTests.js @@ -24,7 +24,7 @@ const ChildProcess = require('child_process') const ClsiApp = require('./helpers/ClsiApp') const logger = require('logger-sharelatex') const Path = require('path') -const fixturePath = path => { +const fixturePath = (path) => { if (path.slice(0, 3) === 'tmp') { return '/tmp/clsi_acceptance_tests' + path.slice(3) } @@ -41,23 +41,23 @@ console.log( const MOCHA_LATEX_TIMEOUT = 60 * 1000 -const convertToPng = function(pdfPath, pngPath, callback) { +const convertToPng = function (pdfPath, pngPath, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } const command = `convert ${fixturePath(pdfPath)} ${fixturePath(pngPath)}` console.log('COMMAND') console.log(command) const convert = ChildProcess.exec(command) const stdout = '' - convert.stdout.on('data', chunk => console.log('STDOUT', chunk.toString())) - convert.stderr.on('data', chunk => console.log('STDERR', chunk.toString())) + convert.stdout.on('data', (chunk) => console.log('STDOUT', chunk.toString())) + convert.stderr.on('data', (chunk) => console.log('STDERR', chunk.toString())) return convert.on('exit', () => callback()) } -const compare = function(originalPath, generatedPath, callback) { +const compare = function (originalPath, generatedPath, callback) { if (callback == null) { - callback = function(error, same) {} + callback = function (error, same) {} } const diff_file = `${fixturePath(generatedPath)}-diff.png` const proc = ChildProcess.exec( @@ -66,11 +66,11 @@ const compare = function(originalPath, generatedPath, callback) { )} ${diff_file}` ) let stderr = '' - proc.stderr.on('data', chunk => (stderr += chunk)) + proc.stderr.on('data', (chunk) => (stderr += chunk)) return proc.on('exit', () => { if (stderr.trim() === '0 (0)') { // remove output diff if test matches expected image - fs.unlink(diff_file, err => { + fs.unlink(diff_file, (err) => { if (err) { throw err } @@ -83,14 +83,14 @@ const compare = function(originalPath, generatedPath, callback) { }) } -const checkPdfInfo = function(pdfPath, callback) { +const checkPdfInfo = function (pdfPath, callback) { if (callback == null) { - callback = function(error, output) {} + callback = function (error, output) {} } const proc = ChildProcess.exec(`pdfinfo ${fixturePath(pdfPath)}`) let stdout = '' - proc.stdout.on('data', chunk => (stdout += chunk)) - proc.stderr.on('data', chunk => console.log('STDERR', chunk.toString())) + proc.stdout.on('data', (chunk) => (stdout += chunk)) + proc.stderr.on('data', (chunk) => console.log('STDERR', chunk.toString())) return proc.on('exit', () => { if (stdout.match(/Optimized:\s+yes/)) { return callback(null, true) @@ -100,11 +100,11 @@ const checkPdfInfo = function(pdfPath, callback) { }) } -const compareMultiplePages = function(project_id, callback) { +const compareMultiplePages = function (project_id, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } - var compareNext = function(page_no, callback) { + var compareNext = function (page_no, callback) { const path = `tmp/${project_id}-source-${page_no}.png` return fs.stat(fixturePath(path), (error, stat) => { if (error != null) { @@ -127,23 +127,23 @@ const compareMultiplePages = function(project_id, callback) { return compareNext(0, callback) } -const comparePdf = function(project_id, example_dir, callback) { +const comparePdf = function (project_id, example_dir, callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } console.log('CONVERT') console.log(`tmp/${project_id}.pdf`, `tmp/${project_id}-generated.png`) return convertToPng( `tmp/${project_id}.pdf`, `tmp/${project_id}-generated.png`, - error => { + (error) => { if (error != null) { throw error } return convertToPng( `examples/${example_dir}/output.pdf`, `tmp/${project_id}-source.png`, - error => { + (error) => { if (error != null) { throw error } @@ -163,7 +163,7 @@ const comparePdf = function(project_id, example_dir, callback) { } ) } else { - return compareMultiplePages(project_id, error => { + return compareMultiplePages(project_id, (error) => { if (error != null) { throw error } @@ -178,9 +178,14 @@ const comparePdf = function(project_id, example_dir, callback) { ) } -const downloadAndComparePdf = function(project_id, example_dir, url, callback) { +const downloadAndComparePdf = function ( + project_id, + example_dir, + url, + callback +) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } const writeStream = fs.createWriteStream(fixturePath(`tmp/${project_id}.pdf`)) request.get(url).pipe(writeStream) @@ -198,85 +203,96 @@ const downloadAndComparePdf = function(project_id, example_dir, url, callback) { Client.runServer(4242, fixturePath('examples')) -describe('Example Documents', function() { - before(function(done) { +describe('Example Documents', function () { + before(function (done) { ClsiApp.ensureRunning(done) }) - before(function(done) { + before(function (done) { fsExtra.remove(fixturePath('tmp'), done) }) - before(function(done) { + before(function (done) { fs.mkdir(fixturePath('tmp'), done) }) - after(function(done) { + after(function (done) { fsExtra.remove(fixturePath('tmp'), done) }) - return Array.from(fs.readdirSync(fixturePath('examples'))).map(example_dir => - (example_dir => - describe(example_dir, function() { - before(function() { - return (this.project_id = Client.randomId() + '_' + example_dir) - }) + return Array.from(fs.readdirSync(fixturePath('examples'))).map( + (example_dir) => + ((example_dir) => + describe(example_dir, function () { + before(function () { + return (this.project_id = Client.randomId() + '_' + example_dir) + }) - it('should generate the correct pdf', function(done) { - this.timeout(MOCHA_LATEX_TIMEOUT) - return Client.compileDirectory( - this.project_id, - fixturePath('examples'), - example_dir, - 4242, - (error, res, body) => { - if ( - error || - __guard__( - body != null ? body.compile : undefined, - x => x.status - ) === 'failure' - ) { - console.log('DEBUG: error', error, 'body', JSON.stringify(body)) - return done(new Error('Compile failed')) + it('should generate the correct pdf', function (done) { + this.timeout(MOCHA_LATEX_TIMEOUT) + return Client.compileDirectory( + this.project_id, + fixturePath('examples'), + example_dir, + 4242, + (error, res, body) => { + if ( + error || + __guard__( + body != null ? body.compile : undefined, + (x) => x.status + ) === 'failure' + ) { + console.log( + 'DEBUG: error', + error, + 'body', + JSON.stringify(body) + ) + return done(new Error('Compile failed')) + } + const pdf = Client.getOutputFile(body, 'pdf') + return downloadAndComparePdf( + this.project_id, + example_dir, + pdf.url, + done + ) } - const pdf = Client.getOutputFile(body, 'pdf') - return downloadAndComparePdf( - this.project_id, - example_dir, - pdf.url, - done - ) - } - ) - }) + ) + }) - return it('should generate the correct pdf on the second run as well', function(done) { - this.timeout(MOCHA_LATEX_TIMEOUT) - return Client.compileDirectory( - this.project_id, - fixturePath('examples'), - example_dir, - 4242, - (error, res, body) => { - if ( - error || - __guard__( - body != null ? body.compile : undefined, - x => x.status - ) === 'failure' - ) { - console.log('DEBUG: error', error, 'body', JSON.stringify(body)) - return done(new Error('Compile failed')) + return it('should generate the correct pdf on the second run as well', function (done) { + this.timeout(MOCHA_LATEX_TIMEOUT) + return Client.compileDirectory( + this.project_id, + fixturePath('examples'), + example_dir, + 4242, + (error, res, body) => { + if ( + error || + __guard__( + body != null ? body.compile : undefined, + (x) => x.status + ) === 'failure' + ) { + console.log( + 'DEBUG: error', + error, + 'body', + JSON.stringify(body) + ) + return done(new Error('Compile failed')) + } + const pdf = Client.getOutputFile(body, 'pdf') + return downloadAndComparePdf( + this.project_id, + example_dir, + pdf.url, + done + ) } - const pdf = Client.getOutputFile(body, 'pdf') - return downloadAndComparePdf( - this.project_id, - example_dir, - pdf.url, - done - ) - } - ) - }) - }))(example_dir) + ) + }) + }))(example_dir) ) }) diff --git a/services/clsi/test/acceptance/js/SimpleLatexFileTests.js b/services/clsi/test/acceptance/js/SimpleLatexFileTests.js index 447e1b635a..dd45802692 100644 --- a/services/clsi/test/acceptance/js/SimpleLatexFileTests.js +++ b/services/clsi/test/acceptance/js/SimpleLatexFileTests.js @@ -13,8 +13,8 @@ const request = require('request') require('chai').should() const ClsiApp = require('./helpers/ClsiApp') -describe('Simple LaTeX file', function() { - before(function(done) { +describe('Simple LaTeX file', function () { + before(function (done) { this.project_id = Client.randomId() this.request = { resources: [ @@ -43,17 +43,17 @@ Hello world }) }) - it('should return the PDF', function() { + it('should return the PDF', function () { const pdf = Client.getOutputFile(this.body, 'pdf') return pdf.type.should.equal('pdf') }) - it('should return the log', function() { + it('should return the log', function () { const log = Client.getOutputFile(this.body, 'log') return log.type.should.equal('log') }) - it('should provide the pdf for download', function(done) { + it('should provide the pdf for download', function (done) { const pdf = Client.getOutputFile(this.body, 'pdf') return request.get(pdf.url, (error, res, body) => { res.statusCode.should.equal(200) @@ -61,7 +61,7 @@ Hello world }) }) - return it('should provide the log for download', function(done) { + return it('should provide the log for download', function (done) { const log = Client.getOutputFile(this.body, 'pdf') return request.get(log.url, (error, res, body) => { res.statusCode.should.equal(200) diff --git a/services/clsi/test/acceptance/js/SynctexTests.js b/services/clsi/test/acceptance/js/SynctexTests.js index 1140f3fade..afe6330df3 100644 --- a/services/clsi/test/acceptance/js/SynctexTests.js +++ b/services/clsi/test/acceptance/js/SynctexTests.js @@ -16,8 +16,8 @@ const { expect } = require('chai') const ClsiApp = require('./helpers/ClsiApp') const crypto = require('crypto') -describe('Syncing', function() { - before(function(done) { +describe('Syncing', function () { + before(function (done) { const content = `\ \\documentclass{article} \\begin{document} @@ -47,8 +47,8 @@ Hello world }) }) - describe('from code to pdf', function() { - return it('should return the correct location', function(done) { + describe('from code to pdf', function () { + return it('should return the correct location', function (done) { return Client.syncFromCode( this.project_id, 'main.tex', @@ -69,8 +69,8 @@ Hello world }) }) - describe('from pdf to code', function() { - return it('should return the correct location', function(done) { + describe('from pdf to code', function () { + return it('should return the correct location', function (done) { return Client.syncFromPdf( this.project_id, 1, @@ -89,12 +89,12 @@ Hello world }) }) - describe('when the project directory is not available', function() { - before(function() { + describe('when the project directory is not available', function () { + before(function () { this.other_project_id = Client.randomId() }) - describe('from code to pdf', function() { - it('should return a 404 response', function(done) { + describe('from code to pdf', function () { + it('should return a 404 response', function (done) { return Client.syncFromCode( this.other_project_id, 'main.tex', @@ -110,8 +110,8 @@ Hello world ) }) }) - describe('from pdf to code', function() { - it('should return a 404 response', function(done) { + describe('from pdf to code', function () { + it('should return a 404 response', function (done) { return Client.syncFromPdf( this.other_project_id, 1, @@ -129,8 +129,8 @@ Hello world }) }) - describe('when the synctex file is not available', function() { - before(function(done) { + describe('when the synctex file is not available', function () { + before(function (done) { this.broken_project_id = Client.randomId() const content = 'this is not valid tex' // not a valid tex file this.request = { @@ -153,8 +153,8 @@ Hello world ) }) - describe('from code to pdf', function() { - it('should return a 404 response', function(done) { + describe('from code to pdf', function () { + it('should return a 404 response', function (done) { return Client.syncFromCode( this.broken_project_id, 'main.tex', @@ -170,8 +170,8 @@ Hello world ) }) }) - describe('from pdf to code', function() { - it('should return a 404 response', function(done) { + describe('from pdf to code', function () { + it('should return a 404 response', function (done) { return Client.syncFromPdf( this.broken_project_id, 1, diff --git a/services/clsi/test/acceptance/js/TimeoutTests.js b/services/clsi/test/acceptance/js/TimeoutTests.js index f6812e8c6e..36bcf9aa24 100644 --- a/services/clsi/test/acceptance/js/TimeoutTests.js +++ b/services/clsi/test/acceptance/js/TimeoutTests.js @@ -13,8 +13,8 @@ const request = require('request') require('chai').should() const ClsiApp = require('./helpers/ClsiApp') -describe('Timed out compile', function() { - before(function(done) { +describe('Timed out compile', function () { + before(function (done) { this.request = { options: { timeout: 10 @@ -47,16 +47,16 @@ describe('Timed out compile', function() { }) }) - it('should return a timeout error', function() { + it('should return a timeout error', function () { return this.body.compile.error.should.equal('container timed out') }) - it('should return a timedout status', function() { + it('should return a timedout status', function () { return this.body.compile.status.should.equal('timedout') }) - return it('should return the log output file name', function() { - const outputFilePaths = this.body.compile.outputFiles.map(x => x.path) + return it('should return the log output file name', function () { + const outputFilePaths = this.body.compile.outputFiles.map((x) => x.path) return outputFilePaths.should.include('output.log') }) }) diff --git a/services/clsi/test/acceptance/js/UrlCachingTests.js b/services/clsi/test/acceptance/js/UrlCachingTests.js index b86541b681..03927ea1ef 100644 --- a/services/clsi/test/acceptance/js/UrlCachingTests.js +++ b/services/clsi/test/acceptance/js/UrlCachingTests.js @@ -35,17 +35,15 @@ const Server = { getFile() {}, randomId() { - return Math.random() - .toString(16) - .slice(2) + return Math.random().toString(16).slice(2) } } Server.run() -describe('Url Caching', function() { - describe('Downloading an image for the first time', function() { - before(function(done) { +describe('Url Caching', function () { + describe('Downloading an image for the first time', function () { + before(function (done) { this.project_id = Client.randomId() this.file = `${Server.randomId()}/lion.png` this.request = { @@ -82,17 +80,17 @@ describe('Url Caching', function() { }) }) - afterEach(function() { + afterEach(function () { return Server.getFile.restore() }) - return it('should download the image', function() { + return it('should download the image', function () { return Server.getFile.calledWith(`/${this.file}`).should.equal(true) }) }) - describe('When an image is in the cache and the last modified date is unchanged', function() { - before(function(done) { + describe('When an image is in the cache and the last modified date is unchanged', function () { + before(function (done) { this.project_id = Client.randomId() this.file = `${Server.randomId()}/lion.png` this.request = { @@ -137,17 +135,17 @@ describe('Url Caching', function() { ) }) - after(function() { + after(function () { return Server.getFile.restore() }) - return it('should not download the image again', function() { + return it('should not download the image again', function () { return Server.getFile.called.should.equal(false) }) }) - describe('When an image is in the cache and the last modified date is advanced', function() { - before(function(done) { + describe('When an image is in the cache and the last modified date is advanced', function () { + before(function (done) { this.project_id = Client.randomId() this.file = `${Server.randomId()}/lion.png` this.request = { @@ -193,17 +191,17 @@ describe('Url Caching', function() { ) }) - afterEach(function() { + afterEach(function () { return Server.getFile.restore() }) - return it('should download the image again', function() { + return it('should download the image again', function () { return Server.getFile.called.should.equal(true) }) }) - describe('When an image is in the cache and the last modified date is further in the past', function() { - before(function(done) { + describe('When an image is in the cache and the last modified date is further in the past', function () { + before(function (done) { this.project_id = Client.randomId() this.file = `${Server.randomId()}/lion.png` this.request = { @@ -249,17 +247,17 @@ describe('Url Caching', function() { ) }) - afterEach(function() { + afterEach(function () { return Server.getFile.restore() }) - return it('should not download the image again', function() { + return it('should not download the image again', function () { return Server.getFile.called.should.equal(false) }) }) - describe('When an image is in the cache and the last modified date is not specified', function() { - before(function(done) { + describe('When an image is in the cache and the last modified date is not specified', function () { + before(function (done) { this.project_id = Client.randomId() this.file = `${Server.randomId()}/lion.png` this.request = { @@ -305,17 +303,17 @@ describe('Url Caching', function() { ) }) - afterEach(function() { + afterEach(function () { return Server.getFile.restore() }) - return it('should download the image again', function() { + return it('should download the image again', function () { return Server.getFile.called.should.equal(true) }) }) - return describe('After clearing the cache', function() { - before(function(done) { + return describe('After clearing the cache', function () { + before(function (done) { this.project_id = Client.randomId() this.file = `${Server.randomId()}/lion.png` this.request = { @@ -338,7 +336,7 @@ describe('Url Caching', function() { ] } - return Client.compile(this.project_id, this.request, error => { + return Client.compile(this.project_id, this.request, (error) => { if (error != null) { throw error } @@ -361,11 +359,11 @@ describe('Url Caching', function() { }) }) - afterEach(function() { + afterEach(function () { return Server.getFile.restore() }) - return it('should download the image again', function() { + return it('should download the image again', function () { return Server.getFile.called.should.equal(true) }) }) diff --git a/services/clsi/test/acceptance/js/WordcountTests.js b/services/clsi/test/acceptance/js/WordcountTests.js index 8721857030..73a11f7693 100644 --- a/services/clsi/test/acceptance/js/WordcountTests.js +++ b/services/clsi/test/acceptance/js/WordcountTests.js @@ -17,8 +17,8 @@ const path = require('path') const fs = require('fs') const ClsiApp = require('./helpers/ClsiApp') -describe('Syncing', function() { - before(function(done) { +describe('Syncing', function () { + before(function (done) { this.request = { resources: [ { @@ -45,8 +45,8 @@ describe('Syncing', function() { }) }) - return describe('wordcount file', function() { - return it('should return wordcount info', function(done) { + return describe('wordcount file', function () { + return it('should return wordcount info', function (done) { return Client.wordcount(this.project_id, 'main.tex', (error, result) => { if (error != null) { throw error diff --git a/services/clsi/test/acceptance/js/helpers/Client.js b/services/clsi/test/acceptance/js/helpers/Client.js index c940e305f8..7cd0c1148b 100644 --- a/services/clsi/test/acceptance/js/helpers/Client.js +++ b/services/clsi/test/acceptance/js/helpers/Client.js @@ -23,14 +23,12 @@ module.exports = Client = { host: Settings.apis.clsi.url, randomId() { - return Math.random() - .toString(16) - .slice(2) + return Math.random().toString(16).slice(2) }, compile(project_id, data, callback) { if (callback == null) { - callback = function(error, res, body) {} + callback = function (error, res, body) {} } return request.post( { @@ -45,7 +43,7 @@ module.exports = Client = { clearCache(project_id, callback) { if (callback == null) { - callback = function(error, res, body) {} + callback = function (error, res, body) {} } return request.del(`${this.host}/project/${project_id}`, callback) }, @@ -64,7 +62,7 @@ module.exports = Client = { const app = express() app.use(express.static(directory)) console.log('starting test server on', port, host) - return app.listen(port, host).on('error', error => { + return app.listen(port, host).on('error', (error) => { console.error('error starting server:', error.message) return process.exit(1) }) @@ -72,7 +70,7 @@ module.exports = Client = { syncFromCode(project_id, file, line, column, callback) { if (callback == null) { - callback = function(error, pdfPositions) {} + callback = function (error, pdfPositions) {} } return request.get( { @@ -95,7 +93,7 @@ module.exports = Client = { syncFromPdf(project_id, page, h, v, callback) { if (callback == null) { - callback = function(error, pdfPositions) {} + callback = function (error, pdfPositions) {} } return request.get( { @@ -118,7 +116,7 @@ module.exports = Client = { compileDirectory(project_id, baseDirectory, directory, serverPort, callback) { if (callback == null) { - callback = function(error, res, body) {} + callback = function (error, res, body) {} } const resources = [] let entities = fs.readdirSync(`${baseDirectory}/${directory}`) @@ -130,7 +128,7 @@ module.exports = Client = { entities = entities.concat( fs .readdirSync(`${baseDirectory}/${directory}/${entity}`) - .map(subEntity => { + .map((subEntity) => { if (subEntity === 'main.tex') { rootResourcePath = `${entity}/${subEntity}` } @@ -195,7 +193,7 @@ module.exports = Client = { wordcountWithImage(project_id, file, image, callback) { if (callback == null) { - callback = function(error, pdfPositions) {} + callback = function (error, pdfPositions) {} } return request.get( { diff --git a/services/clsi/test/acceptance/js/helpers/ClsiApp.js b/services/clsi/test/acceptance/js/helpers/ClsiApp.js index f8038460cf..160b07e51e 100644 --- a/services/clsi/test/acceptance/js/helpers/ClsiApp.js +++ b/services/clsi/test/acceptance/js/helpers/ClsiApp.js @@ -23,7 +23,7 @@ module.exports = { callbacks: [], ensureRunning(callback) { if (callback == null) { - callback = function(error) {} + callback = function (error) {} } if (this.running) { return callback() @@ -35,10 +35,10 @@ module.exports = { return app.listen( __guard__( Settings.internal != null ? Settings.internal.clsi : undefined, - x => x.port + (x) => x.port ), 'localhost', - error => { + (error) => { if (error != null) { throw error } diff --git a/services/clsi/test/load/js/loadTest.js b/services/clsi/test/load/js/loadTest.js index c4116a2a30..65090b8b5f 100644 --- a/services/clsi/test/load/js/loadTest.js +++ b/services/clsi/test/load/js/loadTest.js @@ -17,7 +17,7 @@ const _ = require('lodash') const concurentCompiles = 5 const totalCompiles = 50 -const buildUrl = path => +const buildUrl = (path) => `http://${Settings.internal.clsi.host}:${Settings.internal.clsi.port}/${path}` const mainTexContent = fs.readFileSync('./bulk.tex', 'utf-8') @@ -25,12 +25,12 @@ const mainTexContent = fs.readFileSync('./bulk.tex', 'utf-8') const compileTimes = [] let failedCount = 0 -const getAverageCompileTime = function() { +const getAverageCompileTime = function () { const totalTime = _.reduce(compileTimes, (sum, time) => sum + time, 0) return totalTime / compileTimes.length } -const makeRequest = function(compileNumber, callback) { +const makeRequest = function (compileNumber, callback) { let bulkBodyCount = 7 let bodyContent = '' while (--bulkBodyCount) { @@ -74,12 +74,12 @@ ${bodyContent} ) } -const jobs = _.map(__range__(1, totalCompiles, true), i => cb => +const jobs = _.map(__range__(1, totalCompiles, true), (i) => (cb) => makeRequest(i, cb) ) const startTime = new Date() -async.parallelLimit(jobs, concurentCompiles, err => { +async.parallelLimit(jobs, concurentCompiles, (err) => { if (err != null) { console.error(err) } diff --git a/services/clsi/test/smoke/js/SmokeTests.js b/services/clsi/test/smoke/js/SmokeTests.js index fc52121ee0..8cfb190b30 100644 --- a/services/clsi/test/smoke/js/SmokeTests.js +++ b/services/clsi/test/smoke/js/SmokeTests.js @@ -1,20 +1,20 @@ const request = require('request') const Settings = require('settings-sharelatex') -const buildUrl = path => +const buildUrl = (path) => `http://${Settings.internal.clsi.host}:${Settings.internal.clsi.port}/${path}` const url = buildUrl(`project/smoketest-${process.pid}/compile`) module.exports = { sendNewResult(res) { - this._run(error => this._sendResponse(res, error)) + this._run((error) => this._sendResponse(res, error)) }, sendLastResult(res) { this._sendResponse(res, this._lastError) }, triggerRun(cb) { - this._run(error => { + this._run((error) => { this._lastError = error cb(error) }) diff --git a/services/clsi/test/unit/js/CompileControllerTests.js b/services/clsi/test/unit/js/CompileControllerTests.js index 8bb83e66af..1c93c96ea4 100644 --- a/services/clsi/test/unit/js/CompileControllerTests.js +++ b/services/clsi/test/unit/js/CompileControllerTests.js @@ -19,8 +19,8 @@ const modulePath = require('path').join( ) const tk = require('timekeeper') -describe('CompileController', function() { - beforeEach(function() { +describe('CompileController', function () { + beforeEach(function () { this.CompileController = SandboxedModule.require(modulePath, { requires: { './CompileManager': (this.CompileManager = {}), @@ -47,8 +47,8 @@ describe('CompileController', function() { return (this.next = sinon.stub()) }) - describe('compile', function() { - beforeEach(function() { + describe('compile', function () { + beforeEach(function () { this.req.body = { compile: 'mock-body' } @@ -82,40 +82,40 @@ describe('CompileController', function() { return (this.res.send = sinon.stub()) }) - describe('successfully', function() { - beforeEach(function() { + describe('successfully', function () { + beforeEach(function () { this.CompileManager.doCompileWithLock = sinon .stub() .callsArgWith(1, null, this.output_files) return this.CompileController.compile(this.req, this.res) }) - it('should parse the request', function() { + it('should parse the request', function () { return this.RequestParser.parse .calledWith(this.req.body) .should.equal(true) }) - it('should run the compile for the specified project', function() { + it('should run the compile for the specified project', function () { return this.CompileManager.doCompileWithLock .calledWith(this.request_with_project_id) .should.equal(true) }) - it('should mark the project as accessed', function() { + it('should mark the project as accessed', function () { return this.ProjectPersistenceManager.markProjectAsJustAccessed .calledWith(this.project_id) .should.equal(true) }) - return it('should return the JSON response', function() { + return it('should return the JSON response', function () { this.res.status.calledWith(200).should.equal(true) return this.res.send .calledWith({ compile: { status: 'success', error: null, - outputFiles: this.output_files.map(file => { + outputFiles: this.output_files.map((file) => { return { url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`, path: file.path, @@ -129,15 +129,15 @@ describe('CompileController', function() { }) }) - describe('with an error', function() { - beforeEach(function() { + describe('with an error', function () { + beforeEach(function () { this.CompileManager.doCompileWithLock = sinon .stub() .callsArgWith(1, new Error((this.message = 'error message')), null) return this.CompileController.compile(this.req, this.res) }) - return it('should return the JSON response with the error', function() { + return it('should return the JSON response with the error', function () { this.res.status.calledWith(500).should.equal(true) return this.res.send .calledWith({ @@ -151,8 +151,8 @@ describe('CompileController', function() { }) }) - describe('when the request times out', function() { - beforeEach(function() { + describe('when the request times out', function () { + beforeEach(function () { this.error = new Error((this.message = 'container timed out')) this.error.timedout = true this.CompileManager.doCompileWithLock = sinon @@ -161,7 +161,7 @@ describe('CompileController', function() { return this.CompileController.compile(this.req, this.res) }) - return it('should return the JSON response with the timeout status', function() { + return it('should return the JSON response with the timeout status', function () { this.res.status.calledWith(200).should.equal(true) return this.res.send .calledWith({ @@ -175,15 +175,15 @@ describe('CompileController', function() { }) }) - return describe('when the request returns no output files', function() { - beforeEach(function() { + return describe('when the request returns no output files', function () { + beforeEach(function () { this.CompileManager.doCompileWithLock = sinon .stub() .callsArgWith(1, null, []) return this.CompileController.compile(this.req, this.res) }) - return it('should return the JSON response with the failure status', function() { + return it('should return the JSON response with the failure status', function () { this.res.status.calledWith(200).should.equal(true) return this.res.send .calledWith({ @@ -198,8 +198,8 @@ describe('CompileController', function() { }) }) - describe('syncFromCode', function() { - beforeEach(function() { + describe('syncFromCode', function () { + beforeEach(function () { this.file = 'main.tex' this.line = 42 this.column = 5 @@ -218,7 +218,7 @@ describe('CompileController', function() { return this.CompileController.syncFromCode(this.req, this.res, this.next) }) - it('should find the corresponding location in the PDF', function() { + it('should find the corresponding location in the PDF', function () { return this.CompileManager.syncFromCode .calledWith( this.project_id, @@ -230,7 +230,7 @@ describe('CompileController', function() { .should.equal(true) }) - return it('should return the positions', function() { + return it('should return the positions', function () { return this.res.json .calledWith({ pdf: this.pdfPositions @@ -239,8 +239,8 @@ describe('CompileController', function() { }) }) - describe('syncFromPdf', function() { - beforeEach(function() { + describe('syncFromPdf', function () { + beforeEach(function () { this.page = 5 this.h = 100.23 this.v = 45.67 @@ -259,13 +259,13 @@ describe('CompileController', function() { return this.CompileController.syncFromPdf(this.req, this.res, this.next) }) - it('should find the corresponding location in the code', function() { + it('should find the corresponding location in the code', function () { return this.CompileManager.syncFromPdf .calledWith(this.project_id, undefined, this.page, this.h, this.v) .should.equal(true) }) - return it('should return the positions', function() { + return it('should return the positions', function () { return this.res.json .calledWith({ code: this.codePositions @@ -274,8 +274,8 @@ describe('CompileController', function() { }) }) - return describe('wordcount', function() { - beforeEach(function() { + return describe('wordcount', function () { + beforeEach(function () { this.file = 'main.tex' this.project_id = 'mock-project-id' this.req.params = { project_id: this.project_id } @@ -290,14 +290,14 @@ describe('CompileController', function() { .callsArgWith(4, null, (this.texcount = ['mock-texcount'])) }) - it('should return the word count of a file', function() { + it('should return the word count of a file', function () { this.CompileController.wordcount(this.req, this.res, this.next) return this.CompileManager.wordcount .calledWith(this.project_id, undefined, this.file, this.image) .should.equal(true) }) - it('should return the texcount info', function() { + it('should return the texcount info', function () { this.CompileController.wordcount(this.req, this.res, this.next) return this.res.json .calledWith({ @@ -306,8 +306,8 @@ describe('CompileController', function() { .should.equal(true) }) - describe('when allowedImages is set', function() { - beforeEach(function() { + describe('when allowedImages is set', function () { + beforeEach(function () { this.Settings.clsi = { docker: {} } this.Settings.clsi.docker.allowedImages = [ 'repo/image:tag1', @@ -317,28 +317,28 @@ describe('CompileController', function() { this.res.status = sinon.stub().returns({ send: this.res.send }) }) - describe('with an invalid image', function() { - beforeEach(function() { + describe('with an invalid image', function () { + beforeEach(function () { this.req.query.image = 'something/evil:1337' this.CompileController.wordcount(this.req, this.res, this.next) }) - it('should return a 400', function() { + it('should return a 400', function () { expect(this.res.status.calledWith(400)).to.equal(true) }) - it('should not run the query', function() { + it('should not run the query', function () { expect(this.CompileManager.wordcount.called).to.equal(false) }) }) - describe('with a valid image', function() { - beforeEach(function() { + describe('with a valid image', function () { + beforeEach(function () { this.req.query.image = 'repo/image:tag1' this.CompileController.wordcount(this.req, this.res, this.next) }) - it('should not return a 400', function() { + it('should not return a 400', function () { expect(this.res.status.calledWith(400)).to.equal(false) }) - it('should run the query', function() { + it('should run the query', function () { expect(this.CompileManager.wordcount.called).to.equal(true) }) }) diff --git a/services/clsi/test/unit/js/CompileManagerTests.js b/services/clsi/test/unit/js/CompileManagerTests.js index 90d572b2a2..cb85f2f131 100644 --- a/services/clsi/test/unit/js/CompileManagerTests.js +++ b/services/clsi/test/unit/js/CompileManagerTests.js @@ -24,8 +24,8 @@ const tk = require('timekeeper') const { EventEmitter } = require('events') const Path = require('path') -describe('CompileManager', function() { - beforeEach(function() { +describe('CompileManager', function () { + beforeEach(function () { this.CompileManager = SandboxedModule.require(modulePath, { requires: { './LatexRunner': (this.LatexRunner = {}), @@ -60,8 +60,8 @@ describe('CompileManager', function() { this.project_id = 'project-id-123' return (this.user_id = '1234') }) - describe('doCompileWithLock', function() { - beforeEach(function() { + describe('doCompileWithLock', function () { + beforeEach(function () { this.request = { resources: (this.resources = 'mock-resources'), project_id: this.project_id, @@ -77,33 +77,33 @@ describe('CompileManager', function() { runner((err, ...result) => callback(err, ...Array.from(result)))) }) - describe('when the project is not locked', function() { - beforeEach(function() { + describe('when the project is not locked', function () { + beforeEach(function () { return this.CompileManager.doCompileWithLock( this.request, this.callback ) }) - it('should ensure that the compile directory exists', function() { + it('should ensure that the compile directory exists', function () { return this.fse.ensureDir.calledWith(this.compileDir).should.equal(true) }) - it('should call doCompile with the request', function() { + it('should call doCompile with the request', function () { return this.CompileManager.doCompile .calledWith(this.request) .should.equal(true) }) - return it('should call the callback with the output files', function() { + return it('should call the callback with the output files', function () { return this.callback .calledWithExactly(null, this.output_files) .should.equal(true) }) }) - return describe('when the project is locked', function() { - beforeEach(function() { + return describe('when the project is locked', function () { + beforeEach(function () { this.error = new Error('locked') this.LockManager.runWithLock = (lockFile, runner, callback) => { return callback(this.error) @@ -114,22 +114,22 @@ describe('CompileManager', function() { ) }) - it('should ensure that the compile directory exists', function() { + it('should ensure that the compile directory exists', function () { return this.fse.ensureDir.calledWith(this.compileDir).should.equal(true) }) - it('should not call doCompile with the request', function() { + it('should not call doCompile with the request', function () { return this.CompileManager.doCompile.called.should.equal(false) }) - return it('should call the callback with the error', function() { + return it('should call the callback with the error', function () { return this.callback.calledWithExactly(this.error).should.equal(true) }) }) }) - describe('doCompile', function() { - beforeEach(function() { + describe('doCompile', function () { + beforeEach(function () { this.output_files = [ { path: 'output.log', @@ -180,18 +180,18 @@ describe('CompileManager', function() { return (this.TikzManager.checkMainFile = sinon.stub().callsArg(3, false)) }) - describe('normally', function() { - beforeEach(function() { + describe('normally', function () { + beforeEach(function () { return this.CompileManager.doCompile(this.request, this.callback) }) - it('should write the resources to disk', function() { + it('should write the resources to disk', function () { return this.ResourceWriter.syncResourcesToDisk .calledWith(this.request, this.compileDir) .should.equal(true) }) - it('should run LaTeX', function() { + it('should run LaTeX', function () { return this.LatexRunner.runLatex .calledWith(`${this.project_id}-${this.user_id}`, { directory: this.compileDir, @@ -206,43 +206,43 @@ describe('CompileManager', function() { .should.equal(true) }) - it('should find the output files', function() { + it('should find the output files', function () { return this.OutputFileFinder.findOutputFiles .calledWith(this.resources, this.compileDir) .should.equal(true) }) - it('should return the output files', function() { + it('should return the output files', function () { return this.callback .calledWith(null, this.build_files) .should.equal(true) }) - return it('should not inject draft mode by default', function() { + return it('should not inject draft mode by default', function () { return this.DraftModeManager.injectDraftMode.called.should.equal(false) }) }) - describe('with draft mode', function() { - beforeEach(function() { + describe('with draft mode', function () { + beforeEach(function () { this.request.draft = true return this.CompileManager.doCompile(this.request, this.callback) }) - return it('should inject the draft mode header', function() { + return it('should inject the draft mode header', function () { return this.DraftModeManager.injectDraftMode .calledWith(this.compileDir + '/' + this.rootResourcePath) .should.equal(true) }) }) - describe('with a check option', function() { - beforeEach(function() { + describe('with a check option', function () { + beforeEach(function () { this.request.check = 'error' return this.CompileManager.doCompile(this.request, this.callback) }) - return it('should run chktex', function() { + return it('should run chktex', function () { return this.LatexRunner.runLatex .calledWith(`${this.project_id}-${this.user_id}`, { directory: this.compileDir, @@ -262,14 +262,14 @@ describe('CompileManager', function() { }) }) - return describe('with a knitr file and check options', function() { - beforeEach(function() { + return describe('with a knitr file and check options', function () { + beforeEach(function () { this.request.rootResourcePath = 'main.Rtex' this.request.check = 'error' return this.CompileManager.doCompile(this.request, this.callback) }) - return it('should not run chktex', function() { + return it('should not run chktex', function () { return this.LatexRunner.runLatex .calledWith(`${this.project_id}-${this.user_id}`, { directory: this.compileDir, @@ -286,9 +286,9 @@ describe('CompileManager', function() { }) }) - describe('clearProject', function() { - describe('succesfully', function() { - beforeEach(function() { + describe('clearProject', function () { + describe('succesfully', function () { + beforeEach(function () { this.Settings.compileDir = 'compiles' this.fs.lstat = sinon.stub().callsArgWith(1, null, { isDirectory() { @@ -308,7 +308,7 @@ describe('CompileManager', function() { return this.proc.emit('close', 0) }) - it('should remove the project directory', function() { + it('should remove the project directory', function () { return this.child_process.spawn .calledWith('rm', [ '-r', @@ -317,13 +317,13 @@ describe('CompileManager', function() { .should.equal(true) }) - return it('should call the callback', function() { + return it('should call the callback', function () { return this.callback.called.should.equal(true) }) }) - return describe('with a non-success status code', function() { - beforeEach(function() { + return describe('with a non-success status code', function () { + beforeEach(function () { this.Settings.compileDir = 'compiles' this.fs.lstat = sinon.stub().callsArgWith(1, null, { isDirectory() { @@ -344,7 +344,7 @@ describe('CompileManager', function() { return this.proc.emit('close', 1) }) - it('should remove the project directory', function() { + it('should remove the project directory', function () { return this.child_process.spawn .calledWith('rm', [ '-r', @@ -353,7 +353,7 @@ describe('CompileManager', function() { .should.equal(true) }) - it('should call the callback with an error from the stderr', function() { + it('should call the callback with an error from the stderr', function () { this.callback.calledWithExactly(sinon.match(Error)).should.equal(true) this.callback.args[0][0].message.should.equal( @@ -363,8 +363,8 @@ describe('CompileManager', function() { }) }) - describe('syncing', function() { - beforeEach(function() { + describe('syncing', function () { + beforeEach(function () { this.page = 1 this.h = 42.23 this.v = 87.56 @@ -374,12 +374,12 @@ describe('CompileManager', function() { this.column = 3 this.file_name = 'main.tex' this.child_process.execFile = sinon.stub() - return (this.Settings.path.synctexBaseDir = project_id => + return (this.Settings.path.synctexBaseDir = (project_id) => `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`) }) - describe('syncFromCode', function() { - beforeEach(function() { + describe('syncFromCode', function () { + beforeEach(function () { this.fs.stat = sinon.stub().callsArgWith(1, null, { isFile() { return true @@ -399,7 +399,7 @@ describe('CompileManager', function() { ) }) - it('should execute the synctex binary', function() { + it('should execute the synctex binary', function () { const bin_path = Path.resolve(__dirname + '/../../../bin/synctex') const synctex_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/output.pdf` const file_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/${this.file_name}` @@ -422,7 +422,7 @@ describe('CompileManager', function() { .should.equal(true) }) - return it('should call the callback with the parsed output', function() { + return it('should call the callback with the parsed output', function () { return this.callback .calledWith(null, [ { @@ -437,8 +437,8 @@ describe('CompileManager', function() { }) }) - return describe('syncFromPdf', function() { - beforeEach(function() { + return describe('syncFromPdf', function () { + beforeEach(function () { this.fs.stat = sinon.stub().callsArgWith(1, null, { isFile() { return true @@ -458,7 +458,7 @@ describe('CompileManager', function() { ) }) - it('should execute the synctex binary', function() { + it('should execute the synctex binary', function () { const bin_path = Path.resolve(__dirname + '/../../../bin/synctex') const synctex_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/output.pdf` return this.CommandRunner.run @@ -473,7 +473,7 @@ describe('CompileManager', function() { .should.equal(true) }) - return it('should call the callback with the parsed output', function() { + return it('should call the callback with the parsed output', function () { return this.callback .calledWith(null, [ { @@ -487,8 +487,8 @@ describe('CompileManager', function() { }) }) - return describe('wordcount', function() { - beforeEach(function() { + return describe('wordcount', function () { + beforeEach(function () { this.CommandRunner.run = sinon.stub().callsArg(7) this.fs.readFile = sinon .stub() @@ -514,7 +514,7 @@ describe('CompileManager', function() { ) }) - it('should run the texcount command', function() { + it('should run the texcount command', function () { this.directory = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}` this.file_path = `$COMPILE_DIR/${this.file_name}` this.command = [ @@ -537,7 +537,7 @@ describe('CompileManager', function() { .should.equal(true) }) - return it('should call the callback with the parsed output', function() { + return it('should call the callback with the parsed output', function () { return this.callback .calledWith(null, { encode: 'ascii', diff --git a/services/clsi/test/unit/js/ContentTypeMapperTests.js b/services/clsi/test/unit/js/ContentTypeMapperTests.js index 41fc37e486..21173bc526 100644 --- a/services/clsi/test/unit/js/ContentTypeMapperTests.js +++ b/services/clsi/test/unit/js/ContentTypeMapperTests.js @@ -18,61 +18,61 @@ const modulePath = require('path').join( '../../../app/js/ContentTypeMapper' ) -describe('ContentTypeMapper', function() { - beforeEach(function() { +describe('ContentTypeMapper', function () { + beforeEach(function () { return (this.ContentTypeMapper = SandboxedModule.require(modulePath)) }) - return describe('map', function() { - it('should map .txt to text/plain', function() { + return describe('map', function () { + it('should map .txt to text/plain', function () { const content_type = this.ContentTypeMapper.map('example.txt') return content_type.should.equal('text/plain') }) - it('should map .csv to text/csv', function() { + it('should map .csv to text/csv', function () { const content_type = this.ContentTypeMapper.map('example.csv') return content_type.should.equal('text/csv') }) - it('should map .pdf to application/pdf', function() { + it('should map .pdf to application/pdf', function () { const content_type = this.ContentTypeMapper.map('example.pdf') return content_type.should.equal('application/pdf') }) - it('should fall back to octet-stream', function() { + it('should fall back to octet-stream', function () { const content_type = this.ContentTypeMapper.map('example.unknown') return content_type.should.equal('application/octet-stream') }) - describe('coercing web files to plain text', function() { - it('should map .js to plain text', function() { + describe('coercing web files to plain text', function () { + it('should map .js to plain text', function () { const content_type = this.ContentTypeMapper.map('example.js') return content_type.should.equal('text/plain') }) - it('should map .html to plain text', function() { + it('should map .html to plain text', function () { const content_type = this.ContentTypeMapper.map('example.html') return content_type.should.equal('text/plain') }) - return it('should map .css to plain text', function() { + return it('should map .css to plain text', function () { const content_type = this.ContentTypeMapper.map('example.css') return content_type.should.equal('text/plain') }) }) - return describe('image files', function() { - it('should map .png to image/png', function() { + return describe('image files', function () { + it('should map .png to image/png', function () { const content_type = this.ContentTypeMapper.map('example.png') return content_type.should.equal('image/png') }) - it('should map .jpeg to image/jpeg', function() { + it('should map .jpeg to image/jpeg', function () { const content_type = this.ContentTypeMapper.map('example.jpeg') return content_type.should.equal('image/jpeg') }) - return it('should map .svg to text/plain to protect against XSS (SVG can execute JS)', function() { + return it('should map .svg to text/plain to protect against XSS (SVG can execute JS)', function () { const content_type = this.ContentTypeMapper.map('example.svg') return content_type.should.equal('text/plain') }) diff --git a/services/clsi/test/unit/js/DockerLockManagerTests.js b/services/clsi/test/unit/js/DockerLockManagerTests.js index bc13c5ade5..19c289a128 100644 --- a/services/clsi/test/unit/js/DockerLockManagerTests.js +++ b/services/clsi/test/unit/js/DockerLockManagerTests.js @@ -17,8 +17,8 @@ const modulePath = require('path').join( '../../../app/js/DockerLockManager' ) -describe('LockManager', function() { - beforeEach(function() { +describe('LockManager', function () { + beforeEach(function () { return (this.LockManager = SandboxedModule.require(modulePath, { requires: { 'settings-sharelatex': (this.Settings = { clsi: { docker: {} } }), @@ -30,13 +30,13 @@ describe('LockManager', function() { })) }) - return describe('runWithLock', function() { - describe('with a single lock', function() { - beforeEach(function(done) { + return describe('runWithLock', function () { + describe('with a single lock', function () { + beforeEach(function (done) { this.callback = sinon.stub() return this.LockManager.runWithLock( 'lock-one', - releaseLock => + (releaseLock) => setTimeout(() => releaseLock(null, 'hello', 'world'), 100), (err, ...args) => { @@ -46,20 +46,20 @@ describe('LockManager', function() { ) }) - return it('should call the callback', function() { + return it('should call the callback', function () { return this.callback .calledWith(null, 'hello', 'world') .should.equal(true) }) }) - describe('with two locks', function() { - beforeEach(function(done) { + describe('with two locks', function () { + beforeEach(function (done) { this.callback1 = sinon.stub() this.callback2 = sinon.stub() this.LockManager.runWithLock( 'lock-one', - releaseLock => + (releaseLock) => setTimeout(() => releaseLock(null, 'hello', 'world', 'one'), 100), (err, ...args) => { @@ -68,7 +68,7 @@ describe('LockManager', function() { ) return this.LockManager.runWithLock( 'lock-two', - releaseLock => + (releaseLock) => setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 200), (err, ...args) => { @@ -78,29 +78,29 @@ describe('LockManager', function() { ) }) - it('should call the first callback', function() { + it('should call the first callback', function () { return this.callback1 .calledWith(null, 'hello', 'world', 'one') .should.equal(true) }) - return it('should call the second callback', function() { + return it('should call the second callback', function () { return this.callback2 .calledWith(null, 'hello', 'world', 'two') .should.equal(true) }) }) - return describe('with lock contention', function() { - describe('where the first lock is released quickly', function() { - beforeEach(function(done) { + return describe('with lock contention', function () { + describe('where the first lock is released quickly', function () { + beforeEach(function (done) { this.LockManager.MAX_LOCK_WAIT_TIME = 1000 this.LockManager.LOCK_TEST_INTERVAL = 100 this.callback1 = sinon.stub() this.callback2 = sinon.stub() this.LockManager.runWithLock( 'lock', - releaseLock => + (releaseLock) => setTimeout(() => releaseLock(null, 'hello', 'world', 'one'), 100), (err, ...args) => { @@ -109,7 +109,7 @@ describe('LockManager', function() { ) return this.LockManager.runWithLock( 'lock', - releaseLock => + (releaseLock) => setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 200), (err, ...args) => { @@ -119,21 +119,21 @@ describe('LockManager', function() { ) }) - it('should call the first callback', function() { + it('should call the first callback', function () { return this.callback1 .calledWith(null, 'hello', 'world', 'one') .should.equal(true) }) - return it('should call the second callback', function() { + return it('should call the second callback', function () { return this.callback2 .calledWith(null, 'hello', 'world', 'two') .should.equal(true) }) }) - describe('where the first lock is held longer than the waiting time', function() { - beforeEach(function(done) { + describe('where the first lock is held longer than the waiting time', function () { + beforeEach(function (done) { let doneTwo this.LockManager.MAX_LOCK_HOLD_TIME = 10000 this.LockManager.MAX_LOCK_WAIT_TIME = 1000 @@ -141,7 +141,7 @@ describe('LockManager', function() { this.callback1 = sinon.stub() this.callback2 = sinon.stub() let doneOne = (doneTwo = false) - const finish = function(key) { + const finish = function (key) { if (key === 1) { doneOne = true } @@ -154,7 +154,7 @@ describe('LockManager', function() { } this.LockManager.runWithLock( 'lock', - releaseLock => + (releaseLock) => setTimeout( () => releaseLock(null, 'hello', 'world', 'one'), 1100 @@ -167,7 +167,7 @@ describe('LockManager', function() { ) return this.LockManager.runWithLock( 'lock', - releaseLock => + (releaseLock) => setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 100), (err, ...args) => { @@ -177,20 +177,20 @@ describe('LockManager', function() { ) }) - it('should call the first callback', function() { + it('should call the first callback', function () { return this.callback1 .calledWith(null, 'hello', 'world', 'one') .should.equal(true) }) - return it('should call the second callback with an error', function() { + return it('should call the second callback with an error', function () { const error = sinon.match.instanceOf(Error) return this.callback2.calledWith(error).should.equal(true) }) }) - return describe('where the first lock is held longer than the max holding time', function() { - beforeEach(function(done) { + return describe('where the first lock is held longer than the max holding time', function () { + beforeEach(function (done) { let doneTwo this.LockManager.MAX_LOCK_HOLD_TIME = 1000 this.LockManager.MAX_LOCK_WAIT_TIME = 2000 @@ -198,7 +198,7 @@ describe('LockManager', function() { this.callback1 = sinon.stub() this.callback2 = sinon.stub() let doneOne = (doneTwo = false) - const finish = function(key) { + const finish = function (key) { if (key === 1) { doneOne = true } @@ -211,7 +211,7 @@ describe('LockManager', function() { } this.LockManager.runWithLock( 'lock', - releaseLock => + (releaseLock) => setTimeout( () => releaseLock(null, 'hello', 'world', 'one'), 1500 @@ -224,7 +224,7 @@ describe('LockManager', function() { ) return this.LockManager.runWithLock( 'lock', - releaseLock => + (releaseLock) => setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 100), (err, ...args) => { @@ -234,13 +234,13 @@ describe('LockManager', function() { ) }) - it('should call the first callback', function() { + it('should call the first callback', function () { return this.callback1 .calledWith(null, 'hello', 'world', 'one') .should.equal(true) }) - return it('should call the second callback', function() { + return it('should call the second callback', function () { return this.callback2 .calledWith(null, 'hello', 'world', 'two') .should.equal(true) diff --git a/services/clsi/test/unit/js/DockerRunnerTests.js b/services/clsi/test/unit/js/DockerRunnerTests.js index 9c1731a809..553b20f3ad 100644 --- a/services/clsi/test/unit/js/DockerRunnerTests.js +++ b/services/clsi/test/unit/js/DockerRunnerTests.js @@ -23,8 +23,8 @@ const modulePath = require('path').join( ) const Path = require('path') -describe('DockerRunner', function() { - beforeEach(function() { +describe('DockerRunner', function () { + beforeEach(function () { let container, Docker, Timer this.container = container = {} this.DockerRunner = SandboxedModule.require(modulePath, { @@ -39,7 +39,7 @@ describe('DockerRunner', function() { info: sinon.stub(), warn: sinon.stub() }), - dockerode: (Docker = (function() { + dockerode: (Docker = (function () { Docker = class Docker { static initClass() { this.prototype.getContainer = sinon.stub().returns(container) @@ -90,12 +90,12 @@ describe('DockerRunner', function() { return (this.Settings.clsi.docker.env = { PATH: 'mock-path' }) }) - afterEach(function() { + afterEach(function () { this.DockerRunner.stopContainerMonitor() }) - describe('run', function() { - beforeEach(function(done) { + describe('run', function () { + beforeEach(function (done) { this.DockerRunner._getContainerOptions = sinon .stub() .returns((this.options = { mockoptions: 'foo' })) @@ -111,8 +111,8 @@ describe('DockerRunner', function() { return done() }) - describe('successfully', function() { - beforeEach(function(done) { + describe('successfully', function () { + beforeEach(function (done) { this.DockerRunner._runAndWaitForContainer = sinon .stub() .callsArgWith(3, null, (this.output = 'mock-output')) @@ -131,7 +131,7 @@ describe('DockerRunner', function() { ) }) - it('should generate the options for the container', function() { + it('should generate the options for the container', function () { return this.DockerRunner._getContainerOptions .calledWith( this.command_with_dir, @@ -142,25 +142,25 @@ describe('DockerRunner', function() { .should.equal(true) }) - it('should generate the fingerprint from the returned options', function() { + it('should generate the fingerprint from the returned options', function () { return this.DockerRunner._fingerprintContainer .calledWith(this.options) .should.equal(true) }) - it('should do the run', function() { + it('should do the run', function () { return this.DockerRunner._runAndWaitForContainer .calledWith(this.options, this.volumes, this.timeout) .should.equal(true) }) - return it('should call the callback', function() { + return it('should call the callback', function () { return this.callback.calledWith(null, this.output).should.equal(true) }) }) - describe('when path.sandboxedCompilesHostDir is set', function() { - beforeEach(function() { + describe('when path.sandboxedCompilesHostDir is set', function () { + beforeEach(function () { this.Settings.path.sandboxedCompilesHostDir = '/some/host/dir/compiles' this.directory = '/var/lib/sharelatex/data/compiles/xyz' this.DockerRunner._runAndWaitForContainer = sinon @@ -178,7 +178,7 @@ describe('DockerRunner', function() { ) }) - it('should re-write the bind directory', function() { + it('should re-write the bind directory', function () { const volumes = this.DockerRunner._runAndWaitForContainer.lastCall .args[1] return expect(volumes).to.deep.equal({ @@ -186,13 +186,13 @@ describe('DockerRunner', function() { }) }) - return it('should call the callback', function() { + return it('should call the callback', function () { return this.callback.calledWith(null, this.output).should.equal(true) }) }) - describe('when the run throws an error', function() { - beforeEach(function() { + describe('when the run throws an error', function () { + beforeEach(function () { let firstTime = true this.output = 'mock-output' this.DockerRunner._runAndWaitForContainer = ( @@ -202,7 +202,7 @@ describe('DockerRunner', function() { callback ) => { if (callback == null) { - callback = function(error, output) {} + callback = function (error, output) {} } if (firstTime) { firstTime = false @@ -227,25 +227,25 @@ describe('DockerRunner', function() { ) }) - it('should do the run twice', function() { + it('should do the run twice', function () { return this.DockerRunner._runAndWaitForContainer.calledTwice.should.equal( true ) }) - it('should destroy the container in between', function() { + it('should destroy the container in between', function () { return this.DockerRunner.destroyContainer .calledWith(this.name, null) .should.equal(true) }) - return it('should call the callback', function() { + return it('should call the callback', function () { return this.callback.calledWith(null, this.output).should.equal(true) }) }) - describe('with no image', function() { - beforeEach(function() { + describe('with no image', function () { + beforeEach(function () { this.DockerRunner._runAndWaitForContainer = sinon .stub() .callsArgWith(3, null, (this.output = 'mock-output')) @@ -261,7 +261,7 @@ describe('DockerRunner', function() { ) }) - return it('should use the default image', function() { + return it('should use the default image', function () { return this.DockerRunner._getContainerOptions .calledWith( this.command_with_dir, @@ -273,8 +273,8 @@ describe('DockerRunner', function() { }) }) - describe('with image override', function() { - beforeEach(function() { + describe('with image override', function () { + beforeEach(function () { this.Settings.texliveImageNameOveride = 'overrideimage.com/something' this.DockerRunner._runAndWaitForContainer = sinon .stub() @@ -291,14 +291,14 @@ describe('DockerRunner', function() { ) }) - return it('should use the override and keep the tag', function() { + return it('should use the override and keep the tag', function () { const image = this.DockerRunner._getContainerOptions.args[0][1] return image.should.equal('overrideimage.com/something/image:2016.2') }) }) - describe('with image restriction', function() { - beforeEach(function() { + describe('with image restriction', function () { + beforeEach(function () { this.Settings.clsi.docker.allowedImages = [ 'repo/image:tag1', 'repo/image:tag2' @@ -308,8 +308,8 @@ describe('DockerRunner', function() { .callsArgWith(3, null, (this.output = 'mock-output')) }) - describe('with a valid image', function() { - beforeEach(function() { + describe('with a valid image', function () { + beforeEach(function () { this.DockerRunner.run( this.project_id, this.command, @@ -322,13 +322,13 @@ describe('DockerRunner', function() { ) }) - it('should setup the container', function() { + it('should setup the container', function () { this.DockerRunner._getContainerOptions.called.should.equal(true) }) }) - describe('with a invalid image', function() { - beforeEach(function() { + describe('with a invalid image', function () { + beforeEach(function () { this.DockerRunner.run( this.project_id, this.command, @@ -341,21 +341,21 @@ describe('DockerRunner', function() { ) }) - it('should call the callback with an error', function() { + it('should call the callback with an error', function () { const err = new Error('image not allowed') this.callback.called.should.equal(true) this.callback.args[0][0].message.should.equal(err.message) }) - it('should not setup the container', function() { + it('should not setup the container', function () { this.DockerRunner._getContainerOptions.called.should.equal(false) }) }) }) }) - describe('run with _getOptions', function() { - beforeEach(function(done) { + describe('run with _getOptions', function () { + beforeEach(function (done) { // this.DockerRunner._getContainerOptions = sinon // .stub() // .returns((this.options = { mockoptions: 'foo' })) @@ -371,8 +371,8 @@ describe('DockerRunner', function() { return done() }) - describe('when a compile group config is set', function() { - beforeEach(function() { + describe('when a compile group config is set', function () { + beforeEach(function () { this.Settings.clsi.docker.compileGroupConfig = { 'compile-group': { 'HostConfig.newProperty': 'new-property' @@ -394,7 +394,7 @@ describe('DockerRunner', function() { ) }) - it('should set the docker options for the compile group', function() { + it('should set the docker options for the compile group', function () { const options = this.DockerRunner._runAndWaitForContainer.lastCall .args[0] return expect(options.HostConfig).to.deep.include({ @@ -406,14 +406,14 @@ describe('DockerRunner', function() { }) }) - return it('should call the callback', function() { + return it('should call the callback', function () { return this.callback.calledWith(null, this.output).should.equal(true) }) }) }) - describe('_runAndWaitForContainer', function() { - beforeEach(function() { + describe('_runAndWaitForContainer', function () { + beforeEach(function () { this.options = { mockoptions: 'foo', name: (this.name = 'mock-name') } this.DockerRunner.startContainer = ( options, @@ -436,25 +436,25 @@ describe('DockerRunner', function() { ) }) - it('should create/start the container', function() { + it('should create/start the container', function () { return this.DockerRunner.startContainer .calledWith(this.options, this.volumes) .should.equal(true) }) - it('should wait for the container to finish', function() { + it('should wait for the container to finish', function () { return this.DockerRunner.waitForContainer .calledWith(this.name, this.timeout) .should.equal(true) }) - return it('should call the callback with the output', function() { + return it('should call the callback with the output', function () { return this.callback.calledWith(null, this.output).should.equal(true) }) }) - describe('startContainer', function() { - beforeEach(function() { + describe('startContainer', function () { + beforeEach(function () { this.attachStreamHandler = sinon.stub() this.attachStreamHandler.cock = true this.options = { mockoptions: 'foo', name: 'mock-name' } @@ -470,8 +470,8 @@ describe('DockerRunner', function() { return sinon.spy(this.DockerRunner, 'attachToContainer') }) - describe('when the container exists', function() { - beforeEach(function() { + describe('when the container exists', function () { + beforeEach(function () { this.container.inspect = sinon.stub().callsArgWith(0) this.container.start = sinon.stub().yields() @@ -483,24 +483,24 @@ describe('DockerRunner', function() { ) }) - it('should start the container with the given name', function() { + it('should start the container with the given name', function () { this.getContainer.calledWith(this.options.name).should.equal(true) return this.container.start.called.should.equal(true) }) - it('should not try to create the container', function() { + it('should not try to create the container', function () { return this.createContainer.called.should.equal(false) }) - it('should attach to the container', function() { + it('should attach to the container', function () { return this.DockerRunner.attachToContainer.called.should.equal(true) }) - it('should call the callback', function() { + it('should call the callback', function () { return this.callback.called.should.equal(true) }) - return it('should attach before the container starts', function() { + return it('should attach before the container starts', function () { return sinon.assert.callOrder( this.DockerRunner.attachToContainer, this.container.start @@ -508,8 +508,8 @@ describe('DockerRunner', function() { }) }) - describe('when the container does not exist', function() { - beforeEach(function() { + describe('when the container does not exist', function () { + beforeEach(function () { const exists = false this.container.start = sinon.stub().yields() this.container.inspect = sinon @@ -523,20 +523,20 @@ describe('DockerRunner', function() { ) }) - it('should create the container', function() { + it('should create the container', function () { return this.createContainer.calledWith(this.options).should.equal(true) }) - it('should call the callback and stream handler', function() { + it('should call the callback and stream handler', function () { this.attachStreamHandler.called.should.equal(true) return this.callback.called.should.equal(true) }) - it('should attach to the container', function() { + it('should attach to the container', function () { return this.DockerRunner.attachToContainer.called.should.equal(true) }) - return it('should attach before the container starts', function() { + return it('should attach before the container starts', function () { return sinon.assert.callOrder( this.DockerRunner.attachToContainer, this.container.start @@ -544,8 +544,8 @@ describe('DockerRunner', function() { }) }) - describe('when the container is already running', function() { - beforeEach(function() { + describe('when the container is already running', function () { + beforeEach(function () { const error = new Error( `HTTP code is 304 which indicates error: server error - start: Cannot start container ${this.name}: The container MOCKID is already running.` ) @@ -560,18 +560,18 @@ describe('DockerRunner', function() { ) }) - it('should not try to create the container', function() { + it('should not try to create the container', function () { return this.createContainer.called.should.equal(false) }) - return it('should call the callback and stream handler without an error', function() { + return it('should call the callback and stream handler without an error', function () { this.attachStreamHandler.called.should.equal(true) return this.callback.called.should.equal(true) }) }) - describe('when a volume does not exist', function() { - beforeEach(function() { + describe('when a volume does not exist', function () { + beforeEach(function () { this.fs.stat = sinon.stub().yields(new Error('no such path')) return this.DockerRunner.startContainer( this.options, @@ -581,17 +581,17 @@ describe('DockerRunner', function() { ) }) - it('should not try to create the container', function() { + it('should not try to create the container', function () { return this.createContainer.called.should.equal(false) }) - it('should call the callback with an error', function() { + it('should call the callback with an error', function () { this.callback.calledWith(sinon.match(Error)).should.equal(true) }) }) - describe('when a volume exists but is not a directory', function() { - beforeEach(function() { + describe('when a volume exists but is not a directory', function () { + beforeEach(function () { this.fs.stat = sinon.stub().yields(null, { isDirectory() { return false @@ -605,17 +605,17 @@ describe('DockerRunner', function() { ) }) - it('should not try to create the container', function() { + it('should not try to create the container', function () { return this.createContainer.called.should.equal(false) }) - it('should call the callback with an error', function() { + it('should call the callback with an error', function () { this.callback.calledWith(sinon.match(Error)).should.equal(true) }) }) - describe('when a volume does not exist, but sibling-containers are used', function() { - beforeEach(function() { + describe('when a volume does not exist, but sibling-containers are used', function () { + beforeEach(function () { this.fs.stat = sinon.stub().yields(new Error('no such path')) this.Settings.path.sandboxedCompilesHostDir = '/some/path' this.container.start = sinon.stub().yields() @@ -626,30 +626,30 @@ describe('DockerRunner', function() { ) }) - afterEach(function() { + afterEach(function () { return delete this.Settings.path.sandboxedCompilesHostDir }) - it('should start the container with the given name', function() { + it('should start the container with the given name', function () { this.getContainer.calledWith(this.options.name).should.equal(true) return this.container.start.called.should.equal(true) }) - it('should not try to create the container', function() { + it('should not try to create the container', function () { return this.createContainer.called.should.equal(false) }) - return it('should call the callback', function() { + return it('should call the callback', function () { this.callback.called.should.equal(true) return this.callback.calledWith(new Error()).should.equal(false) }) }) - return describe('when the container tries to be created, but already has been (race condition)', function() {}) + return describe('when the container tries to be created, but already has been (race condition)', function () {}) }) - describe('waitForContainer', function() { - beforeEach(function() { + describe('waitForContainer', function () { + beforeEach(function () { this.containerId = 'container-id' this.timeout = 5000 this.container.wait = sinon @@ -658,8 +658,8 @@ describe('DockerRunner', function() { return (this.container.kill = sinon.stub().yields()) }) - describe('when the container returns in time', function() { - beforeEach(function() { + describe('when the container returns in time', function () { + beforeEach(function () { return this.DockerRunner.waitForContainer( this.containerId, this.timeout, @@ -667,23 +667,23 @@ describe('DockerRunner', function() { ) }) - it('should wait for the container', function() { + it('should wait for the container', function () { this.getContainer.calledWith(this.containerId).should.equal(true) return this.container.wait.called.should.equal(true) }) - return it('should call the callback with the exit', function() { + return it('should call the callback with the exit', function () { return this.callback .calledWith(null, this.statusCode) .should.equal(true) }) }) - return describe('when the container does not return before the timeout', function() { - beforeEach(function(done) { - this.container.wait = function(callback) { + return describe('when the container does not return before the timeout', function () { + beforeEach(function (done) { + this.container.wait = function (callback) { if (callback == null) { - callback = function(error, exitCode) {} + callback = function (error, exitCode) {} } return setTimeout(() => callback(null, { StatusCode: 42 }), 100) } @@ -698,12 +698,12 @@ describe('DockerRunner', function() { ) }) - it('should call kill on the container', function() { + it('should call kill on the container', function () { this.getContainer.calledWith(this.containerId).should.equal(true) return this.container.kill.called.should.equal(true) }) - it('should call the callback with an error', function() { + it('should call the callback with an error', function () { this.callback.calledWith(sinon.match(Error)).should.equal(true) const errorObj = this.callback.args[0][0] @@ -713,8 +713,8 @@ describe('DockerRunner', function() { }) }) - describe('destroyOldContainers', function() { - beforeEach(function(done) { + describe('destroyOldContainers', function () { + beforeEach(function (done) { const oneHourInSeconds = 60 * 60 const oneHourInMilliseconds = oneHourInSeconds * 1000 const nowInSeconds = Date.now() / 1000 @@ -738,42 +738,42 @@ describe('DockerRunner', function() { this.DockerRunner.MAX_CONTAINER_AGE = oneHourInMilliseconds this.listContainers.callsArgWith(1, null, this.containers) this.DockerRunner.destroyContainer = sinon.stub().callsArg(3) - return this.DockerRunner.destroyOldContainers(error => { + return this.DockerRunner.destroyOldContainers((error) => { this.callback(error) return done() }) }) - it('should list all containers', function() { + it('should list all containers', function () { return this.listContainers.calledWith({ all: true }).should.equal(true) }) - it('should destroy old containers', function() { + it('should destroy old containers', function () { this.DockerRunner.destroyContainer.callCount.should.equal(1) return this.DockerRunner.destroyContainer .calledWith('project-old-container-name', 'old-container-id') .should.equal(true) }) - it('should not destroy new containers', function() { + it('should not destroy new containers', function () { return this.DockerRunner.destroyContainer .calledWith('project-new-container-name', 'new-container-id') .should.equal(false) }) - it('should not destroy non-project containers', function() { + it('should not destroy non-project containers', function () { return this.DockerRunner.destroyContainer .calledWith('totally-not-a-project-container', 'some-random-id') .should.equal(false) }) - return it('should callback the callback', function() { + return it('should callback the callback', function () { return this.callback.called.should.equal(true) }) }) - describe('_destroyContainer', function() { - beforeEach(function() { + describe('_destroyContainer', function () { + beforeEach(function () { this.containerId = 'some_id' this.fakeContainer = { remove: sinon.stub().callsArgWith(1, null) } return (this.Docker.prototype.getContainer = sinon @@ -781,11 +781,11 @@ describe('DockerRunner', function() { .returns(this.fakeContainer)) }) - it('should get the container', function(done) { + it('should get the container', function (done) { return this.DockerRunner._destroyContainer( this.containerId, false, - err => { + (err) => { this.Docker.prototype.getContainer.callCount.should.equal(1) this.Docker.prototype.getContainer .calledWith(this.containerId) @@ -795,11 +795,11 @@ describe('DockerRunner', function() { ) }) - it('should try to force-destroy the container when shouldForce=true', function(done) { + it('should try to force-destroy the container when shouldForce=true', function (done) { return this.DockerRunner._destroyContainer( this.containerId, true, - err => { + (err) => { this.fakeContainer.remove.callCount.should.equal(1) this.fakeContainer.remove .calledWith({ force: true }) @@ -809,11 +809,11 @@ describe('DockerRunner', function() { ) }) - it('should not try to force-destroy the container when shouldForce=false', function(done) { + it('should not try to force-destroy the container when shouldForce=false', function (done) { return this.DockerRunner._destroyContainer( this.containerId, false, - err => { + (err) => { this.fakeContainer.remove.callCount.should.equal(1) this.fakeContainer.remove .calledWith({ force: false }) @@ -823,19 +823,19 @@ describe('DockerRunner', function() { ) }) - it('should not produce an error', function(done) { + it('should not produce an error', function (done) { return this.DockerRunner._destroyContainer( this.containerId, false, - err => { + (err) => { expect(err).to.equal(null) return done() } ) }) - describe('when the container is already gone', function() { - beforeEach(function() { + describe('when the container is already gone', function () { + beforeEach(function () { this.fakeError = new Error('woops') this.fakeError.statusCode = 404 this.fakeContainer = { @@ -846,11 +846,11 @@ describe('DockerRunner', function() { .returns(this.fakeContainer)) }) - return it('should not produce an error', function(done) { + return it('should not produce an error', function (done) { return this.DockerRunner._destroyContainer( this.containerId, false, - err => { + (err) => { expect(err).to.equal(null) return done() } @@ -858,8 +858,8 @@ describe('DockerRunner', function() { }) }) - return describe('when container.destroy produces an error', function(done) { - beforeEach(function() { + return describe('when container.destroy produces an error', function (done) { + beforeEach(function () { this.fakeError = new Error('woops') this.fakeError.statusCode = 500 this.fakeContainer = { @@ -870,11 +870,11 @@ describe('DockerRunner', function() { .returns(this.fakeContainer)) }) - return it('should produce an error', function(done) { + return it('should produce an error', function (done) { return this.DockerRunner._destroyContainer( this.containerId, false, - err => { + (err) => { expect(err).to.not.equal(null) expect(err).to.equal(this.fakeError) return done() @@ -884,8 +884,8 @@ describe('DockerRunner', function() { }) }) - return describe('kill', function() { - beforeEach(function() { + return describe('kill', function () { + beforeEach(function () { this.containerId = 'some_id' this.fakeContainer = { kill: sinon.stub().callsArgWith(0, null) } return (this.Docker.prototype.getContainer = sinon @@ -893,8 +893,8 @@ describe('DockerRunner', function() { .returns(this.fakeContainer)) }) - it('should get the container', function(done) { - return this.DockerRunner.kill(this.containerId, err => { + it('should get the container', function (done) { + return this.DockerRunner.kill(this.containerId, (err) => { this.Docker.prototype.getContainer.callCount.should.equal(1) this.Docker.prototype.getContainer .calledWith(this.containerId) @@ -903,22 +903,22 @@ describe('DockerRunner', function() { }) }) - it('should try to force-destroy the container', function(done) { - return this.DockerRunner.kill(this.containerId, err => { + it('should try to force-destroy the container', function (done) { + return this.DockerRunner.kill(this.containerId, (err) => { this.fakeContainer.kill.callCount.should.equal(1) return done() }) }) - it('should not produce an error', function(done) { - return this.DockerRunner.kill(this.containerId, err => { + it('should not produce an error', function (done) { + return this.DockerRunner.kill(this.containerId, (err) => { expect(err).to.equal(undefined) return done() }) }) - describe('when the container is not actually running', function() { - beforeEach(function() { + describe('when the container is not actually running', function () { + beforeEach(function () { this.fakeError = new Error('woops') this.fakeError.statusCode = 500 this.fakeError.message = @@ -931,16 +931,16 @@ describe('DockerRunner', function() { .returns(this.fakeContainer)) }) - return it('should not produce an error', function(done) { - return this.DockerRunner.kill(this.containerId, err => { + return it('should not produce an error', function (done) { + return this.DockerRunner.kill(this.containerId, (err) => { expect(err).to.equal(undefined) return done() }) }) }) - return describe('when container.kill produces a legitimate error', function(done) { - beforeEach(function() { + return describe('when container.kill produces a legitimate error', function (done) { + beforeEach(function () { this.fakeError = new Error('woops') this.fakeError.statusCode = 500 this.fakeError.message = 'Totally legitimate reason to throw an error' @@ -952,8 +952,8 @@ describe('DockerRunner', function() { .returns(this.fakeContainer)) }) - return it('should produce an error', function(done) { - return this.DockerRunner.kill(this.containerId, err => { + return it('should produce an error', function (done) { + return this.DockerRunner.kill(this.containerId, (err) => { expect(err).to.not.equal(undefined) expect(err).to.equal(this.fakeError) return done() diff --git a/services/clsi/test/unit/js/DraftModeManagerTests.js b/services/clsi/test/unit/js/DraftModeManagerTests.js index 2c30b404bf..937cde90d9 100644 --- a/services/clsi/test/unit/js/DraftModeManagerTests.js +++ b/services/clsi/test/unit/js/DraftModeManagerTests.js @@ -16,8 +16,8 @@ const modulePath = require('path').join( '../../../app/js/DraftModeManager' ) -describe('DraftModeManager', function() { - beforeEach(function() { +describe('DraftModeManager', function () { + beforeEach(function () { return (this.DraftModeManager = SandboxedModule.require(modulePath, { requires: { fs: (this.fs = {}), @@ -26,8 +26,8 @@ describe('DraftModeManager', function() { })) }) - describe('_injectDraftOption', function() { - it('should add draft option into documentclass with existing options', function() { + describe('_injectDraftOption', function () { + it('should add draft option into documentclass with existing options', function () { return this.DraftModeManager._injectDraftOption(`\ \\documentclass[a4paper,foo=bar]{article}\ `).should.equal(`\ @@ -35,7 +35,7 @@ describe('DraftModeManager', function() { `) }) - return it('should add draft option into documentclass with no options', function() { + return it('should add draft option into documentclass with no options', function () { return this.DraftModeManager._injectDraftOption(`\ \\documentclass{article}\ `).should.equal(`\ @@ -44,8 +44,8 @@ describe('DraftModeManager', function() { }) }) - return describe('injectDraftMode', function() { - beforeEach(function() { + return describe('injectDraftMode', function () { + beforeEach(function () { this.filename = '/mock/filename.tex' this.callback = sinon.stub() const content = `\ @@ -59,13 +59,13 @@ Hello world return this.DraftModeManager.injectDraftMode(this.filename, this.callback) }) - it('should read the file', function() { + it('should read the file', function () { return this.fs.readFile .calledWith(this.filename, 'utf8') .should.equal(true) }) - it('should write the modified file', function() { + it('should write the modified file', function () { return this.fs.writeFile .calledWith( this.filename, @@ -79,7 +79,7 @@ Hello world .should.equal(true) }) - return it('should call the callback', function() { + return it('should call the callback', function () { return this.callback.called.should.equal(true) }) }) diff --git a/services/clsi/test/unit/js/LatexRunnerTests.js b/services/clsi/test/unit/js/LatexRunnerTests.js index f480bc8245..9cd0d0ac95 100644 --- a/services/clsi/test/unit/js/LatexRunnerTests.js +++ b/services/clsi/test/unit/js/LatexRunnerTests.js @@ -18,8 +18,8 @@ const modulePath = require('path').join( ) const Path = require('path') -describe('LatexRunner', function() { - beforeEach(function() { +describe('LatexRunner', function () { + beforeEach(function () { let Timer this.LatexRunner = SandboxedModule.require(modulePath, { requires: { @@ -54,16 +54,16 @@ describe('LatexRunner', function() { return (this.env = { foo: '123' }) }) - return describe('runLatex', function() { - beforeEach(function() { + return describe('runLatex', function () { + beforeEach(function () { return (this.CommandRunner.run = sinon.stub().callsArgWith(7, null, { stdout: 'this is stdout', stderr: 'this is stderr' })) }) - describe('normally', function() { - beforeEach(function() { + describe('normally', function () { + beforeEach(function () { return this.LatexRunner.runLatex( this.project_id, { @@ -79,7 +79,7 @@ describe('LatexRunner', function() { ) }) - it('should run the latex command', function() { + it('should run the latex command', function () { return this.CommandRunner.run .calledWith( this.project_id, @@ -93,7 +93,7 @@ describe('LatexRunner', function() { .should.equal(true) }) - it('should record the stdout and stderr', function() { + it('should record the stdout and stderr', function () { this.fs.writeFile .calledWith(this.directory + '/' + 'output.stdout', 'this is stdout') .should.equal(true) @@ -103,8 +103,8 @@ describe('LatexRunner', function() { }) }) - describe('with an .Rtex main file', function() { - beforeEach(function() { + describe('with an .Rtex main file', function () { + beforeEach(function () { return this.LatexRunner.runLatex( this.project_id, { @@ -118,15 +118,15 @@ describe('LatexRunner', function() { ) }) - return it('should run the latex command on the equivalent .tex file', function() { + return it('should run the latex command on the equivalent .tex file', function () { const command = this.CommandRunner.run.args[0][1] const mainFile = command.slice(-1)[0] return mainFile.should.equal('$COMPILE_DIR/main-file.tex') }) }) - return describe('with a flags option', function() { - beforeEach(function() { + return describe('with a flags option', function () { + beforeEach(function () { return this.LatexRunner.runLatex( this.project_id, { @@ -141,10 +141,10 @@ describe('LatexRunner', function() { ) }) - return it('should include the flags in the command', function() { + return it('should include the flags in the command', function () { const command = this.CommandRunner.run.args[0][1] const flags = command.filter( - arg => arg === '-file-line-error' || arg === '-halt-on-error' + (arg) => arg === '-file-line-error' || arg === '-halt-on-error' ) flags.length.should.equal(2) flags[0].should.equal('-file-line-error') diff --git a/services/clsi/test/unit/js/LockManagerTests.js b/services/clsi/test/unit/js/LockManagerTests.js index cb8ab9b747..16a43ade48 100644 --- a/services/clsi/test/unit/js/LockManagerTests.js +++ b/services/clsi/test/unit/js/LockManagerTests.js @@ -19,8 +19,8 @@ const modulePath = require('path').join( const Path = require('path') const Errors = require('../../../app/js/Errors') -describe('DockerLockManager', function() { - beforeEach(function() { +describe('DockerLockManager', function () { + beforeEach(function () { this.LockManager = SandboxedModule.require(modulePath, { requires: { 'settings-sharelatex': {}, @@ -39,14 +39,14 @@ describe('DockerLockManager', function() { return (this.lockFile = '/local/compile/directory/.project-lock') }) - return describe('runWithLock', function() { - beforeEach(function() { + return describe('runWithLock', function () { + beforeEach(function () { this.runner = sinon.stub().callsArgWith(0, null, 'foo', 'bar') return (this.callback = sinon.stub()) }) - describe('normally', function() { - beforeEach(function() { + describe('normally', function () { + beforeEach(function () { this.Lockfile.lock = sinon.stub().callsArgWith(2, null) this.Lockfile.unlock = sinon.stub().callsArgWith(1, null) return this.LockManager.runWithLock( @@ -56,19 +56,19 @@ describe('DockerLockManager', function() { ) }) - it('should run the compile', function() { + it('should run the compile', function () { return this.runner.calledWith().should.equal(true) }) - return it('should call the callback with the response from the compile', function() { + return it('should call the callback with the response from the compile', function () { return this.callback .calledWithExactly(null, 'foo', 'bar') .should.equal(true) }) }) - return describe('when the project is locked', function() { - beforeEach(function() { + return describe('when the project is locked', function () { + beforeEach(function () { this.error = new Error() this.error.code = 'EEXIST' this.Lockfile.lock = sinon.stub().callsArgWith(2, this.error) @@ -80,11 +80,11 @@ describe('DockerLockManager', function() { ) }) - it('should not run the compile', function() { + it('should not run the compile', function () { return this.runner.called.should.equal(false) }) - it('should return an error', function() { + it('should return an error', function () { this.callback .calledWithExactly(sinon.match(Errors.AlreadyCompilingError)) .should.equal(true) diff --git a/services/clsi/test/unit/js/OutputFileFinderTests.js b/services/clsi/test/unit/js/OutputFileFinderTests.js index ee591b408d..4afa25e83e 100644 --- a/services/clsi/test/unit/js/OutputFileFinderTests.js +++ b/services/clsi/test/unit/js/OutputFileFinderTests.js @@ -21,8 +21,8 @@ const path = require('path') const { expect } = require('chai') const { EventEmitter } = require('events') -describe('OutputFileFinder', function() { - beforeEach(function() { +describe('OutputFileFinder', function () { + beforeEach(function () { this.OutputFileFinder = SandboxedModule.require(modulePath, { requires: { fs: (this.fs = {}), @@ -34,8 +34,8 @@ describe('OutputFileFinder', function() { return (this.callback = sinon.stub()) }) - describe('findOutputFiles', function() { - beforeEach(function() { + describe('findOutputFiles', function () { + beforeEach(function () { this.resource_path = 'resource/path.tex' this.output_paths = ['output.pdf', 'extra/file.tex'] this.all_paths = this.output_paths.concat([this.resource_path]) @@ -52,7 +52,7 @@ describe('OutputFileFinder', function() { ) }) - return it('should only return the output files, not directories or resource paths', function() { + return it('should only return the output files, not directories or resource paths', function () { return expect(this.outputFiles).to.deep.equal([ { path: 'output.pdf', @@ -66,8 +66,8 @@ describe('OutputFileFinder', function() { }) }) - return describe('_getAllFiles', function() { - beforeEach(function() { + return describe('_getAllFiles', function () { + beforeEach(function () { this.proc = new EventEmitter() this.proc.stdout = new EventEmitter() this.proc.stdout.setEncoding = sinon.stub().returns(this.proc.stdout) @@ -76,8 +76,8 @@ describe('OutputFileFinder', function() { return this.OutputFileFinder._getAllFiles(this.directory, this.callback) }) - describe('successfully', function() { - beforeEach(function() { + describe('successfully', function () { + beforeEach(function () { this.proc.stdout.emit( 'data', ['/base/dir/main.tex', '/base/dir/chapters/chapter1.tex'].join('\n') + @@ -86,19 +86,19 @@ describe('OutputFileFinder', function() { return this.proc.emit('close', 0) }) - return it('should call the callback with the relative file paths', function() { + return it('should call the callback with the relative file paths', function () { return this.callback .calledWith(null, ['main.tex', 'chapters/chapter1.tex']) .should.equal(true) }) }) - return describe("when the directory doesn't exist", function() { - beforeEach(function() { + return describe("when the directory doesn't exist", function () { + beforeEach(function () { return this.proc.emit('close', 1) }) - return it('should call the callback with a blank array', function() { + return it('should call the callback with a blank array', function () { return this.callback.calledWith(null, []).should.equal(true) }) }) diff --git a/services/clsi/test/unit/js/OutputFileOptimiserTests.js b/services/clsi/test/unit/js/OutputFileOptimiserTests.js index 669044142c..e7f7b8b0c8 100644 --- a/services/clsi/test/unit/js/OutputFileOptimiserTests.js +++ b/services/clsi/test/unit/js/OutputFileOptimiserTests.js @@ -21,8 +21,8 @@ const path = require('path') const { expect } = require('chai') const { EventEmitter } = require('events') -describe('OutputFileOptimiser', function() { - beforeEach(function() { +describe('OutputFileOptimiser', function () { + beforeEach(function () { this.OutputFileOptimiser = SandboxedModule.require(modulePath, { requires: { fs: (this.fs = {}), @@ -37,14 +37,14 @@ describe('OutputFileOptimiser', function() { return (this.callback = sinon.stub()) }) - describe('optimiseFile', function() { - beforeEach(function() { + describe('optimiseFile', function () { + beforeEach(function () { this.src = './output.pdf' return (this.dst = './output.pdf') }) - describe('when the file is not a pdf file', function() { - beforeEach(function(done) { + describe('when the file is not a pdf file', function () { + beforeEach(function (done) { this.src = './output.log' this.OutputFileOptimiser.checkIfPDFIsOptimised = sinon .stub() @@ -55,21 +55,21 @@ describe('OutputFileOptimiser', function() { return this.OutputFileOptimiser.optimiseFile(this.src, this.dst, done) }) - it('should not check if the file is optimised', function() { + it('should not check if the file is optimised', function () { return this.OutputFileOptimiser.checkIfPDFIsOptimised .calledWith(this.src) .should.equal(false) }) - return it('should not optimise the file', function() { + return it('should not optimise the file', function () { return this.OutputFileOptimiser.optimisePDF .calledWith(this.src, this.dst) .should.equal(false) }) }) - describe('when the pdf file is not optimised', function() { - beforeEach(function(done) { + describe('when the pdf file is not optimised', function () { + beforeEach(function (done) { this.OutputFileOptimiser.checkIfPDFIsOptimised = sinon .stub() .callsArgWith(1, null, false) @@ -79,21 +79,21 @@ describe('OutputFileOptimiser', function() { return this.OutputFileOptimiser.optimiseFile(this.src, this.dst, done) }) - it('should check if the pdf is optimised', function() { + it('should check if the pdf is optimised', function () { return this.OutputFileOptimiser.checkIfPDFIsOptimised .calledWith(this.src) .should.equal(true) }) - return it('should optimise the pdf', function() { + return it('should optimise the pdf', function () { return this.OutputFileOptimiser.optimisePDF .calledWith(this.src, this.dst) .should.equal(true) }) }) - return describe('when the pdf file is optimised', function() { - beforeEach(function(done) { + return describe('when the pdf file is optimised', function () { + beforeEach(function (done) { this.OutputFileOptimiser.checkIfPDFIsOptimised = sinon .stub() .callsArgWith(1, null, true) @@ -103,13 +103,13 @@ describe('OutputFileOptimiser', function() { return this.OutputFileOptimiser.optimiseFile(this.src, this.dst, done) }) - it('should check if the pdf is optimised', function() { + it('should check if the pdf is optimised', function () { return this.OutputFileOptimiser.checkIfPDFIsOptimised .calledWith(this.src) .should.equal(true) }) - return it('should not optimise the pdf', function() { + return it('should not optimise the pdf', function () { return this.OutputFileOptimiser.optimisePDF .calledWith(this.src, this.dst) .should.equal(false) @@ -117,8 +117,8 @@ describe('OutputFileOptimiser', function() { }) }) - return describe('checkIfPDFISOptimised', function() { - beforeEach(function() { + return describe('checkIfPDFISOptimised', function () { + beforeEach(function () { this.callback = sinon.stub() this.fd = 1234 this.fs.open = sinon.stub().yields(null, this.fd) @@ -126,18 +126,15 @@ describe('OutputFileOptimiser', function() { .stub() .withArgs(this.fd) .yields(null, 100, Buffer.from('hello /Linearized 1')) - this.fs.close = sinon - .stub() - .withArgs(this.fd) - .yields(null) + this.fs.close = sinon.stub().withArgs(this.fd).yields(null) return this.OutputFileOptimiser.checkIfPDFIsOptimised( this.src, this.callback ) }) - describe('for a linearised file', function() { - beforeEach(function() { + describe('for a linearised file', function () { + beforeEach(function () { this.fs.read = sinon .stub() .withArgs(this.fd) @@ -148,25 +145,25 @@ describe('OutputFileOptimiser', function() { ) }) - it('should open the file', function() { + it('should open the file', function () { return this.fs.open.calledWith(this.src, 'r').should.equal(true) }) - it('should read the header', function() { + it('should read the header', function () { return this.fs.read.calledWith(this.fd).should.equal(true) }) - it('should close the file', function() { + it('should close the file', function () { return this.fs.close.calledWith(this.fd).should.equal(true) }) - return it('should call the callback with a true result', function() { + return it('should call the callback with a true result', function () { return this.callback.calledWith(null, true).should.equal(true) }) }) - return describe('for an unlinearised file', function() { - beforeEach(function() { + return describe('for an unlinearised file', function () { + beforeEach(function () { this.fs.read = sinon .stub() .withArgs(this.fd) @@ -177,19 +174,19 @@ describe('OutputFileOptimiser', function() { ) }) - it('should open the file', function() { + it('should open the file', function () { return this.fs.open.calledWith(this.src, 'r').should.equal(true) }) - it('should read the header', function() { + it('should read the header', function () { return this.fs.read.calledWith(this.fd).should.equal(true) }) - it('should close the file', function() { + it('should close the file', function () { return this.fs.close.calledWith(this.fd).should.equal(true) }) - return it('should call the callback with a false result', function() { + return it('should call the callback with a false result', function () { return this.callback.calledWith(null, false).should.equal(true) }) }) diff --git a/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js b/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js index 1a12cfffce..5e4368fd40 100644 --- a/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js +++ b/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js @@ -21,8 +21,8 @@ const modulePath = require('path').join( ) const tk = require('timekeeper') -describe('ProjectPersistenceManager', function() { - beforeEach(function() { +describe('ProjectPersistenceManager', function () { + beforeEach(function () { this.ProjectPersistenceManager = SandboxedModule.require(modulePath, { requires: { './UrlCache': (this.UrlCache = {}), @@ -44,8 +44,8 @@ describe('ProjectPersistenceManager', function() { return (this.user_id = '1234') }) - describe('refreshExpiryTimeout', function() { - it('should leave expiry alone if plenty of disk', function(done) { + describe('refreshExpiryTimeout', function () { + it('should leave expiry alone if plenty of disk', function (done) { this.diskusage.check.callsArgWith(1, null, { available: 40, total: 100 @@ -59,7 +59,7 @@ describe('ProjectPersistenceManager', function() { }) }) - it('should drop EXPIRY_TIMEOUT 10% if low disk usage', function(done) { + it('should drop EXPIRY_TIMEOUT 10% if low disk usage', function (done) { this.diskusage.check.callsArgWith(1, null, { available: 5, total: 100 @@ -71,7 +71,7 @@ describe('ProjectPersistenceManager', function() { }) }) - it('should not drop EXPIRY_TIMEOUT to below 50% of project_cache_length_ms', function(done) { + it('should not drop EXPIRY_TIMEOUT to below 50% of project_cache_length_ms', function (done) { this.diskusage.check.callsArgWith(1, null, { available: 5, total: 100 @@ -83,7 +83,7 @@ describe('ProjectPersistenceManager', function() { }) }) - it('should not modify EXPIRY_TIMEOUT if there is an error getting disk values', function(done) { + it('should not modify EXPIRY_TIMEOUT if there is an error getting disk values', function (done) { this.diskusage.check.callsArgWith(1, 'Error', { available: 5, total: 100 @@ -95,8 +95,8 @@ describe('ProjectPersistenceManager', function() { }) }) - describe('clearExpiredProjects', function() { - beforeEach(function() { + describe('clearExpiredProjects', function () { + beforeEach(function () { this.project_ids = ['project-id-1', 'project-id-2'] this.ProjectPersistenceManager._findExpiredProjectIds = sinon .stub() @@ -108,21 +108,21 @@ describe('ProjectPersistenceManager', function() { return this.ProjectPersistenceManager.clearExpiredProjects(this.callback) }) - it('should clear each expired project', function() { - return Array.from(this.project_ids).map(project_id => + it('should clear each expired project', function () { + return Array.from(this.project_ids).map((project_id) => this.ProjectPersistenceManager.clearProjectFromCache .calledWith(project_id) .should.equal(true) ) }) - return it('should call the callback', function() { + return it('should call the callback', function () { return this.callback.called.should.equal(true) }) }) - return describe('clearProject', function() { - beforeEach(function() { + return describe('clearProject', function () { + beforeEach(function () { this.ProjectPersistenceManager._clearProjectFromDatabase = sinon .stub() .callsArg(1) @@ -135,25 +135,25 @@ describe('ProjectPersistenceManager', function() { ) }) - it('should clear the project from the database', function() { + it('should clear the project from the database', function () { return this.ProjectPersistenceManager._clearProjectFromDatabase .calledWith(this.project_id) .should.equal(true) }) - it('should clear all the cached Urls for the project', function() { + it('should clear all the cached Urls for the project', function () { return this.UrlCache.clearProject .calledWith(this.project_id) .should.equal(true) }) - it('should clear the project compile folder', function() { + it('should clear the project compile folder', function () { return this.CompileManager.clearProject .calledWith(this.project_id, this.user_id) .should.equal(true) }) - return it('should call the callback', function() { + return it('should call the callback', function () { return this.callback.called.should.equal(true) }) }) diff --git a/services/clsi/test/unit/js/RequestParserTests.js b/services/clsi/test/unit/js/RequestParserTests.js index 25b6b29349..6e6c922571 100644 --- a/services/clsi/test/unit/js/RequestParserTests.js +++ b/services/clsi/test/unit/js/RequestParserTests.js @@ -19,8 +19,8 @@ const modulePath = require('path').join( ) const tk = require('timekeeper') -describe('RequestParser', function() { - beforeEach(function() { +describe('RequestParser', function () { + beforeEach(function () { tk.freeze() this.callback = sinon.stub() this.validResource = { @@ -46,41 +46,41 @@ describe('RequestParser', function() { })) }) - afterEach(function() { + afterEach(function () { return tk.reset() }) - describe('without a top level object', function() { - beforeEach(function() { + describe('without a top level object', function () { + beforeEach(function () { return this.RequestParser.parse([], this.callback) }) - return it('should return an error', function() { + return it('should return an error', function () { return this.callback .calledWith('top level object should have a compile attribute') .should.equal(true) }) }) - describe('without a compile attribute', function() { - beforeEach(function() { + describe('without a compile attribute', function () { + beforeEach(function () { return this.RequestParser.parse({}, this.callback) }) - return it('should return an error', function() { + return it('should return an error', function () { return this.callback .calledWith('top level object should have a compile attribute') .should.equal(true) }) }) - describe('without a valid compiler', function() { - beforeEach(function() { + describe('without a valid compiler', function () { + beforeEach(function () { this.validRequest.compile.options.compiler = 'not-a-compiler' return this.RequestParser.parse(this.validRequest, this.callback) }) - return it('should return an error', function() { + return it('should return an error', function () { return this.callback .calledWith( 'compiler attribute should be one of: pdflatex, latex, xelatex, lualatex' @@ -89,33 +89,33 @@ describe('RequestParser', function() { }) }) - describe('without a compiler specified', function() { - beforeEach(function() { + describe('without a compiler specified', function () { + beforeEach(function () { delete this.validRequest.compile.options.compiler return this.RequestParser.parse(this.validRequest, (error, data) => { this.data = data }) }) - return it('should set the compiler to pdflatex by default', function() { + return it('should set the compiler to pdflatex by default', function () { return this.data.compiler.should.equal('pdflatex') }) }) - describe('with imageName set', function() { - beforeEach(function() { + describe('with imageName set', function () { + beforeEach(function () { return this.RequestParser.parse(this.validRequest, (error, data) => { this.data = data }) }) - return it('should set the imageName', function() { + return it('should set the imageName', function () { return this.data.imageName.should.equal('basicImageName/here:2017-1') }) }) - describe('when image restrictions are present', function() { - beforeEach(function() { + describe('when image restrictions are present', function () { + beforeEach(function () { this.settings.clsi = { docker: {} } this.settings.clsi.docker.allowedImages = [ 'repo/name:tag1', @@ -123,8 +123,8 @@ describe('RequestParser', function() { ] }) - describe('with imageName set to something invalid', function() { - beforeEach(function() { + describe('with imageName set to something invalid', function () { + beforeEach(function () { const request = this.validRequest request.compile.options.imageName = 'something/different:latest' this.RequestParser.parse(request, (error, data) => { @@ -133,15 +133,15 @@ describe('RequestParser', function() { }) }) - it('should throw an error for imageName', function() { + it('should throw an error for imageName', function () { expect(String(this.error)).to.include( 'imageName attribute should be one of' ) }) }) - describe('with imageName set to something valid', function() { - beforeEach(function() { + describe('with imageName set to something valid', function () { + beforeEach(function () { const request = this.validRequest request.compile.options.imageName = 'repo/name:tag1' this.RequestParser.parse(request, (error, data) => { @@ -150,54 +150,54 @@ describe('RequestParser', function() { }) }) - it('should set the imageName', function() { + it('should set the imageName', function () { this.data.imageName.should.equal('repo/name:tag1') }) }) }) - describe('with flags set', function() { - beforeEach(function() { + describe('with flags set', function () { + beforeEach(function () { this.validRequest.compile.options.flags = ['-file-line-error'] return this.RequestParser.parse(this.validRequest, (error, data) => { this.data = data }) }) - return it('should set the flags attribute', function() { + return it('should set the flags attribute', function () { return expect(this.data.flags).to.deep.equal(['-file-line-error']) }) }) - describe('with flags not specified', function() { - beforeEach(function() { + describe('with flags not specified', function () { + beforeEach(function () { return this.RequestParser.parse(this.validRequest, (error, data) => { this.data = data }) }) - return it('it should have an empty flags list', function() { + return it('it should have an empty flags list', function () { return expect(this.data.flags).to.deep.equal([]) }) }) - describe('without a timeout specified', function() { - beforeEach(function() { + describe('without a timeout specified', function () { + beforeEach(function () { delete this.validRequest.compile.options.timeout return this.RequestParser.parse(this.validRequest, (error, data) => { this.data = data }) }) - return it('should set the timeout to MAX_TIMEOUT', function() { + return it('should set the timeout to MAX_TIMEOUT', function () { return this.data.timeout.should.equal( this.RequestParser.MAX_TIMEOUT * 1000 ) }) }) - describe('with a timeout larger than the maximum', function() { - beforeEach(function() { + describe('with a timeout larger than the maximum', function () { + beforeEach(function () { this.validRequest.compile.options.timeout = this.RequestParser.MAX_TIMEOUT + 1 return this.RequestParser.parse(this.validRequest, (error, data) => { @@ -205,62 +205,62 @@ describe('RequestParser', function() { }) }) - return it('should set the timeout to MAX_TIMEOUT', function() { + return it('should set the timeout to MAX_TIMEOUT', function () { return this.data.timeout.should.equal( this.RequestParser.MAX_TIMEOUT * 1000 ) }) }) - describe('with a timeout', function() { - beforeEach(function() { + describe('with a timeout', function () { + beforeEach(function () { return this.RequestParser.parse(this.validRequest, (error, data) => { this.data = data }) }) - return it('should set the timeout (in milliseconds)', function() { + return it('should set the timeout (in milliseconds)', function () { return this.data.timeout.should.equal( this.validRequest.compile.options.timeout * 1000 ) }) }) - describe('with a resource without a path', function() { - beforeEach(function() { + describe('with a resource without a path', function () { + beforeEach(function () { delete this.validResource.path this.validRequest.compile.resources.push(this.validResource) return this.RequestParser.parse(this.validRequest, this.callback) }) - return it('should return an error', function() { + return it('should return an error', function () { return this.callback .calledWith('all resources should have a path attribute') .should.equal(true) }) }) - describe('with a resource with a path', function() { - beforeEach(function() { + describe('with a resource with a path', function () { + beforeEach(function () { this.validResource.path = this.path = 'test.tex' this.validRequest.compile.resources.push(this.validResource) this.RequestParser.parse(this.validRequest, this.callback) return (this.data = this.callback.args[0][1]) }) - return it('should return the path in the parsed response', function() { + return it('should return the path in the parsed response', function () { return this.data.resources[0].path.should.equal(this.path) }) }) - describe('with a resource with a malformed modified date', function() { - beforeEach(function() { + describe('with a resource with a malformed modified date', function () { + beforeEach(function () { this.validResource.modified = 'not-a-date' this.validRequest.compile.resources.push(this.validResource) return this.RequestParser.parse(this.validRequest, this.callback) }) - return it('should return an error', function() { + return it('should return an error', function () { return this.callback .calledWith( 'resource modified date could not be understood: ' + @@ -270,8 +270,8 @@ describe('RequestParser', function() { }) }) - describe('with a resource with a valid date', function() { - beforeEach(function() { + describe('with a resource with a valid date', function () { + beforeEach(function () { this.date = '12:00 01/02/03' this.validResource.modified = this.date this.validRequest.compile.resources.push(this.validResource) @@ -279,7 +279,7 @@ describe('RequestParser', function() { return (this.data = this.callback.args[0][1]) }) - return it('should return the date as a Javascript Date object', function() { + return it('should return the date as a Javascript Date object', function () { ;(this.data.resources[0].modified instanceof Date).should.equal(true) return this.data.resources[0].modified .getTime() @@ -287,15 +287,15 @@ describe('RequestParser', function() { }) }) - describe('with a resource without either a content or URL attribute', function() { - beforeEach(function() { + describe('with a resource without either a content or URL attribute', function () { + beforeEach(function () { delete this.validResource.url delete this.validResource.content this.validRequest.compile.resources.push(this.validResource) return this.RequestParser.parse(this.validRequest, this.callback) }) - return it('should return an error', function() { + return it('should return an error', function () { return this.callback .calledWith( 'all resources should have either a url or content attribute' @@ -304,99 +304,99 @@ describe('RequestParser', function() { }) }) - describe('with a resource where the content is not a string', function() { - beforeEach(function() { + describe('with a resource where the content is not a string', function () { + beforeEach(function () { this.validResource.content = [] this.validRequest.compile.resources.push(this.validResource) return this.RequestParser.parse(this.validRequest, this.callback) }) - return it('should return an error', function() { + return it('should return an error', function () { return this.callback .calledWith('content attribute should be a string') .should.equal(true) }) }) - describe('with a resource where the url is not a string', function() { - beforeEach(function() { + describe('with a resource where the url is not a string', function () { + beforeEach(function () { this.validResource.url = [] this.validRequest.compile.resources.push(this.validResource) return this.RequestParser.parse(this.validRequest, this.callback) }) - return it('should return an error', function() { + return it('should return an error', function () { return this.callback .calledWith('url attribute should be a string') .should.equal(true) }) }) - describe('with a resource with a url', function() { - beforeEach(function() { + describe('with a resource with a url', function () { + beforeEach(function () { this.validResource.url = this.url = 'www.example.com' this.validRequest.compile.resources.push(this.validResource) this.RequestParser.parse(this.validRequest, this.callback) return (this.data = this.callback.args[0][1]) }) - return it('should return the url in the parsed response', function() { + return it('should return the url in the parsed response', function () { return this.data.resources[0].url.should.equal(this.url) }) }) - describe('with a resource with a content attribute', function() { - beforeEach(function() { + describe('with a resource with a content attribute', function () { + beforeEach(function () { this.validResource.content = this.content = 'Hello world' this.validRequest.compile.resources.push(this.validResource) this.RequestParser.parse(this.validRequest, this.callback) return (this.data = this.callback.args[0][1]) }) - return it('should return the content in the parsed response', function() { + return it('should return the content in the parsed response', function () { return this.data.resources[0].content.should.equal(this.content) }) }) - describe('without a root resource path', function() { - beforeEach(function() { + describe('without a root resource path', function () { + beforeEach(function () { delete this.validRequest.compile.rootResourcePath this.RequestParser.parse(this.validRequest, this.callback) return (this.data = this.callback.args[0][1]) }) - return it("should set the root resource path to 'main.tex' by default", function() { + return it("should set the root resource path to 'main.tex' by default", function () { return this.data.rootResourcePath.should.equal('main.tex') }) }) - describe('with a root resource path', function() { - beforeEach(function() { + describe('with a root resource path', function () { + beforeEach(function () { this.validRequest.compile.rootResourcePath = this.path = 'test.tex' this.RequestParser.parse(this.validRequest, this.callback) return (this.data = this.callback.args[0][1]) }) - return it('should return the root resource path in the parsed response', function() { + return it('should return the root resource path in the parsed response', function () { return this.data.rootResourcePath.should.equal(this.path) }) }) - describe('with a root resource path that is not a string', function() { - beforeEach(function() { + describe('with a root resource path that is not a string', function () { + beforeEach(function () { this.validRequest.compile.rootResourcePath = [] return this.RequestParser.parse(this.validRequest, this.callback) }) - return it('should return an error', function() { + return it('should return an error', function () { return this.callback .calledWith('rootResourcePath attribute should be a string') .should.equal(true) }) }) - describe('with a root resource path that needs escaping', function() { - beforeEach(function() { + describe('with a root resource path that needs escaping', function () { + beforeEach(function () { this.badPath = '`rm -rf foo`.tex' this.goodPath = 'rm -rf foo.tex' this.validRequest.compile.rootResourcePath = this.badPath @@ -409,51 +409,51 @@ describe('RequestParser', function() { return (this.data = this.callback.args[0][1]) }) - it('should return the escaped resource', function() { + it('should return the escaped resource', function () { return this.data.rootResourcePath.should.equal(this.goodPath) }) - return it('should also escape the resource path', function() { + return it('should also escape the resource path', function () { return this.data.resources[0].path.should.equal(this.goodPath) }) }) - describe('with a root resource path that has a relative path', function() { - beforeEach(function() { + describe('with a root resource path that has a relative path', function () { + beforeEach(function () { this.validRequest.compile.rootResourcePath = 'foo/../../bar.tex' this.RequestParser.parse(this.validRequest, this.callback) return (this.data = this.callback.args[0][1]) }) - return it('should return an error', function() { + return it('should return an error', function () { return this.callback .calledWith('relative path in root resource') .should.equal(true) }) }) - describe('with a root resource path that has unescaped + relative path', function() { - beforeEach(function() { + describe('with a root resource path that has unescaped + relative path', function () { + beforeEach(function () { this.validRequest.compile.rootResourcePath = 'foo/#../bar.tex' this.RequestParser.parse(this.validRequest, this.callback) return (this.data = this.callback.args[0][1]) }) - return it('should return an error', function() { + return it('should return an error', function () { return this.callback .calledWith('relative path in root resource') .should.equal(true) }) }) - return describe('with an unknown syncType', function() { - beforeEach(function() { + return describe('with an unknown syncType', function () { + beforeEach(function () { this.validRequest.compile.options.syncType = 'unexpected' this.RequestParser.parse(this.validRequest, this.callback) return (this.data = this.callback.args[0][1]) }) - return it('should return an error', function() { + return it('should return an error', function () { return this.callback .calledWith('syncType attribute should be one of: full, incremental') .should.equal(true) diff --git a/services/clsi/test/unit/js/ResourceStateManagerTests.js b/services/clsi/test/unit/js/ResourceStateManagerTests.js index efc4065bae..ca2b625f33 100644 --- a/services/clsi/test/unit/js/ResourceStateManagerTests.js +++ b/services/clsi/test/unit/js/ResourceStateManagerTests.js @@ -20,8 +20,8 @@ const modulePath = require('path').join( const Path = require('path') const Errors = require('../../../app/js/Errors') -describe('ResourceStateManager', function() { - beforeEach(function() { +describe('ResourceStateManager', function () { + beforeEach(function () { this.ResourceStateManager = SandboxedModule.require(modulePath, { singleOnly: true, requires: { @@ -42,13 +42,13 @@ describe('ResourceStateManager', function() { return (this.callback = sinon.stub()) }) - describe('saveProjectState', function() { - beforeEach(function() { + describe('saveProjectState', function () { + beforeEach(function () { return (this.fs.writeFile = sinon.stub().callsArg(2)) }) - describe('when the state is specified', function() { - beforeEach(function() { + describe('when the state is specified', function () { + beforeEach(function () { return this.ResourceStateManager.saveProjectState( this.state, this.resources, @@ -57,19 +57,19 @@ describe('ResourceStateManager', function() { ) }) - it('should write the resource list to disk', function() { + it('should write the resource list to disk', function () { return this.fs.writeFile .calledWith(this.resourceFileName, this.resourceFileContents) .should.equal(true) }) - return it('should call the callback', function() { + return it('should call the callback', function () { return this.callback.called.should.equal(true) }) }) - return describe('when the state is undefined', function() { - beforeEach(function() { + return describe('when the state is undefined', function () { + beforeEach(function () { this.state = undefined this.fs.unlink = sinon.stub().callsArg(1) return this.ResourceStateManager.saveProjectState( @@ -80,25 +80,25 @@ describe('ResourceStateManager', function() { ) }) - it('should unlink the resource file', function() { + it('should unlink the resource file', function () { return this.fs.unlink .calledWith(this.resourceFileName) .should.equal(true) }) - it('should not write the resource list to disk', function() { + it('should not write the resource list to disk', function () { return this.fs.writeFile.called.should.equal(false) }) - return it('should call the callback', function() { + return it('should call the callback', function () { return this.callback.called.should.equal(true) }) }) }) - describe('checkProjectStateMatches', function() { - describe('when the state matches', function() { - beforeEach(function() { + describe('checkProjectStateMatches', function () { + describe('when the state matches', function () { + beforeEach(function () { this.SafeReader.readFile = sinon .stub() .callsArgWith(3, null, this.resourceFileContents) @@ -109,21 +109,21 @@ describe('ResourceStateManager', function() { ) }) - it('should read the resource file', function() { + it('should read the resource file', function () { return this.SafeReader.readFile .calledWith(this.resourceFileName) .should.equal(true) }) - return it('should call the callback with the results', function() { + return it('should call the callback with the results', function () { return this.callback .calledWithMatch(null, this.resources) .should.equal(true) }) }) - return describe('when the state does not match', function() { - beforeEach(function() { + return describe('when the state does not match', function () { + beforeEach(function () { this.SafeReader.readFile = sinon .stub() .callsArgWith(3, null, this.resourceFileContents) @@ -134,7 +134,7 @@ describe('ResourceStateManager', function() { ) }) - it('should call the callback with an error', function() { + it('should call the callback with an error', function () { this.callback .calledWith(sinon.match(Errors.FilesOutOfSyncError)) .should.equal(true) @@ -145,9 +145,9 @@ describe('ResourceStateManager', function() { }) }) - return describe('checkResourceFiles', function() { - describe('when all the files are present', function() { - beforeEach(function() { + return describe('checkResourceFiles', function () { + describe('when all the files are present', function () { + beforeEach(function () { this.allFiles = [ this.resources[0].path, this.resources[1].path, @@ -161,13 +161,13 @@ describe('ResourceStateManager', function() { ) }) - return it('should call the callback', function() { + return it('should call the callback', function () { return this.callback.calledWithExactly().should.equal(true) }) }) - describe('when there is a missing file', function() { - beforeEach(function() { + describe('when there is a missing file', function () { + beforeEach(function () { this.allFiles = [this.resources[0].path, this.resources[1].path] this.fs.stat = sinon.stub().callsArgWith(1, new Error()) return this.ResourceStateManager.checkResourceFiles( @@ -178,7 +178,7 @@ describe('ResourceStateManager', function() { ) }) - it('should call the callback with an error', function() { + it('should call the callback with an error', function () { this.callback .calledWith(sinon.match(Errors.FilesOutOfSyncError)) .should.equal(true) @@ -190,8 +190,8 @@ describe('ResourceStateManager', function() { }) }) - return describe('when a resource contains a relative path', function() { - beforeEach(function() { + return describe('when a resource contains a relative path', function () { + beforeEach(function () { this.resources[0].path = '../foo/bar.tex' this.allFiles = [ this.resources[0].path, @@ -206,7 +206,7 @@ describe('ResourceStateManager', function() { ) }) - it('should call the callback with an error', function() { + it('should call the callback with an error', function () { this.callback.calledWith(sinon.match(Error)).should.equal(true) const message = this.callback.args[0][0].message diff --git a/services/clsi/test/unit/js/ResourceWriterTests.js b/services/clsi/test/unit/js/ResourceWriterTests.js index 36a951361e..030fe70eb2 100644 --- a/services/clsi/test/unit/js/ResourceWriterTests.js +++ b/services/clsi/test/unit/js/ResourceWriterTests.js @@ -20,8 +20,8 @@ const modulePath = require('path').join( ) const path = require('path') -describe('ResourceWriter', function() { - beforeEach(function() { +describe('ResourceWriter', function () { + beforeEach(function () { let Timer this.ResourceWriter = SandboxedModule.require(modulePath, { singleOnly: true, @@ -37,7 +37,7 @@ describe('ResourceWriter', function() { 'logger-sharelatex': { log: sinon.stub(), err: sinon.stub() }, './Metrics': (this.Metrics = { inc: sinon.stub(), - Timer: (Timer = (function() { + Timer: (Timer = (function () { Timer = class Timer { static initClass() { this.prototype.done = sinon.stub() @@ -54,8 +54,8 @@ describe('ResourceWriter', function() { return (this.callback = sinon.stub()) }) - describe('syncResourcesToDisk on a full request', function() { - beforeEach(function() { + describe('syncResourcesToDisk on a full request', function () { + beforeEach(function () { this.resources = ['resource-1-mock', 'resource-2-mock', 'resource-3-mock'] this.ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3) this.ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2) @@ -71,33 +71,33 @@ describe('ResourceWriter', function() { ) }) - it('should remove old files', function() { + it('should remove old files', function () { return this.ResourceWriter._removeExtraneousFiles .calledWith(this.resources, this.basePath) .should.equal(true) }) - it('should write each resource to disk', function() { - return Array.from(this.resources).map(resource => + it('should write each resource to disk', function () { + return Array.from(this.resources).map((resource) => this.ResourceWriter._writeResourceToDisk .calledWith(this.project_id, resource, this.basePath) .should.equal(true) ) }) - it('should store the sync state and resource list', function() { + it('should store the sync state and resource list', function () { return this.ResourceStateManager.saveProjectState .calledWith(this.syncState, this.resources, this.basePath) .should.equal(true) }) - return it('should call the callback', function() { + return it('should call the callback', function () { return this.callback.called.should.equal(true) }) }) - describe('syncResourcesToDisk on an incremental update', function() { - beforeEach(function() { + describe('syncResourcesToDisk on an incremental update', function () { + beforeEach(function () { this.resources = ['resource-1-mock'] this.ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3) this.ResourceWriter._removeExtraneousFiles = sinon @@ -120,39 +120,39 @@ describe('ResourceWriter', function() { ) }) - it('should check the sync state matches', function() { + it('should check the sync state matches', function () { return this.ResourceStateManager.checkProjectStateMatches .calledWith(this.syncState, this.basePath) .should.equal(true) }) - it('should remove old files', function() { + it('should remove old files', function () { return this.ResourceWriter._removeExtraneousFiles .calledWith(this.resources, this.basePath) .should.equal(true) }) - it('should check each resource exists', function() { + it('should check each resource exists', function () { return this.ResourceStateManager.checkResourceFiles .calledWith(this.resources, this.allFiles, this.basePath) .should.equal(true) }) - it('should write each resource to disk', function() { - return Array.from(this.resources).map(resource => + it('should write each resource to disk', function () { + return Array.from(this.resources).map((resource) => this.ResourceWriter._writeResourceToDisk .calledWith(this.project_id, resource, this.basePath) .should.equal(true) ) }) - return it('should call the callback', function() { + return it('should call the callback', function () { return this.callback.called.should.equal(true) }) }) - describe('syncResourcesToDisk on an incremental update when the state does not match', function() { - beforeEach(function() { + describe('syncResourcesToDisk on an incremental update when the state does not match', function () { + beforeEach(function () { this.resources = ['resource-1-mock'] this.ResourceStateManager.checkProjectStateMatches = sinon .stub() @@ -169,19 +169,19 @@ describe('ResourceWriter', function() { ) }) - it('should check whether the sync state matches', function() { + it('should check whether the sync state matches', function () { return this.ResourceStateManager.checkProjectStateMatches .calledWith(this.syncState, this.basePath) .should.equal(true) }) - return it('should call the callback with an error', function() { + return it('should call the callback with an error', function () { return this.callback.calledWith(this.error).should.equal(true) }) }) - describe('_removeExtraneousFiles', function() { - beforeEach(function() { + describe('_removeExtraneousFiles', function () { + beforeEach(function () { this.output_files = [ { path: 'output.pdf', @@ -250,49 +250,49 @@ describe('ResourceWriter', function() { ) }) - it('should find the existing output files', function() { + it('should find the existing output files', function () { return this.OutputFileFinder.findOutputFiles .calledWith(this.resources, this.basePath) .should.equal(true) }) - it('should delete the output files', function() { + it('should delete the output files', function () { return this.ResourceWriter._deleteFileIfNotDirectory .calledWith(path.join(this.basePath, 'output.pdf')) .should.equal(true) }) - it('should delete the stdout log file', function() { + it('should delete the stdout log file', function () { return this.ResourceWriter._deleteFileIfNotDirectory .calledWith(path.join(this.basePath, 'output.stdout')) .should.equal(true) }) - it('should delete the stderr log file', function() { + it('should delete the stderr log file', function () { return this.ResourceWriter._deleteFileIfNotDirectory .calledWith(path.join(this.basePath, 'output.stderr')) .should.equal(true) }) - it('should delete the extra files', function() { + it('should delete the extra files', function () { return this.ResourceWriter._deleteFileIfNotDirectory .calledWith(path.join(this.basePath, 'extra/file.tex')) .should.equal(true) }) - it('should not delete the extra aux files', function() { + it('should not delete the extra aux files', function () { return this.ResourceWriter._deleteFileIfNotDirectory .calledWith(path.join(this.basePath, 'extra.aux')) .should.equal(false) }) - it('should not delete the knitr cache file', function() { + it('should not delete the knitr cache file', function () { return this.ResourceWriter._deleteFileIfNotDirectory .calledWith(path.join(this.basePath, 'cache/_chunk1')) .should.equal(false) }) - it('should not delete the epstopdf converted files', function() { + it('should not delete the epstopdf converted files', function () { return this.ResourceWriter._deleteFileIfNotDirectory .calledWith( path.join(this.basePath, 'figures/image-eps-converted-to.pdf') @@ -300,25 +300,25 @@ describe('ResourceWriter', function() { .should.equal(false) }) - it('should not delete the tikz md5 files', function() { + it('should not delete the tikz md5 files', function () { return this.ResourceWriter._deleteFileIfNotDirectory .calledWith(path.join(this.basePath, 'foo/main-figure0.md5')) .should.equal(false) }) - it('should not delete the tikz dpth files', function() { + it('should not delete the tikz dpth files', function () { return this.ResourceWriter._deleteFileIfNotDirectory .calledWith(path.join(this.basePath, 'foo/main-figure0.dpth')) .should.equal(false) }) - it('should not delete the tikz pdf files', function() { + it('should not delete the tikz pdf files', function () { return this.ResourceWriter._deleteFileIfNotDirectory .calledWith(path.join(this.basePath, 'foo/main-figure0.pdf')) .should.equal(false) }) - it('should not delete the minted pygstyle files', function() { + it('should not delete the minted pygstyle files', function () { return this.ResourceWriter._deleteFileIfNotDirectory .calledWith( path.join(this.basePath, '_minted-main/default-pyg-prefix.pygstyle') @@ -326,13 +326,13 @@ describe('ResourceWriter', function() { .should.equal(false) }) - it('should not delete the minted default pygstyle files', function() { + it('should not delete the minted default pygstyle files', function () { return this.ResourceWriter._deleteFileIfNotDirectory .calledWith(path.join(this.basePath, '_minted-main/default.pygstyle')) .should.equal(false) }) - it('should not delete the minted default pygtex files', function() { + it('should not delete the minted default pygtex files', function () { return this.ResourceWriter._deleteFileIfNotDirectory .calledWith( path.join( @@ -343,7 +343,7 @@ describe('ResourceWriter', function() { .should.equal(false) }) - it('should not delete the markdown md.tex files', function() { + it('should not delete the markdown md.tex files', function () { return this.ResourceWriter._deleteFileIfNotDirectory .calledWith( path.join( @@ -354,18 +354,18 @@ describe('ResourceWriter', function() { .should.equal(false) }) - it('should call the callback', function() { + it('should call the callback', function () { return this.callback.called.should.equal(true) }) - return it('should time the request', function() { + return it('should time the request', function () { return this.Metrics.Timer.prototype.done.called.should.equal(true) }) }) - describe('_writeResourceToDisk', function() { - describe('with a url based resource', function() { - beforeEach(function() { + describe('_writeResourceToDisk', function () { + describe('with a url based resource', function () { + beforeEach(function () { this.fs.mkdir = sinon.stub().callsArg(2) this.resource = { path: 'main.tex', @@ -383,7 +383,7 @@ describe('ResourceWriter', function() { ) }) - it('should ensure the directory exists', function() { + it('should ensure the directory exists', function () { this.fs.mkdir .calledWith( path.dirname(path.join(this.basePath, this.resource.path)) @@ -391,7 +391,7 @@ describe('ResourceWriter', function() { .should.equal(true) }) - it('should write the URL from the cache', function() { + it('should write the URL from the cache', function () { return this.UrlCache.downloadUrlToFile .calledWith( this.project_id, @@ -402,17 +402,17 @@ describe('ResourceWriter', function() { .should.equal(true) }) - it('should call the callback', function() { + it('should call the callback', function () { return this.callback.called.should.equal(true) }) - return it('should not return an error if the resource writer errored', function() { + return it('should not return an error if the resource writer errored', function () { return should.not.exist(this.callback.args[0][0]) }) }) - describe('with a content based resource', function() { - beforeEach(function() { + describe('with a content based resource', function () { + beforeEach(function () { this.resource = { path: 'main.tex', content: 'Hello world' @@ -427,7 +427,7 @@ describe('ResourceWriter', function() { ) }) - it('should ensure the directory exists', function() { + it('should ensure the directory exists', function () { return this.fs.mkdir .calledWith( path.dirname(path.join(this.basePath, this.resource.path)) @@ -435,7 +435,7 @@ describe('ResourceWriter', function() { .should.equal(true) }) - it('should write the contents to disk', function() { + it('should write the contents to disk', function () { return this.fs.writeFile .calledWith( path.join(this.basePath, this.resource.path), @@ -444,13 +444,13 @@ describe('ResourceWriter', function() { .should.equal(true) }) - return it('should call the callback', function() { + return it('should call the callback', function () { return this.callback.called.should.equal(true) }) }) - return describe('with a file path that breaks out of the root folder', function() { - beforeEach(function() { + return describe('with a file path that breaks out of the root folder', function () { + beforeEach(function () { this.resource = { path: '../../main.tex', content: 'Hello world' @@ -464,11 +464,11 @@ describe('ResourceWriter', function() { ) }) - it('should not write to disk', function() { + it('should not write to disk', function () { return this.fs.writeFile.called.should.equal(false) }) - it('should return an error', function() { + it('should return an error', function () { this.callback.calledWith(sinon.match(Error)).should.equal(true) const message = this.callback.args[0][0].message @@ -477,23 +477,23 @@ describe('ResourceWriter', function() { }) }) - return describe('checkPath', function() { - describe('with a valid path', function() { - beforeEach(function() { + return describe('checkPath', function () { + describe('with a valid path', function () { + beforeEach(function () { return this.ResourceWriter.checkPath('foo', 'bar', this.callback) }) - return it('should return the joined path', function() { + return it('should return the joined path', function () { return this.callback.calledWith(null, 'foo/bar').should.equal(true) }) }) - describe('with an invalid path', function() { - beforeEach(function() { + describe('with an invalid path', function () { + beforeEach(function () { this.ResourceWriter.checkPath('foo', 'baz/../../bar', this.callback) }) - it('should return an error', function() { + it('should return an error', function () { this.callback.calledWith(sinon.match(Error)).should.equal(true) const message = this.callback.args[0][0].message @@ -501,8 +501,8 @@ describe('ResourceWriter', function() { }) }) - describe('with another invalid path matching on a prefix', function() { - beforeEach(function() { + describe('with another invalid path matching on a prefix', function () { + beforeEach(function () { return this.ResourceWriter.checkPath( 'foo', '../foobar/baz', @@ -510,7 +510,7 @@ describe('ResourceWriter', function() { ) }) - it('should return an error', function() { + it('should return an error', function () { this.callback.calledWith(sinon.match(Error)).should.equal(true) const message = this.callback.args[0][0].message diff --git a/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js b/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js index b9545a4ce1..9a72168f1f 100644 --- a/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js +++ b/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js @@ -20,8 +20,8 @@ const modulePath = path.join( ) const { expect } = require('chai') -describe('StaticServerForbidSymlinks', function() { - beforeEach(function() { +describe('StaticServerForbidSymlinks', function () { + beforeEach(function () { this.settings = { path: { compilesDir: '/compiles/here' @@ -60,8 +60,8 @@ describe('StaticServerForbidSymlinks', function() { return (this.req.url = '/12345/output.pdf') }) - describe('sending a normal file through', function() { - beforeEach(function() { + describe('sending a normal file through', function () { + beforeEach(function () { return (this.fs.realpath = sinon .stub() .callsArgWith( @@ -71,8 +71,8 @@ describe('StaticServerForbidSymlinks', function() { )) }) - return it('should call next', function(done) { - this.res.sendStatus = function(resCode) { + return it('should call next', function (done) { + this.res.sendStatus = function (resCode) { resCode.should.equal(200) return done() } @@ -80,8 +80,8 @@ describe('StaticServerForbidSymlinks', function() { }) }) - describe('with a missing file', function() { - beforeEach(function() { + describe('with a missing file', function () { + beforeEach(function () { return (this.fs.realpath = sinon .stub() .callsArgWith( @@ -91,8 +91,8 @@ describe('StaticServerForbidSymlinks', function() { )) }) - return it('should send a 404', function(done) { - this.res.sendStatus = function(resCode) { + return it('should send a 404', function (done) { + this.res.sendStatus = function (resCode) { resCode.should.equal(404) return done() } @@ -100,15 +100,15 @@ describe('StaticServerForbidSymlinks', function() { }) }) - describe('with a symlink file', function() { - beforeEach(function() { + describe('with a symlink file', function () { + beforeEach(function () { return (this.fs.realpath = sinon .stub() .callsArgWith(1, null, `/etc/${this.req.params.project_id}/output.pdf`)) }) - return it('should send a 404', function(done) { - this.res.sendStatus = function(resCode) { + return it('should send a 404', function (done) { + this.res.sendStatus = function (resCode) { resCode.should.equal(404) return done() } @@ -116,13 +116,13 @@ describe('StaticServerForbidSymlinks', function() { }) }) - describe('with a relative file', function() { - beforeEach(function() { + describe('with a relative file', function () { + beforeEach(function () { return (this.req.url = '/12345/../67890/output.pdf') }) - return it('should send a 404', function(done) { - this.res.sendStatus = function(resCode) { + return it('should send a 404', function (done) { + this.res.sendStatus = function (resCode) { resCode.should.equal(404) return done() } @@ -130,13 +130,13 @@ describe('StaticServerForbidSymlinks', function() { }) }) - describe('with a unnormalized file containing .', function() { - beforeEach(function() { + describe('with a unnormalized file containing .', function () { + beforeEach(function () { return (this.req.url = '/12345/foo/./output.pdf') }) - return it('should send a 404', function(done) { - this.res.sendStatus = function(resCode) { + return it('should send a 404', function (done) { + this.res.sendStatus = function (resCode) { resCode.should.equal(404) return done() } @@ -144,13 +144,13 @@ describe('StaticServerForbidSymlinks', function() { }) }) - describe('with a file containing an empty path', function() { - beforeEach(function() { + describe('with a file containing an empty path', function () { + beforeEach(function () { return (this.req.url = '/12345/foo//output.pdf') }) - return it('should send a 404', function(done) { - this.res.sendStatus = function(resCode) { + return it('should send a 404', function (done) { + this.res.sendStatus = function (resCode) { resCode.should.equal(404) return done() } @@ -158,13 +158,13 @@ describe('StaticServerForbidSymlinks', function() { }) }) - describe('with a non-project file', function() { - beforeEach(function() { + describe('with a non-project file', function () { + beforeEach(function () { return (this.req.url = '/.foo/output.pdf') }) - return it('should send a 404', function(done) { - this.res.sendStatus = function(resCode) { + return it('should send a 404', function (done) { + this.res.sendStatus = function (resCode) { resCode.should.equal(404) return done() } @@ -172,13 +172,13 @@ describe('StaticServerForbidSymlinks', function() { }) }) - describe('with a file outside the compiledir', function() { - beforeEach(function() { + describe('with a file outside the compiledir', function () { + beforeEach(function () { return (this.req.url = '/../bar/output.pdf') }) - return it('should send a 404', function(done) { - this.res.sendStatus = function(resCode) { + return it('should send a 404', function (done) { + this.res.sendStatus = function (resCode) { resCode.should.equal(404) return done() } @@ -186,13 +186,13 @@ describe('StaticServerForbidSymlinks', function() { }) }) - describe('with a file with no leading /', function() { - beforeEach(function() { + describe('with a file with no leading /', function () { + beforeEach(function () { return (this.req.url = './../bar/output.pdf') }) - return it('should send a 404', function(done) { - this.res.sendStatus = function(resCode) { + return it('should send a 404', function (done) { + this.res.sendStatus = function (resCode) { resCode.should.equal(404) return done() } @@ -200,8 +200,8 @@ describe('StaticServerForbidSymlinks', function() { }) }) - describe('with a github style path', function() { - beforeEach(function() { + describe('with a github style path', function () { + beforeEach(function () { this.req.url = '/henryoswald-latex_example/output/output.log' return (this.fs.realpath = sinon .stub() @@ -212,8 +212,8 @@ describe('StaticServerForbidSymlinks', function() { )) }) - return it('should call next', function(done) { - this.res.sendStatus = function(resCode) { + return it('should call next', function (done) { + this.res.sendStatus = function (resCode) { resCode.should.equal(200) return done() } @@ -221,13 +221,13 @@ describe('StaticServerForbidSymlinks', function() { }) }) - return describe('with an error from fs.realpath', function() { - beforeEach(function() { + return describe('with an error from fs.realpath', function () { + beforeEach(function () { return (this.fs.realpath = sinon.stub().callsArgWith(1, 'error')) }) - return it('should send a 500', function(done) { - this.res.sendStatus = function(resCode) { + return it('should send a 500', function (done) { + this.res.sendStatus = function (resCode) { resCode.should.equal(500) return done() } diff --git a/services/clsi/test/unit/js/TikzManager.js b/services/clsi/test/unit/js/TikzManager.js index 1a9874cbe0..4304edc028 100644 --- a/services/clsi/test/unit/js/TikzManager.js +++ b/services/clsi/test/unit/js/TikzManager.js @@ -16,8 +16,8 @@ const modulePath = require('path').join( '../../../app/js/TikzManager' ) -describe('TikzManager', function() { - beforeEach(function() { +describe('TikzManager', function () { + beforeEach(function () { return (this.TikzManager = SandboxedModule.require(modulePath, { requires: { './ResourceWriter': (this.ResourceWriter = {}), @@ -28,15 +28,15 @@ describe('TikzManager', function() { })) }) - describe('checkMainFile', function() { - beforeEach(function() { + describe('checkMainFile', function () { + beforeEach(function () { this.compileDir = 'compile-dir' this.mainFile = 'main.tex' return (this.callback = sinon.stub()) }) - describe('if there is already an output.tex file in the resources', function() { - beforeEach(function() { + describe('if there is already an output.tex file in the resources', function () { + beforeEach(function () { this.resources = [{ path: 'main.tex' }, { path: 'output.tex' }] return this.TikzManager.checkMainFile( this.compileDir, @@ -46,13 +46,13 @@ describe('TikzManager', function() { ) }) - return it('should call the callback with false ', function() { + return it('should call the callback with false ', function () { return this.callback.calledWithExactly(null, false).should.equal(true) }) }) - return describe('if there is no output.tex file in the resources', function() { - beforeEach(function() { + return describe('if there is no output.tex file in the resources', function () { + beforeEach(function () { this.resources = [{ path: 'main.tex' }] return (this.ResourceWriter.checkPath = sinon .stub() @@ -60,8 +60,8 @@ describe('TikzManager', function() { .callsArgWith(2, null, `${this.compileDir}/${this.mainFile}`)) }) - describe('and the main file contains tikzexternalize', function() { - beforeEach(function() { + describe('and the main file contains tikzexternalize', function () { + beforeEach(function () { this.SafeReader.readFile = sinon .stub() .withArgs(`${this.compileDir}/${this.mainFile}`) @@ -74,19 +74,19 @@ describe('TikzManager', function() { ) }) - it('should look at the file on disk', function() { + it('should look at the file on disk', function () { return this.SafeReader.readFile .calledWith(`${this.compileDir}/${this.mainFile}`) .should.equal(true) }) - return it('should call the callback with true ', function() { + return it('should call the callback with true ', function () { return this.callback.calledWithExactly(null, true).should.equal(true) }) }) - describe('and the main file does not contain tikzexternalize', function() { - beforeEach(function() { + describe('and the main file does not contain tikzexternalize', function () { + beforeEach(function () { this.SafeReader.readFile = sinon .stub() .withArgs(`${this.compileDir}/${this.mainFile}`) @@ -99,19 +99,19 @@ describe('TikzManager', function() { ) }) - it('should look at the file on disk', function() { + it('should look at the file on disk', function () { return this.SafeReader.readFile .calledWith(`${this.compileDir}/${this.mainFile}`) .should.equal(true) }) - return it('should call the callback with false', function() { + return it('should call the callback with false', function () { return this.callback.calledWithExactly(null, false).should.equal(true) }) }) - return describe('and the main file contains \\usepackage{pstool}', function() { - beforeEach(function() { + return describe('and the main file contains \\usepackage{pstool}', function () { + beforeEach(function () { this.SafeReader.readFile = sinon .stub() .withArgs(`${this.compileDir}/${this.mainFile}`) @@ -124,21 +124,21 @@ describe('TikzManager', function() { ) }) - it('should look at the file on disk', function() { + it('should look at the file on disk', function () { return this.SafeReader.readFile .calledWith(`${this.compileDir}/${this.mainFile}`) .should.equal(true) }) - return it('should call the callback with true ', function() { + return it('should call the callback with true ', function () { return this.callback.calledWithExactly(null, true).should.equal(true) }) }) }) }) - return describe('injectOutputFile', function() { - beforeEach(function() { + return describe('injectOutputFile', function () { + beforeEach(function () { this.rootDir = '/mock' this.filename = 'filename.tex' this.callback = sinon.stub() @@ -162,25 +162,25 @@ Hello world ) }) - it('sould check the path', function() { + it('sould check the path', function () { return this.ResourceWriter.checkPath .calledWith(this.rootDir, this.filename) .should.equal(true) }) - it('should read the file', function() { + it('should read the file', function () { return this.fs.readFile .calledWith(`${this.rootDir}/${this.filename}`, 'utf8') .should.equal(true) }) - it('should write out the same file as output.tex', function() { + it('should write out the same file as output.tex', function () { return this.fs.writeFile .calledWith(`${this.rootDir}/output.tex`, this.content, { flag: 'wx' }) .should.equal(true) }) - return it('should call the callback', function() { + return it('should call the callback', function () { return this.callback.called.should.equal(true) }) }) diff --git a/services/clsi/test/unit/js/UrlCacheTests.js b/services/clsi/test/unit/js/UrlCacheTests.js index f5c0f3e20d..023b92de51 100644 --- a/services/clsi/test/unit/js/UrlCacheTests.js +++ b/services/clsi/test/unit/js/UrlCacheTests.js @@ -16,8 +16,8 @@ require('chai').should() const modulePath = require('path').join(__dirname, '../../../app/js/UrlCache') const { EventEmitter } = require('events') -describe('UrlCache', function() { - beforeEach(function() { +describe('UrlCache', function () { + beforeEach(function () { this.callback = sinon.stub() this.url = 'www.example.com/file' this.project_id = 'project-id-123' @@ -34,16 +34,16 @@ describe('UrlCache', function() { })) }) - describe('_doesUrlNeedDownloading', function() { - beforeEach(function() { + describe('_doesUrlNeedDownloading', function () { + beforeEach(function () { this.lastModified = new Date() return (this.lastModifiedRoundedToSeconds = new Date( Math.floor(this.lastModified.getTime() / 1000) * 1000 )) }) - describe('when URL does not exist in cache', function() { - beforeEach(function() { + describe('when URL does not exist in cache', function () { + beforeEach(function () { this.UrlCache._findUrlDetails = sinon.stub().callsArgWith(2, null, null) return this.UrlCache._doesUrlNeedDownloading( this.project_id, @@ -53,21 +53,21 @@ describe('UrlCache', function() { ) }) - return it('should return the callback with true', function() { + return it('should return the callback with true', function () { return this.callback.calledWith(null, true).should.equal(true) }) }) - return describe('when URL does exist in cache', function() { - beforeEach(function() { + return describe('when URL does exist in cache', function () { + beforeEach(function () { this.urlDetails = {} return (this.UrlCache._findUrlDetails = sinon .stub() .callsArgWith(2, null, this.urlDetails)) }) - describe('when the modified date is more recent than the cached modified date', function() { - beforeEach(function() { + describe('when the modified date is more recent than the cached modified date', function () { + beforeEach(function () { this.urlDetails.lastModified = new Date( this.lastModified.getTime() - 1000 ) @@ -79,19 +79,19 @@ describe('UrlCache', function() { ) }) - it('should get the url details', function() { + it('should get the url details', function () { return this.UrlCache._findUrlDetails .calledWith(this.project_id, this.url) .should.equal(true) }) - return it('should return the callback with true', function() { + return it('should return the callback with true', function () { return this.callback.calledWith(null, true).should.equal(true) }) }) - describe('when the cached modified date is more recent than the modified date', function() { - beforeEach(function() { + describe('when the cached modified date is more recent than the modified date', function () { + beforeEach(function () { this.urlDetails.lastModified = new Date( this.lastModified.getTime() + 1000 ) @@ -103,13 +103,13 @@ describe('UrlCache', function() { ) }) - return it('should return the callback with false', function() { + return it('should return the callback with false', function () { return this.callback.calledWith(null, false).should.equal(true) }) }) - describe('when the cached modified date is equal to the modified date', function() { - beforeEach(function() { + describe('when the cached modified date is equal to the modified date', function () { + beforeEach(function () { this.urlDetails.lastModified = this.lastModified return this.UrlCache._doesUrlNeedDownloading( this.project_id, @@ -119,13 +119,13 @@ describe('UrlCache', function() { ) }) - return it('should return the callback with false', function() { + return it('should return the callback with false', function () { return this.callback.calledWith(null, false).should.equal(true) }) }) - describe('when the provided modified date does not exist', function() { - beforeEach(function() { + describe('when the provided modified date does not exist', function () { + beforeEach(function () { this.lastModified = null return this.UrlCache._doesUrlNeedDownloading( this.project_id, @@ -135,13 +135,13 @@ describe('UrlCache', function() { ) }) - return it('should return the callback with true', function() { + return it('should return the callback with true', function () { return this.callback.calledWith(null, true).should.equal(true) }) }) - return describe('when the URL does not have a modified date', function() { - beforeEach(function() { + return describe('when the URL does not have a modified date', function () { + beforeEach(function () { this.urlDetails.lastModified = null return this.UrlCache._doesUrlNeedDownloading( this.project_id, @@ -151,23 +151,23 @@ describe('UrlCache', function() { ) }) - return it('should return the callback with true', function() { + return it('should return the callback with true', function () { return this.callback.calledWith(null, true).should.equal(true) }) }) }) }) - describe('_ensureUrlIsInCache', function() { - beforeEach(function() { + describe('_ensureUrlIsInCache', function () { + beforeEach(function () { this.UrlFetcher.pipeUrlToFileWithRetry = sinon.stub().callsArg(2) return (this.UrlCache._updateOrCreateUrlDetails = sinon .stub() .callsArg(3)) }) - describe('when the URL needs updating', function() { - beforeEach(function() { + describe('when the URL needs updating', function () { + beforeEach(function () { this.UrlCache._doesUrlNeedDownloading = sinon .stub() .callsArgWith(3, null, true) @@ -179,7 +179,7 @@ describe('UrlCache', function() { ) }) - it('should check that the url needs downloading', function() { + it('should check that the url needs downloading', function () { return this.UrlCache._doesUrlNeedDownloading .calledWith( this.project_id, @@ -189,7 +189,7 @@ describe('UrlCache', function() { .should.equal(true) }) - it('should download the URL to the cache file', function() { + it('should download the URL to the cache file', function () { return this.UrlFetcher.pipeUrlToFileWithRetry .calledWith( this.url, @@ -198,7 +198,7 @@ describe('UrlCache', function() { .should.equal(true) }) - it('should update the database entry', function() { + it('should update the database entry', function () { return this.UrlCache._updateOrCreateUrlDetails .calledWith( this.project_id, @@ -208,7 +208,7 @@ describe('UrlCache', function() { .should.equal(true) }) - return it('should return the callback with the cache file path', function() { + return it('should return the callback with the cache file path', function () { return this.callback .calledWith( null, @@ -218,8 +218,8 @@ describe('UrlCache', function() { }) }) - return describe('when the URL does not need updating', function() { - beforeEach(function() { + return describe('when the URL does not need updating', function () { + beforeEach(function () { this.UrlCache._doesUrlNeedDownloading = sinon .stub() .callsArgWith(3, null, false) @@ -231,11 +231,11 @@ describe('UrlCache', function() { ) }) - it('should not download the URL to the cache file', function() { + it('should not download the URL to the cache file', function () { return this.UrlFetcher.pipeUrlToFileWithRetry.called.should.equal(false) }) - return it('should return the callback with the cache file path', function() { + return it('should return the callback with the cache file path', function () { return this.callback .calledWith( null, @@ -246,8 +246,8 @@ describe('UrlCache', function() { }) }) - describe('downloadUrlToFile', function() { - beforeEach(function() { + describe('downloadUrlToFile', function () { + beforeEach(function () { this.cachePath = 'path/to/cached/url' this.destPath = 'path/to/destination' this.UrlCache._copyFile = sinon.stub().callsArg(2) @@ -263,25 +263,25 @@ describe('UrlCache', function() { ) }) - it('should ensure the URL is downloaded and updated in the cache', function() { + it('should ensure the URL is downloaded and updated in the cache', function () { return this.UrlCache._ensureUrlIsInCache .calledWith(this.project_id, this.url, this.lastModified) .should.equal(true) }) - it('should copy the file to the new location', function() { + it('should copy the file to the new location', function () { return this.UrlCache._copyFile .calledWith(this.cachePath, this.destPath) .should.equal(true) }) - return it('should call the callback', function() { + return it('should call the callback', function () { return this.callback.called.should.equal(true) }) }) - describe('_deleteUrlCacheFromDisk', function() { - beforeEach(function() { + describe('_deleteUrlCacheFromDisk', function () { + beforeEach(function () { this.fs.unlink = sinon.stub().callsArg(1) return this.UrlCache._deleteUrlCacheFromDisk( this.project_id, @@ -290,7 +290,7 @@ describe('UrlCache', function() { ) }) - it('should delete the cache file', function() { + it('should delete the cache file', function () { return this.fs.unlink .calledWith( this.UrlCache._cacheFilePathForUrl(this.project_id, this.url) @@ -298,13 +298,13 @@ describe('UrlCache', function() { .should.equal(true) }) - return it('should call the callback', function() { + return it('should call the callback', function () { return this.callback.called.should.equal(true) }) }) - describe('_clearUrlFromCache', function() { - beforeEach(function() { + describe('_clearUrlFromCache', function () { + beforeEach(function () { this.UrlCache._deleteUrlCacheFromDisk = sinon.stub().callsArg(2) this.UrlCache._clearUrlDetails = sinon.stub().callsArg(2) return this.UrlCache._clearUrlFromCache( @@ -314,25 +314,25 @@ describe('UrlCache', function() { ) }) - it('should delete the file on disk', function() { + it('should delete the file on disk', function () { return this.UrlCache._deleteUrlCacheFromDisk .calledWith(this.project_id, this.url) .should.equal(true) }) - it('should clear the entry in the database', function() { + it('should clear the entry in the database', function () { return this.UrlCache._clearUrlDetails .calledWith(this.project_id, this.url) .should.equal(true) }) - return it('should call the callback', function() { + return it('should call the callback', function () { return this.callback.called.should.equal(true) }) }) - return describe('clearProject', function() { - beforeEach(function() { + return describe('clearProject', function () { + beforeEach(function () { this.urls = ['www.example.com/file1', 'www.example.com/file2'] this.UrlCache._findAllUrlsInProject = sinon .stub() @@ -341,15 +341,15 @@ describe('UrlCache', function() { return this.UrlCache.clearProject(this.project_id, this.callback) }) - it('should clear the cache for each url in the project', function() { - return Array.from(this.urls).map(url => + it('should clear the cache for each url in the project', function () { + return Array.from(this.urls).map((url) => this.UrlCache._clearUrlFromCache .calledWith(this.project_id, url) .should.equal(true) ) }) - return it('should call the callback', function() { + return it('should call the callback', function () { return this.callback.called.should.equal(true) }) }) diff --git a/services/clsi/test/unit/js/UrlFetcherTests.js b/services/clsi/test/unit/js/UrlFetcherTests.js index 57bee3a7cf..96ef457433 100644 --- a/services/clsi/test/unit/js/UrlFetcherTests.js +++ b/services/clsi/test/unit/js/UrlFetcherTests.js @@ -15,8 +15,8 @@ require('chai').should() const modulePath = require('path').join(__dirname, '../../../app/js/UrlFetcher') const { EventEmitter } = require('events') -describe('UrlFetcher', function() { - beforeEach(function() { +describe('UrlFetcher', function () { + beforeEach(function () { this.callback = sinon.stub() this.url = 'https://www.example.com/file/here?query=string' return (this.UrlFetcher = SandboxedModule.require(modulePath, { @@ -33,34 +33,34 @@ describe('UrlFetcher', function() { } })) }) - describe('pipeUrlToFileWithRetry', function() { - this.beforeEach(function() { + describe('pipeUrlToFileWithRetry', function () { + this.beforeEach(function () { this.UrlFetcher.pipeUrlToFile = sinon.stub() }) - it('should call pipeUrlToFile', function(done) { + it('should call pipeUrlToFile', function (done) { this.UrlFetcher.pipeUrlToFile.callsArgWith(2) - this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, err => { + this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, (err) => { expect(err).to.equal(undefined) this.UrlFetcher.pipeUrlToFile.called.should.equal(true) done() }) }) - it('should call pipeUrlToFile multiple times on error', function(done) { + it('should call pipeUrlToFile multiple times on error', function (done) { const error = new Error("couldn't download file") this.UrlFetcher.pipeUrlToFile.callsArgWith(2, error) - this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, err => { + this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, (err) => { expect(err).to.equal(error) this.UrlFetcher.pipeUrlToFile.callCount.should.equal(3) done() }) }) - it('should call pipeUrlToFile twice if only 1 error', function(done) { + it('should call pipeUrlToFile twice if only 1 error', function (done) { this.UrlFetcher.pipeUrlToFile.onCall(0).callsArgWith(2, 'error') this.UrlFetcher.pipeUrlToFile.onCall(1).callsArgWith(2) - this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, err => { + this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, (err) => { expect(err).to.equal(undefined) this.UrlFetcher.pipeUrlToFile.callCount.should.equal(2) done() @@ -68,13 +68,13 @@ describe('UrlFetcher', function() { }) }) - describe('pipeUrlToFile', function() { - it('should turn off the cookie jar in request', function() { + describe('pipeUrlToFile', function () { + it('should turn off the cookie jar in request', function () { return this.defaults.calledWith({ jar: false }).should.equal(true) }) - describe('rewrite url domain if filestoreDomainOveride is set', function() { - beforeEach(function() { + describe('rewrite url domain if filestoreDomainOveride is set', function () { + beforeEach(function () { this.path = '/path/to/file/on/disk' this.request.get = sinon .stub() @@ -88,7 +88,7 @@ describe('UrlFetcher', function() { return (this.fs.unlink = (file, callback) => callback()) }) - it('should use the normal domain when override not set', function(done) { + it('should use the normal domain when override not set', function (done) { this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { this.request.get.args[0][0].url.should.equal(this.url) return done() @@ -99,7 +99,7 @@ describe('UrlFetcher', function() { return this.fileStream.emit('finish') }) - return it('should use override domain when filestoreDomainOveride is set', function(done) { + return it('should use override domain when filestoreDomainOveride is set', function (done) { this.settings.filestoreDomainOveride = '192.11.11.11' this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { this.request.get.args[0][0].url.should.equal( @@ -114,8 +114,8 @@ describe('UrlFetcher', function() { }) }) - return describe('pipeUrlToFile', function() { - beforeEach(function(done) { + return describe('pipeUrlToFile', function () { + beforeEach(function (done) { this.path = '/path/to/file/on/disk' this.request.get = sinon .stub() @@ -130,8 +130,8 @@ describe('UrlFetcher', function() { return done() }) - describe('successfully', function() { - beforeEach(function(done) { + describe('successfully', function () { + beforeEach(function (done) { this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { this.callback() return done() @@ -142,32 +142,32 @@ describe('UrlFetcher', function() { return this.fileStream.emit('finish') }) - it('should request the URL', function() { + it('should request the URL', function () { return this.request.get .calledWith(sinon.match({ url: this.url })) .should.equal(true) }) - it('should open the file for writing', function() { + it('should open the file for writing', function () { return this.fs.createWriteStream .calledWith(this.path) .should.equal(true) }) - it('should pipe the URL to the file', function() { + it('should pipe the URL to the file', function () { return this.urlStream.pipe .calledWith(this.fileStream) .should.equal(true) }) - return it('should call the callback', function() { + return it('should call the callback', function () { return this.callback.called.should.equal(true) }) }) - describe('with non success status code', function() { - beforeEach(function(done) { - this.UrlFetcher.pipeUrlToFile(this.url, this.path, err => { + describe('with non success status code', function () { + beforeEach(function (done) { + this.UrlFetcher.pipeUrlToFile(this.url, this.path, (err) => { this.callback(err) return done() }) @@ -176,7 +176,7 @@ describe('UrlFetcher', function() { return this.urlStream.emit('end') }) - it('should call the callback with an error', function() { + it('should call the callback with an error', function () { this.callback.calledWith(sinon.match(Error)).should.equal(true) const message = this.callback.args[0][0].message @@ -186,9 +186,9 @@ describe('UrlFetcher', function() { }) }) - return describe('with error', function() { - beforeEach(function(done) { - this.UrlFetcher.pipeUrlToFile(this.url, this.path, err => { + return describe('with error', function () { + beforeEach(function (done) { + this.UrlFetcher.pipeUrlToFile(this.url, this.path, (err) => { this.callback(err) return done() }) @@ -198,11 +198,11 @@ describe('UrlFetcher', function() { ) }) - it('should call the callback with the error', function() { + it('should call the callback with the error', function () { return this.callback.calledWith(this.error).should.equal(true) }) - return it('should only call the callback once, even if end is called', function() { + return it('should only call the callback once, even if end is called', function () { this.urlStream.emit('end') return this.callback.calledOnce.should.equal(true) }) From b1b2a1c134803013ccc88b416e8f0de339869b87 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@overleaf.com> Date: Wed, 12 Aug 2020 15:11:21 +0100 Subject: [PATCH 611/754] [misc] bump logger-sharelatex to version 2.2.0 --- services/clsi/package-lock.json | 693 ++++++++++++++++++++++---------- services/clsi/package.json | 2 +- 2 files changed, 481 insertions(+), 214 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index cc358212d1..e185ad1233 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -163,9 +163,9 @@ } }, "@google-cloud/common": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-2.2.3.tgz", - "integrity": "sha512-lvw54mGKn8VqVIy2NzAk0l5fntBFX4UwQhHk6HaqkyCQ7WBl5oz4XhzKMtMilozF/3ObPcDogqwuyEWyZ6rnQQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-2.4.0.tgz", + "integrity": "sha512-zWFjBS35eI9leAHhjfeOYlK5Plcuj/77EzstnrJIZbKgF/nkqjcQuGiMCpzCwOfPyUbz8ZaEOYgbHa759AKbjg==", "requires": { "@google-cloud/projectify": "^1.0.0", "@google-cloud/promisify": "^1.0.0", @@ -175,7 +175,44 @@ "extend": "^3.0.2", "google-auth-library": "^5.5.0", "retry-request": "^4.0.0", - "teeny-request": "^5.2.1" + "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" + } + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + } } }, "@google-cloud/debug-agent": { @@ -346,44 +383,86 @@ } }, "@google-cloud/logging": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/@google-cloud/logging/-/logging-5.5.3.tgz", - "integrity": "sha512-TZ/DzHod4icaC7wEEBm0PHYfbhvg0CbCVzKLsdAwj11xSD/egGNOsG5optEQcbAQEPrO1B5xBXfsE0wIBBYjpQ==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@google-cloud/logging/-/logging-7.3.0.tgz", + "integrity": "sha512-xTW1V4MKpYC0mjSugyuiyUoZ9g6A42IhrrO3z7Tt3SmAb2IRj2Gf4RLoguKKncs340ooZFXrrVN/++t2Aj5zgg==", "requires": { "@google-cloud/common": "^2.2.2", "@google-cloud/paginator": "^2.0.0", "@google-cloud/projectify": "^1.0.0", "@google-cloud/promisify": "^1.0.0", - "@opencensus/propagation-stackdriver": "0.0.18", + "@opencensus/propagation-stackdriver": "0.0.20", "arrify": "^2.0.0", "dot-prop": "^5.1.0", - "eventid": "^0.1.2", + "eventid": "^1.0.0", "extend": "^3.0.2", "gcp-metadata": "^3.1.0", - "google-gax": "^1.7.5", + "google-auth-library": "^5.2.2", + "google-gax": "^1.11.0", "is": "^3.3.0", "on-finished": "^2.3.0", - "protobufjs": "^6.8.8", "pumpify": "^2.0.0", "snakecase-keys": "^3.0.0", "stream-events": "^1.0.4", "through2": "^3.0.0", - "type-fest": "^0.8.0" + "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" + } + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "type-fest": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", + "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==" + } } }, "@google-cloud/logging-bunyan": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/logging-bunyan/-/logging-bunyan-2.0.0.tgz", - "integrity": "sha512-9W9B8GQNMlBdQSV+c0492+sMMknn4/428EdSO1xv5Hn07P32N/e4T25y4Gnl9jlrItuZHIXRwYPVSHqUyGb1Zg==", + "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": "^5.5.2", - "google-auth-library": "^5.0.0" + "@google-cloud/logging": "^7.0.0", + "google-auth-library": "^6.0.0" } }, "@google-cloud/paginator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-2.0.1.tgz", - "integrity": "sha512-HZ6UTGY/gHGNriD7OCikYWL/Eu0sTEur2qqse2w6OVsz+57se3nTkqH14JIPxtf0vlEJ8IJN5w3BdZ22pjCB8g==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-2.0.3.tgz", + "integrity": "sha512-kp/pkb2p/p0d8/SKUu4mOq8+HGwF8NPzHWkj+VKrIPQPyMRw8deZtrO/OcSiy9C/7bpfU5Txah5ltUNfPkgEXg==", "requires": { "arrify": "^2.0.0", "extend": "^3.0.2" @@ -557,14 +636,14 @@ } }, "@google-cloud/projectify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-1.0.1.tgz", - "integrity": "sha512-xknDOmsMgOYHksKc1GPbwDLsdej8aRNIA17SlSZgQdyrcC0lx0OGo4VZgYfwoEU1YS8oUxF9Y+6EzDOb0eB7Xg==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-1.0.4.tgz", + "integrity": "sha512-ZdzQUN02eRsmTKfBj9FDL0KNDIFNjBn/d6tHQmA/+FImH5DO6ZV8E7FzxMgAUiVAUq41RFAkb25p1oHOZ8psfg==" }, "@google-cloud/promisify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-1.0.2.tgz", - "integrity": "sha512-7WfV4R/3YV5T30WRZW0lqmvZy9hE2/p9MvpI34WuKa2Wz62mLu5XplGTFEMK6uTbJCLWUxTcZ4J4IyClKucE5g==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-1.0.4.tgz", + "integrity": "sha512-VccZDcOql77obTnFh0TbNED/6ZbbmHDf8UMNnzO1d5g9V0Htfm4k5cllY8P1tJsRKC3zWYGRLaViiupcgVjBoQ==" }, "@google-cloud/trace-agent": { "version": "3.6.1", @@ -728,9 +807,9 @@ } }, "@grpc/grpc-js": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-0.6.9.tgz", - "integrity": "sha512-r1nDOEEiYmAsVYBaS4DPPqdwPOXPw7YhVOnnpPdWhlNtKbYzPash6DqWTTza9gBiYMA5d2Wiq6HzrPqsRaP4yA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.0.5.tgz", + "integrity": "sha512-Hm+xOiqAhcpT9RYM8lc15dbQD7aQurM7ZU8ulmulepiPlN7iwBXXwP3vSBUimoFoApRqz7pSIisXU8pZaCB4og==", "requires": { "semver": "^6.2.0" }, @@ -743,18 +822,18 @@ } }, "@grpc/proto-loader": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.2.tgz", - "integrity": "sha512-eBKD/FPxQoY1x6QONW2nBd54QUEyzcFP9FenujmoeDPy1rutVSHki1s/wR68F6O1QfCNDx+ayBH1O2CVNMzyyw==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.5.tgz", + "integrity": "sha512-WwN9jVNdHRQoOBo9FDH7qU+mgfjPc8GygPYms3M+y3fbQLfnCe/Kv/E01t7JRgnrsOHH8euvSbed3mIalXhwqQ==", "requires": { "lodash.camelcase": "^4.3.0", "protobufjs": "^6.8.6" } }, "@opencensus/core": { - "version": "0.0.18", - "resolved": "https://registry.npmjs.org/@opencensus/core/-/core-0.0.18.tgz", - "integrity": "sha512-PgRQXLyb3bLi8Z6pQct9erYFRdnYAZNQXAEVPf6Xq6IMkZaH20wiOTNNPxEckjI31mq5utgstAbwOn4gJiPjBQ==", + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@opencensus/core/-/core-0.0.20.tgz", + "integrity": "sha512-vqOuTd2yuMpKohp8TNNGUAPjWEGjlnGfB9Rh5e3DKqeyR94YgierNs4LbMqxKtsnwB8Dm2yoEtRuUgoe5vD9DA==", "requires": { "continuation-local-storage": "^3.2.1", "log-driver": "^1.2.7", @@ -771,19 +850,19 @@ } }, "@opencensus/propagation-stackdriver": { - "version": "0.0.18", - "resolved": "https://registry.npmjs.org/@opencensus/propagation-stackdriver/-/propagation-stackdriver-0.0.18.tgz", - "integrity": "sha512-BLwfszIGAfqN2mqGf/atfEu84cWeoLM/YuXGfXDO1iDN2k5GXz4QFyhS8sz5l63HtsYuQqFuV+Ze7ZM0NvJp2A==", + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@opencensus/propagation-stackdriver/-/propagation-stackdriver-0.0.20.tgz", + "integrity": "sha512-P8yuHSLtce+yb+2EZjtTVqG7DQ48laC+IuOWi3X9q78s1Gni5F9+hmbmyP6Nb61jb5BEvXQX1s2rtRI6bayUWA==", "requires": { - "@opencensus/core": "^0.0.18", + "@opencensus/core": "^0.0.20", "hex2dec": "^1.0.1", "uuid": "^3.2.1" } }, "@overleaf/o-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@overleaf/o-error/-/o-error-2.1.0.tgz", - "integrity": "sha512-Zd9sks9LrLw8ErHt/cXeWIkyxWAqNAvNGn7wIjLQJH6TTEEW835PWOhpch+hQwwWsTxWIx/JDj+IpZ3ouw925g==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@overleaf/o-error/-/o-error-3.0.0.tgz", + "integrity": "sha512-LsM2s6Iy9G97ktPo0ys4VxtI/m3ahc1ZHwjo5XnhXtjeIkkkVAehsrcRRoV/yWepPjymB0oZonhcfojpjYR/tg==" }, "@protobufjs/aspromise": { "version": "1.1.2", @@ -905,6 +984,11 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" + }, "@types/caseless": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", @@ -935,6 +1019,14 @@ "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", "dev": true }, + "@types/fs-extra": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.1.tgz", + "integrity": "sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w==", + "requires": { + "@types/node": "*" + } + }, "@types/json-schema": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", @@ -1534,6 +1626,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": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" + }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -1781,6 +1878,11 @@ "which": "^1.2.9" } }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" + }, "d64": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/d64/-/d64-1.0.0.tgz", @@ -1944,9 +2046,9 @@ } }, "dot-prop": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.1.1.tgz", - "integrity": "sha512-QCHI6Lkf+9fJMpwfAFsTvbiSh6ujoPmhCLiDvD/n4dGtLvHfhuBwPdN6z2x4YSOwwtTcLoO/LP70xELWGF/JVA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", "requires": { "is-obj": "^2.0.0" } @@ -1960,7 +2062,6 @@ "version": "0.8.8", "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", - "dev": true, "optional": true, "requires": { "nan": "^2.14.0" @@ -1970,7 +2071,6 @@ "version": "2.14.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "dev": true, "optional": true } } @@ -2613,9 +2713,9 @@ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" }, "eventid": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/eventid/-/eventid-0.1.2.tgz", - "integrity": "sha1-CyMtPiROpbHVKJhBQOpprH7IkhU=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eventid/-/eventid-1.0.0.tgz", + "integrity": "sha512-4upSDsvpxhWPsmw4fsJCp0zj8S7I0qh1lCDTmZXP8V3TtryQKDI8CgQPN+e5JakbWwzaAX3lrdp2b3KSoMSUpw==", "requires": { "d64": "^1.0.0", "uuid": "^3.0.1" @@ -2927,23 +3027,23 @@ } }, "gaxios": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.1.0.tgz", - "integrity": "sha512-Gtpb5sdQmb82sgVkT2GnS2n+Kx4dlFwbeMYcDlD395aEvsLCSQXJJcHt7oJ2LrGxDEAeiOkK79Zv2A8Pzt6CFg==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.3.4.tgz", + "integrity": "sha512-US8UMj8C5pRnao3Zykc4AAVr+cffoNKRTg9Rsf2GiuZCW69vgJj38VK2PzlPuQU73FZ/nTk9/Av6/JGcE1N9vA==", "requires": { "abort-controller": "^3.0.0", "extend": "^3.0.2", - "https-proxy-agent": "^3.0.0", + "https-proxy-agent": "^5.0.0", "is-stream": "^2.0.0", "node-fetch": "^2.3.0" } }, "gcp-metadata": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-3.2.0.tgz", - "integrity": "sha512-ympv+yQ6k5QuWCuwQqnGEvFGS7MBKdcQdj1i188v3bW9QLFIchTGaBCEZxSQapT0jffdn1vdt8oJhB5VBWQO1Q==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-3.5.0.tgz", + "integrity": "sha512-ZQf+DLZ5aKcRpLzYUyBS3yo3N0JSa82lNDO8rj3nMSlovLcz2riKFBsYgDzeXcv75oo5eqB2lx+B14UvPoCRnA==", "requires": { - "gaxios": "^2.0.1", + "gaxios": "^2.1.0", "json-bigint": "^0.3.0" } }, @@ -3002,27 +3102,122 @@ "dev": true }, "google-auth-library": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.5.1.tgz", - "integrity": "sha512-zCtjQccWS/EHYyFdXRbfeSGM/gW+d7uMAcVnvXRnjBXON5ijo6s0nsObP0ifqileIDSbZjTlLtgo+UoN8IFJcg==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.0.6.tgz", + "integrity": "sha512-fWYdRdg55HSJoRq9k568jJA1lrhg9i2xgfhVIMJbskUmbDpJGHsbv9l41DGhCDXM21F9Kn4kUwdysgxSYBYJUw==", "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.2.0", - "gtoken": "^4.1.0", - "jws": "^3.1.5", - "lru-cache": "^5.0.0" + "gaxios": "^3.0.0", + "gcp-metadata": "^4.1.0", + "gtoken": "^5.0.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "dependencies": { + "bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" + }, + "gaxios": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.1.0.tgz", + "integrity": "sha512-DDTn3KXVJJigtz+g0J3vhcfbDbKtAroSTxauWsdnP57sM5KZ3d2c/3D9RKFJ86s43hfw6WULg6TXYw/AYiBlpA==", + "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.4", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.1.4.tgz", + "integrity": "sha512-5J/GIH0yWt/56R3dNaNWPGQ/zXsZOddYECfJaqxFWgrZ9HC2Kvc5vl9upOgUUHKzURjAVf2N+f6tEJiojqXUuA==", + "requires": { + "gaxios": "^3.0.0", + "json-bigint": "^1.0.0" + } + }, + "google-p12-pem": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.2.tgz", + "integrity": "sha512-tbjzndQvSIHGBLzHnhDs3cL4RBjLbLXc2pYvGH+imGVu5b4RMAttUTdnmW2UH0t11QeBTXZ7wlXPS7hrypO/tg==", + "requires": { + "node-forge": "^0.9.0" + } + }, + "gtoken": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.3.tgz", + "integrity": "sha512-Nyd1wZCMRc2dj/mAD0LlfQLcAO06uKdpKJXvK85SGrF5+5+Bpfil9u/2aw35ltvEHjvl0h5FMKN5knEU+9JrOg==", + "requires": { + "gaxios": "^3.0.0", + "google-p12-pem": "^3.0.0", + "jws": "^4.0.0", + "mime": "^2.2.0" + } + }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } } }, "google-gax": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-1.7.5.tgz", - "integrity": "sha512-Tz2DFs8umzDcCBTi2W1cY4vEgAKaYRj70g6Hh/MiiZaJizrly7PgyxsIYUGi7sOpEuAbARQymYKvy5mNi8hEbg==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-1.15.3.tgz", + "integrity": "sha512-3JKJCRumNm3x2EksUTw4P1Rad43FTpqrtW9jzpf3xSMYXx+ogaqTM1vGo7VixHB4xkAyATXVIa3OcNSh8H9zsQ==", "requires": { - "@grpc/grpc-js": "0.6.9", + "@grpc/grpc-js": "~1.0.3", "@grpc/proto-loader": "^0.5.1", + "@types/fs-extra": "^8.0.1", + "@types/long": "^4.0.0", "abort-controller": "^3.0.0", "duplexify": "^3.6.0", "google-auth-library": "^5.0.0", @@ -3030,12 +3225,79 @@ "lodash.at": "^4.6.0", "lodash.has": "^4.5.2", "node-fetch": "^2.6.0", - "protobufjs": "^6.8.8", + "protobufjs": "^6.8.9", "retry-request": "^4.0.0", "semver": "^6.0.0", "walkdir": "^0.4.0" }, "dependencies": { + "@types/node": { + "version": "13.13.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.15.tgz", + "integrity": "sha512-kwbcs0jySLxzLsa2nWUAGOd/s21WU1jebrEdtzhsj1D4Yps1EOuyI1Qcu+FD56dL7NRNIJtDDjcqIG22NwkgLw==" + }, + "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" + } + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "protobufjs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.1.tgz", + "integrity": "sha512-pb8kTchL+1Ceg4lFd5XUpK8PdWacbvV5SK2ULH2ebrYtl4GjJmS24m6CKME67jzV53tbJxHlnNOSqQHbTsR9JQ==", + "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.1", + "@types/node": "^13.7.0", + "long": "^4.0.0" + }, + "dependencies": { + "@types/long": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" + } + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -3044,9 +3306,9 @@ } }, "google-p12-pem": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.2.tgz", - "integrity": "sha512-UfnEARfJKI6pbmC1hfFFm+UAcZxeIwTiEcHfqKe/drMsXD/ilnVjF7zgOGpHXyhuvX6jNJK3S8A0hOQjwtFxEw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.4.tgz", + "integrity": "sha512-S4blHBQWZRnEW44OcR7TL9WR+QCqByRvhNDZ/uuQfpxywfupikf/miba8js1jZi6ZOGv5slgSuoshCWh6EMDzg==", "requires": { "node-forge": "^0.9.0" } @@ -3057,20 +3319,39 @@ "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" }, "gtoken": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.1.0.tgz", - "integrity": "sha512-wqyn2gf5buzEZN4QNmmiiW2i2JkEdZnL7Z/9p44RtZqgt4077m4khRgAYNuu8cBwHWCc6MsP6eDUn/KkF6jFIw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.1.4.tgz", + "integrity": "sha512-VxirzD0SWoFUo5p8RDP8Jt2AGyOmyYcT/pOUgDKJCK+iSw0TMqwrVfY37RXTNmoKwrzmDHSk0GMT9FsgVmnVSA==", "requires": { - "gaxios": "^2.0.0", + "gaxios": "^2.1.0", "google-p12-pem": "^2.0.0", - "jws": "^3.1.5", + "jws": "^4.0.0", "mime": "^2.2.0" }, "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" } } }, @@ -3168,21 +3449,35 @@ } }, "http-proxy-agent": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", - "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", "requires": { - "agent-base": "4", - "debug": "3.1.0" + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" }, "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "agent-base": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", + "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", "requires": { - "ms": "2.0.0" + "debug": "4" } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -3197,18 +3492,26 @@ } }, "https-proxy-agent": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz", - "integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" + "agent-base": "6", + "debug": "4" }, "dependencies": { + "agent-base": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", + "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", + "requires": { + "debug": "4" + } + }, "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "requires": { "ms": "^2.1.1" } @@ -3789,75 +4092,33 @@ } }, "logger-sharelatex": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/logger-sharelatex/-/logger-sharelatex-1.9.1.tgz", - "integrity": "sha512-9s6JQnH/PN+Js2CmI8+J3MQCTNlRzP2Dh4pcekXrV6Jm5J4HzyPi+6d3zfBskZ4NBmaUVw9hC4p5dmdaRmh4mQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/logger-sharelatex/-/logger-sharelatex-2.2.0.tgz", + "integrity": "sha512-ko+OmE25XHJJCiz1R9EgwlfM7J/5olpunUfR3WcfuqOQrcUqsdBrDA2sOytngT0ViwjCR0Fh4qZVPwEWfmrvwA==", "requires": { - "@google-cloud/logging-bunyan": "^2.0.0", - "@overleaf/o-error": "^2.0.0", - "bunyan": "1.8.12", - "raven": "1.1.3", - "request": "2.88.0", - "yn": "^3.1.1" + "@google-cloud/logging-bunyan": "^3.0.0", + "@overleaf/o-error": "^3.0.0", + "bunyan": "^1.8.14", + "node-fetch": "^2.6.0", + "raven": "^2.6.4", + "yn": "^4.0.0" }, "dependencies": { "bunyan": { - "version": "1.8.12", - "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz", - "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=", + "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.10.6", + "moment": "^2.19.3", "mv": "~2", "safe-json-stringify": "~1" } }, - "dtrace-provider": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", - "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", - "optional": true, - "requires": { - "nan": "^2.14.0" - } - }, - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "optional": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } + "yn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-4.0.0.tgz", + "integrity": "sha512-huWiiCS4TxKc4SfgmTwW1K7JmXPPAmuXWYy4j9qjQo4+27Kni8mGhAAi1cloRWmBe2EqcLgt3IGqQoRL/MtPgg==" } } }, @@ -3926,11 +4187,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.2.0", "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.2.0.tgz", @@ -3963,6 +4219,23 @@ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==" }, + "md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "requires": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + }, + "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", @@ -5502,19 +5775,10 @@ "stream-shift": "^1.0.0" } }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "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", @@ -5551,21 +5815,26 @@ "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": { + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + }, "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==" } } }, @@ -6095,9 +6364,9 @@ } }, "snakecase-keys": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/snakecase-keys/-/snakecase-keys-3.1.0.tgz", - "integrity": "sha512-QM038drLbhdOY5HcRQVjO1ZJ1WR7yV5D5TIBzcOB/g3f5HURHhfpYEnvOyzXet8K+MQsgeIUA7O7vn90nAX6EA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/snakecase-keys/-/snakecase-keys-3.2.0.tgz", + "integrity": "sha512-WTJ0NhCH/37J+PU3fuz0x5b6TvtWQChTcKPOndWoUy0pteKOe0hrHMzSRsJOWSIP48EQkzUEsgQPmrG3W8pFNQ==", "requires": { "map-obj": "^4.0.0", "to-snake-case": "^1.0.0" @@ -6211,7 +6480,8 @@ "stack-trace": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", - "integrity": "sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU=" + "integrity": "sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU=", + "dev": true }, "statsd-parser": { "version": "0.0.4", @@ -6480,15 +6750,22 @@ } }, "teeny-request": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-5.3.0.tgz", - "integrity": "sha512-sN9E3JvEBe2CFqB/jpJpw1erWD1C7MxyYCxogHFCQSyZfkHYcdf4wzVQSw7FZxbwcfnS+FP0W9BS0mp6SEOKjg==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-6.0.3.tgz", + "integrity": "sha512-TZG/dfd2r6yeji19es1cUIwAlVD8y+/svB1kAC2Y0bjEyysrfbO8EZvJBRwIE6WkwmUoB7uvWLwTIhJbMXZ1Dw==", "requires": { - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^3.0.0", + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", "node-fetch": "^2.2.0", "stream-events": "^1.0.5", - "uuid": "^3.3.2" + "uuid": "^7.0.0" + }, + "dependencies": { + "uuid": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==" + } } }, "text-table": { @@ -6510,6 +6787,11 @@ "readable-stream": "2 || 3" } }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" + }, "timekeeper": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/timekeeper/-/timekeeper-2.2.0.tgz", @@ -6571,22 +6853,6 @@ "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } - } - }, "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", @@ -6624,7 +6890,8 @@ "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true }, "type-is": { "version": "1.6.18", diff --git a/services/clsi/package.json b/services/clsi/package.json index a526075955..8b93ad75a0 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -28,7 +28,7 @@ "heapdump": "^0.3.15", "lockfile": "^1.0.4", "lodash": "^4.17.15", - "logger-sharelatex": "^1.9.1", + "logger-sharelatex": "^2.2.0", "lynx": "0.2.0", "metrics-sharelatex": "^2.6.0", "mysql": "^2.18.1", From 7caf7f4201f1bb4814860a7aa04ff2e621f0e529 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2020 05:11:31 +0000 Subject: [PATCH 612/754] Bump lodash from 4.17.15 to 4.17.20 Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.20. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.20) Signed-off-by: dependabot[bot] <support@github.com> --- services/clsi/package-lock.json | 6 +++--- services/clsi/package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index e185ad1233..b4d1451d84 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -4029,9 +4029,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, "lodash.at": { "version": "4.6.0", diff --git a/services/clsi/package.json b/services/clsi/package.json index 8b93ad75a0..8c1e2f84fe 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -27,7 +27,7 @@ "fs-extra": "^8.1.0", "heapdump": "^0.3.15", "lockfile": "^1.0.4", - "lodash": "^4.17.15", + "lodash": "^4.17.20", "logger-sharelatex": "^2.2.0", "lynx": "0.2.0", "metrics-sharelatex": "^2.6.0", From 6b93bbfe09c1155c248c82fab56c7d44438b33fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2020 16:50:44 +0000 Subject: [PATCH 613/754] Bump bl from 4.0.1 to 4.0.3 Bumps [bl](https://github.com/rvagg/bl) from 4.0.1 to 4.0.3. - [Release notes](https://github.com/rvagg/bl/releases) - [Commits](https://github.com/rvagg/bl/compare/v4.0.1...v4.0.3) Signed-off-by: dependabot[bot] <support@github.com> --- services/clsi/package-lock.json | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index e185ad1233..6501a57d94 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -1437,13 +1437,20 @@ "integrity": "sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ=" }, "bl": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.1.tgz", - "integrity": "sha512-FL/TdvchukRCuWVxT0YMO/7+L5TNeNrVFvRU2IY63aUyv9mpt8splf2NEr6qXtPo5fya5a66YohQKvGNmLrWNA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", + "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", "readable-stream": "^3.4.0" }, "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -1518,6 +1525,15 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, "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", @@ -3531,6 +3547,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", From 08be54a43e4cf9c5f999aa5fdc3f2bf3d5f0d431 Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Wed, 2 Sep 2020 16:45:49 -0400 Subject: [PATCH 614/754] Decaf cleanup: remove unnecessary Array.from() --- services/clsi/app/js/DockerRunner.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index 723453922f..f719ebd50e 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -8,7 +8,6 @@ // 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 * DS103: Rewrite code to no longer use __guard__ * DS205: Consider reworking code to avoid use of IIFEs @@ -77,7 +76,7 @@ module.exports = DockerRunner = { const volumes = {} volumes[directory] = '/compile' - command = Array.from(command).map((arg) => + command = command.map((arg) => __guardMethod__(arg.toString(), 'replace', (o) => o.replace('$COMPILE_DIR', '/compile') ) @@ -177,7 +176,7 @@ module.exports = DockerRunner = { _callback = function (error, output) {} } const callback = function (...args) { - _callback(...Array.from(args || [])) + _callback(...args) // Only call the callback once return (_callback = function () {}) } @@ -538,7 +537,7 @@ module.exports = DockerRunner = { _callback = function (error, exitCode) {} } const callback = function (...args) { - _callback(...Array.from(args || [])) + _callback(...args) // Only call the callback once return (_callback = function () {}) } @@ -668,7 +667,7 @@ module.exports = DockerRunner = { return callback(error) } const jobs = [] - for (const container of Array.from(containers || [])) { + for (const container of containers) { ;((container) => DockerRunner.examineOldContainer(container, function ( err, From 99648341e236af78a18838abf1328eaf12fd4f29 Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Wed, 2 Sep 2020 16:58:41 -0400 Subject: [PATCH 615/754] Decaf cleanup: remove unnecessary returns --- services/clsi/app/js/DockerRunner.js | 89 +++++++++++++--------------- 1 file changed, 42 insertions(+), 47 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index f719ebd50e..8ea3c62ccd 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -8,7 +8,6 @@ // Fix any style issues and re-enable lint. /* * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns * DS103: Rewrite code to no longer use __guard__ * DS205: Consider reworking code to avoid use of IIFEs * DS207: Consider shorter variations of null checks @@ -120,13 +119,11 @@ module.exports = DockerRunner = { { err: error, project_id }, 'error running container so destroying and retrying' ) - return DockerRunner.destroyContainer(name, null, true, function ( - error - ) { + DockerRunner.destroyContainer(name, null, true, function (error) { if (error != null) { return callback(error) } - return DockerRunner._runAndWaitForContainer( + DockerRunner._runAndWaitForContainer( options, volumes, timeout, @@ -134,12 +131,13 @@ module.exports = DockerRunner = { ) }) } else { - return callback(error, output) + callback(error, output) } }) + // pass back the container name to allow it to be killed return name - }, // pass back the container name to allow it to be killed + }, kill(container_id, callback) { if (callback == null) { @@ -147,7 +145,7 @@ module.exports = DockerRunner = { } logger.log({ container_id }, 'sending kill signal to container') const container = dockerode.getContainer(container_id) - return container.kill(function (error) { + container.kill(function (error) { if ( error != null && __guardMethod__( @@ -164,9 +162,9 @@ module.exports = DockerRunner = { } if (error != null) { logger.error({ err: error, container_id }, 'error killing container') - return callback(error) + callback(error) } else { - return callback() + callback() } }) }, @@ -178,7 +176,7 @@ module.exports = DockerRunner = { const callback = function (...args) { _callback(...args) // Only call the callback once - return (_callback = function () {}) + _callback = function () {} } const { name } = options @@ -189,7 +187,7 @@ module.exports = DockerRunner = { const callbackIfFinished = function () { if (streamEnded && containerReturned) { - return callback(null, output) + callback(null, output) } } @@ -199,10 +197,10 @@ module.exports = DockerRunner = { } output = _output streamEnded = true - return callbackIfFinished() + callbackIfFinished() } - return DockerRunner.startContainer( + DockerRunner.startContainer( options, volumes, attachStreamHandler, @@ -211,7 +209,7 @@ module.exports = DockerRunner = { return callback(error) } - return DockerRunner.waitForContainer(name, timeout, function ( + DockerRunner.waitForContainer(name, timeout, function ( error, exitCode ) { @@ -237,7 +235,7 @@ module.exports = DockerRunner = { (x) => (x.SecurityOpt = null) ) // small log line logger.log({ err, exitCode, options }, 'docker container has exited') - return callbackIfFinished() + callbackIfFinished() }) } ) @@ -364,7 +362,7 @@ module.exports = DockerRunner = { }, startContainer(options, volumes, attachStreamHandler, callback) { - return LockManager.runWithLock( + LockManager.runWithLock( options.name, (releaseLock) => // Check that volumes exist before starting the container. @@ -375,7 +373,7 @@ module.exports = DockerRunner = { if (err != null) { return releaseLock(err) } - return DockerRunner._startContainer( + DockerRunner._startContainer( options, volumes, attachStreamHandler, @@ -405,13 +403,13 @@ module.exports = DockerRunner = { if (!(stats != null ? stats.isDirectory() : undefined)) { return cb(DockerRunner.ERR_NOT_DIRECTORY) } - return cb() + cb() }) const jobs = [] for (const vol in volumes) { ;((vol) => jobs.push((cb) => checkVolume(vol, cb)))(vol) } - return async.series(jobs, callback) + async.series(jobs, callback) }, _startContainer(options, volumes, attachStreamHandler, callback) { @@ -429,7 +427,7 @@ module.exports = DockerRunner = { if (error != null) { return callback(error) } - return startExistingContainer() + startExistingContainer() }) var startExistingContainer = () => DockerRunner.attachToContainer( @@ -439,37 +437,37 @@ module.exports = DockerRunner = { if (error != null) { return callback(error) } - return container.start(function (error) { + container.start(function (error) { if ( error != null && (error != null ? error.statusCode : undefined) !== 304 ) { // already running - return callback(error) + callback(error) } else { - return callback() + callback() } }) } ) - return container.inspect(function (error, stats) { + container.inspect(function (error, stats) { if ((error != null ? error.statusCode : undefined) === 404) { - return createAndStartContainer() + createAndStartContainer() } else if (error != null) { logger.err( { container_name: name, error }, 'unable to inspect container to start' ) - return callback(error) + callback(error) } else { - return startExistingContainer() + startExistingContainer() } }) }, attachToContainer(containerId, attachStreamHandler, attachStartCallback) { const container = dockerode.getContainer(containerId) - return container.attach({ stdout: 1, stderr: 1, stream: 1 }, function ( + container.attach({ stdout: 1, stderr: 1, stream: 1 }, function ( error, stream ) { @@ -495,7 +493,7 @@ module.exports = DockerRunner = { return } if (this.data.length < MAX_OUTPUT) { - return (this.data += data) + this.data += data } else { logger.error( { @@ -506,7 +504,7 @@ module.exports = DockerRunner = { `${name} exceeds max size` ) this.data += `(...truncated at ${MAX_OUTPUT} chars...)` - return (this.overflowed = true) + this.overflowed = true } } // kill container if too much output @@ -526,7 +524,7 @@ module.exports = DockerRunner = { ) ) - return stream.on('end', () => + stream.on('end', () => attachStreamHandler(null, { stdout: stdout.data, stderr: stderr.data }) ) }) @@ -539,7 +537,7 @@ module.exports = DockerRunner = { const callback = function (...args) { _callback(...args) // Only call the callback once - return (_callback = function () {}) + _callback = function () {} } const container = dockerode.getContainer(containerId) @@ -551,11 +549,11 @@ module.exports = DockerRunner = { { container_id: containerId }, 'timeout reached, killing container' ) - return container.kill(function () {}) + container.kill(function () {}) }, timeout) logger.log({ container_id: containerId }, 'waiting for docker container') - return container.wait(function (error, res) { + container.wait(function (error, res) { if (error != null) { clearTimeout(timeoutId) logger.error( @@ -568,14 +566,14 @@ module.exports = DockerRunner = { logger.log({ containerId }, 'docker container timed out') error = DockerRunner.ERR_TIMED_OUT error.timedout = true - return callback(error) + callback(error) } else { clearTimeout(timeoutId) logger.log( { container_id: containerId, exitCode: res.StatusCode }, 'docker container returned' ) - return callback(null, res.StatusCode) + callback(null, res.StatusCode) } }) }, @@ -590,7 +588,7 @@ module.exports = DockerRunner = { if (callback == null) { callback = function (error) {} } - return LockManager.runWithLock( + LockManager.runWithLock( containerName, (releaseLock) => DockerRunner._destroyContainer( @@ -608,7 +606,7 @@ module.exports = DockerRunner = { } logger.log({ container_id: containerId }, 'destroying docker container') const container = dockerode.getContainer(containerId) - return container.remove({ force: shouldForce === true }, function (error) { + container.remove({ force: shouldForce === true }, function (error) { if ( error != null && (error != null ? error.statusCode : undefined) === 404 @@ -627,7 +625,7 @@ module.exports = DockerRunner = { } else { logger.log({ container_id: containerId }, 'destroyed container') } - return callback(error) + callback(error) }) }, @@ -652,17 +650,14 @@ module.exports = DockerRunner = { { containerName: name, created, now, age, maxAge, ttl }, 'checking whether to destroy container' ) - return callback(null, name, container.Id, ttl) + callback(null, name, container.Id, ttl) }, destroyOldContainers(callback) { if (callback == null) { callback = function (error) {} } - return dockerode.listContainers({ all: true }, function ( - error, - containers - ) { + dockerode.listContainers({ all: true }, function (error, containers) { if (error != null) { return callback(error) } @@ -679,7 +674,7 @@ module.exports = DockerRunner = { // strip the / prefix // the LockManager uses the plain container name name = name.slice(1) - return jobs.push((cb) => + jobs.push((cb) => DockerRunner.destroyContainer(name, id, false, () => cb()) ) } @@ -687,7 +682,7 @@ module.exports = DockerRunner = { } // Ignore errors because some containers get stuck but // will be destroyed next time - return async.series(jobs, callback) + async.series(jobs, callback) }) }, From f650da867524708be2abb761ba041e7b61903302 Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Wed, 2 Sep 2020 17:06:35 -0400 Subject: [PATCH 616/754] Decaf cleanup: remove __guard__ --- services/clsi/app/js/DockerRunner.js | 43 ++++++---------------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index 8ea3c62ccd..4c7ae2a59d 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -8,7 +8,6 @@ // Fix any style issues and re-enable lint. /* * decaffeinate suggestions: - * DS103: Rewrite code to no longer use __guard__ * DS205: Consider reworking code to avoid use of IIFEs * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md @@ -28,10 +27,9 @@ const _ = require('lodash') logger.info('using docker runner') const usingSiblingContainers = () => - __guard__( - Settings != null ? Settings.path : undefined, - (x) => x.sandboxedCompilesHostDir - ) != null + Settings != null && + Settings.path != null && + Settings.path.sandboxedCompilesHostDir != null let containerMonitorTimeout let containerMonitorInterval @@ -76,9 +74,7 @@ module.exports = DockerRunner = { volumes[directory] = '/compile' command = command.map((arg) => - __guardMethod__(arg.toString(), 'replace', (o) => - o.replace('$COMPILE_DIR', '/compile') - ) + arg.toString().replace('$COMPILE_DIR', '/compile') ) if (image == null) { ;({ image } = Settings.clsi.docker) @@ -148,11 +144,8 @@ module.exports = DockerRunner = { container.kill(function (error) { if ( error != null && - __guardMethod__( - error != null ? error.message : undefined, - 'match', - (o) => o.match(/Cannot kill container .* is not running/) - ) + error.message != null && + error.message.match(/Cannot kill container .* is not running/) ) { logger.warn( { err: error, container_id }, @@ -230,10 +223,9 @@ module.exports = DockerRunner = { return callback(err) } containerReturned = true - __guard__( - options != null ? options.HostConfig : undefined, - (x) => (x.SecurityOpt = null) - ) // small log line + if (options != null && options.HostConfig != null) { + options.HostConfig.SecurityOpt = null + } logger.log({ err, exitCode, options }, 'docker container has exited') callbackIfFinished() }) @@ -718,20 +710,3 @@ module.exports = DockerRunner = { } DockerRunner.startContainerMonitor() - -function __guard__(value, transform) { - return typeof value !== 'undefined' && value !== null - ? transform(value) - : undefined -} -function __guardMethod__(obj, methodName, transform) { - if ( - typeof obj !== 'undefined' && - obj !== null && - typeof obj[methodName] === 'function' - ) { - return transform(obj, methodName) - } else { - return undefined - } -} From 32f0bbe266b2ae9e85118b42511ab9a9169dd124 Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Wed, 2 Sep 2020 17:16:24 -0400 Subject: [PATCH 617/754] Decaf cleanup: remove IIFEs --- services/clsi/app/js/DockerRunner.js | 54 +++++++++++----------------- 1 file changed, 20 insertions(+), 34 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index 4c7ae2a59d..f2ffc58a9d 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -8,7 +8,6 @@ // Fix any style issues and re-enable lint. /* * decaffeinate suggestions: - * DS205: Consider reworking code to avoid use of IIFEs * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ @@ -278,23 +277,11 @@ module.exports = DockerRunner = { NetworkDisabled: true, Memory: 1024 * 1024 * 1024 * 1024, // 1 Gb User: Settings.clsi.docker.user, - Env: (() => { - const result = [] - for (key in env) { - value = env[key] - result.push(`${key}=${value}`) - } - return result - })(), // convert the environment hash to an array + Env: Object.entries(env).map(([key, value]) => `${key}=${value}`), HostConfig: { - Binds: (() => { - const result1 = [] - for (hostVol in volumes) { - dockerVol = volumes[hostVol] - result1.push(`${hostVol}:${dockerVol}`) - } - return result1 - })(), + Binds: Object.entries(volumes).map( + ([hostVol, dockerVol]) => `${hostVol}:${dockerVol}` + ), LogConfig: { Type: 'none', Config: {} }, Ulimits: [ { @@ -399,7 +386,7 @@ module.exports = DockerRunner = { }) const jobs = [] for (const vol in volumes) { - ;((vol) => jobs.push((cb) => checkVolume(vol, cb)))(vol) + jobs.push((cb) => checkVolume(vol, cb)) } async.series(jobs, callback) }, @@ -655,22 +642,21 @@ module.exports = DockerRunner = { } const jobs = [] for (const container of containers) { - ;((container) => - DockerRunner.examineOldContainer(container, function ( - err, - name, - id, - ttl - ) { - if (name.slice(0, 9) === '/project-' && ttl <= 0) { - // strip the / prefix - // the LockManager uses the plain container name - name = name.slice(1) - jobs.push((cb) => - DockerRunner.destroyContainer(name, id, false, () => cb()) - ) - } - }))(container) + DockerRunner.examineOldContainer(container, function ( + err, + name, + id, + ttl + ) { + if (name.slice(0, 9) === '/project-' && ttl <= 0) { + // strip the / prefix + // the LockManager uses the plain container name + name = name.slice(1) + jobs.push((cb) => + DockerRunner.destroyContainer(name, id, false, () => cb()) + ) + } + }) } // Ignore errors because some containers get stuck but // will be destroyed next time From ee4c08868c8ff0a25d1545bc6d3803baa56652fe Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Thu, 3 Sep 2020 14:43:04 -0400 Subject: [PATCH 618/754] Decaf cleanup: remove default callbacks --- services/clsi/app/js/DockerRunner.js | 37 +++++----------------------- 1 file changed, 6 insertions(+), 31 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index f2ffc58a9d..fbb851f8f3 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -50,9 +50,6 @@ module.exports = DockerRunner = { callback ) { let name - if (callback == null) { - callback = function (error, output) {} - } if (usingSiblingContainers()) { const _newPath = Settings.path.sandboxedCompilesHostDir logger.log( @@ -135,9 +132,6 @@ module.exports = DockerRunner = { }, kill(container_id, callback) { - if (callback == null) { - callback = function (error) {} - } logger.log({ container_id }, 'sending kill signal to container') const container = dockerode.getContainer(container_id) container.kill(function (error) { @@ -162,9 +156,6 @@ module.exports = DockerRunner = { }, _runAndWaitForContainer(options, volumes, timeout, _callback) { - if (_callback == null) { - _callback = function (error, output) {} - } const callback = function (...args) { _callback(...args) // Only call the callback once @@ -366,9 +357,6 @@ module.exports = DockerRunner = { // Check that volumes exist and are directories _checkVolumes(options, volumes, callback) { - if (callback == null) { - callback = function (error, containerName) {} - } if (usingSiblingContainers()) { // Server Pro, with sibling-containers active, skip checks return callback(null) @@ -392,9 +380,6 @@ module.exports = DockerRunner = { }, _startContainer(options, volumes, attachStreamHandler, callback) { - if (callback == null) { - callback = function (error, output) {} - } callback = _.once(callback) const { name } = options @@ -510,9 +495,6 @@ module.exports = DockerRunner = { }, waitForContainer(containerId, timeout, _callback) { - if (_callback == null) { - _callback = function (error, exitCode) {} - } const callback = function (...args) { _callback(...args) // Only call the callback once @@ -564,9 +546,6 @@ module.exports = DockerRunner = { // async exception, but if you delete by id it just does a normal // error callback. We fall back to deleting by name if no id is // supplied. - if (callback == null) { - callback = function (error) {} - } LockManager.runWithLock( containerName, (releaseLock) => @@ -580,9 +559,6 @@ module.exports = DockerRunner = { }, _destroyContainer(containerId, shouldForce, callback) { - if (callback == null) { - callback = function (error) {} - } logger.log({ container_id: containerId }, 'destroying docker container') const container = dockerode.getContainer(containerId) container.remove({ force: shouldForce === true }, function (error) { @@ -614,9 +590,6 @@ module.exports = DockerRunner = { Settings.clsi.docker.maxContainerAge || (oneHour = 60 * 60 * 1000), examineOldContainer(container, callback) { - if (callback == null) { - callback = function (error, name, id, ttl) {} - } const name = container.Name || (container.Names != null ? container.Names[0] : undefined) @@ -633,9 +606,6 @@ module.exports = DockerRunner = { }, destroyOldContainers(callback) { - if (callback == null) { - callback = function (error) {} - } dockerode.listContainers({ all: true }, function (error, containers) { if (error != null) { return callback(error) @@ -677,7 +647,12 @@ module.exports = DockerRunner = { const randomDelay = Math.floor(Math.random() * 5 * 60 * 1000) containerMonitorTimeout = setTimeout(() => { containerMonitorInterval = setInterval( - () => DockerRunner.destroyOldContainers(), + () => + DockerRunner.destroyOldContainers((err) => { + if (err) { + logger.error({ err }, 'failed to destroy old containers') + } + }), (oneHour = 60 * 60 * 1000) ) }, randomDelay) From c52d7d8f02d4ad1a1c77829bfb46594bfceb5a32 Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Thu, 3 Sep 2020 14:56:27 -0400 Subject: [PATCH 619/754] Decaf cleanup: simplify null checks --- services/clsi/app/js/DockerRunner.js | 32 ++++++---------------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index fbb851f8f3..9725fe9331 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -4,13 +4,6 @@ no-return-assign, no-unused-vars, */ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ let DockerRunner, oneHour const Settings = require('settings-sharelatex') const logger = require('logger-sharelatex') @@ -286,10 +279,7 @@ module.exports = DockerRunner = { } } - if ( - (Settings.path != null ? Settings.path.synctexBinHostPath : undefined) != - null - ) { + if (Settings.path != null && Settings.path.synctexBinHostPath != null) { options.HostConfig.Binds.push( `${Settings.path.synctexBinHostPath}:/opt/synctex:ro` ) @@ -367,7 +357,7 @@ module.exports = DockerRunner = { if (err != null) { return cb(err) } - if (!(stats != null ? stats.isDirectory() : undefined)) { + if (!stats.isDirectory()) { return cb(DockerRunner.ERR_NOT_DIRECTORY) } cb() @@ -402,20 +392,17 @@ module.exports = DockerRunner = { return callback(error) } container.start(function (error) { - if ( - error != null && - (error != null ? error.statusCode : undefined) !== 304 - ) { - // already running + if (error != null && error.statusCode !== 304) { callback(error) } else { + // already running callback() } }) } ) container.inspect(function (error, stats) { - if ((error != null ? error.statusCode : undefined) === 404) { + if (error != null && error.statusCode === 404) { createAndStartContainer() } else if (error != null) { logger.err( @@ -562,10 +549,7 @@ module.exports = DockerRunner = { logger.log({ container_id: containerId }, 'destroying docker container') const container = dockerode.getContainer(containerId) container.remove({ force: shouldForce === true }, function (error) { - if ( - error != null && - (error != null ? error.statusCode : undefined) === 404 - ) { + if (error != null && error.statusCode === 404) { logger.warn( { err: error, container_id: containerId }, 'container not found, continuing' @@ -590,9 +574,7 @@ module.exports = DockerRunner = { Settings.clsi.docker.maxContainerAge || (oneHour = 60 * 60 * 1000), examineOldContainer(container, callback) { - const name = - container.Name || - (container.Names != null ? container.Names[0] : undefined) + const name = container.Name || (container.Names && container.Names[0]) const created = container.Created * 1000 // creation time is returned in seconds const now = Date.now() const age = now - created From 2584586ba2b7a6b65d517a476f762ce8c62120c3 Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Thu, 3 Sep 2020 14:58:37 -0400 Subject: [PATCH 620/754] Decaf cleanup: camel-case variables --- services/clsi/app/js/DockerRunner.js | 52 +++++++++++----------------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index 9725fe9331..f9977d8090 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -1,5 +1,4 @@ /* eslint-disable - camelcase, handle-callback-err, no-return-assign, no-unused-vars, @@ -33,7 +32,7 @@ module.exports = DockerRunner = { ERR_TIMED_OUT: new Error('container timed out'), run( - project_id, + projectId, command, directory, image, @@ -90,18 +89,18 @@ module.exports = DockerRunner = { compileGroup ) const fingerprint = DockerRunner._fingerprintContainer(options) - options.name = name = `project-${project_id}-${fingerprint}` + options.name = name = `project-${projectId}-${fingerprint}` // logOptions = _.clone(options) // logOptions?.HostConfig?.SecurityOpt = "secomp used, removed in logging" - logger.log({ project_id }, 'running docker container') + logger.log({ projectId }, 'running docker container') DockerRunner._runAndWaitForContainer(options, volumes, timeout, function ( error, output ) { if (error && error.statusCode === 500) { logger.log( - { err: error, project_id }, + { err: error, projectId }, 'error running container so destroying and retrying' ) DockerRunner.destroyContainer(name, null, true, function (error) { @@ -124,9 +123,9 @@ module.exports = DockerRunner = { return name }, - kill(container_id, callback) { - logger.log({ container_id }, 'sending kill signal to container') - const container = dockerode.getContainer(container_id) + kill(containerId, callback) { + logger.log({ containerId }, 'sending kill signal to container') + const container = dockerode.getContainer(containerId) container.kill(function (error) { if ( error != null && @@ -134,13 +133,13 @@ module.exports = DockerRunner = { error.message.match(/Cannot kill container .* is not running/) ) { logger.warn( - { err: error, container_id }, + { err: error, containerId }, 'container not running, continuing' ) error = null } if (error != null) { - logger.error({ err: error, container_id }, 'error killing container') + logger.error({ err: error, containerId }, 'error killing container') callback(error) } else { callback() @@ -424,7 +423,7 @@ module.exports = DockerRunner = { ) { if (error != null) { logger.error( - { err: error, container_id: containerId }, + { err: error, containerId }, 'error attaching to container' ) return attachStartCallback(error) @@ -432,7 +431,7 @@ module.exports = DockerRunner = { attachStartCallback() } - logger.log({ container_id: containerId }, 'attached to container') + logger.log({ containerId }, 'attached to container') const MAX_OUTPUT = 1024 * 1024 // limit output to 1MB const createStringOutputStream = function (name) { @@ -448,7 +447,7 @@ module.exports = DockerRunner = { } else { logger.error( { - container_id: containerId, + containerId, length: this.data.length, maxLen: MAX_OUTPUT }, @@ -470,7 +469,7 @@ module.exports = DockerRunner = { stream.on('error', (err) => logger.error( - { err, container_id: containerId }, + { err, containerId }, 'error reading from container stream' ) ) @@ -493,21 +492,15 @@ module.exports = DockerRunner = { let timedOut = false const timeoutId = setTimeout(function () { timedOut = true - logger.log( - { container_id: containerId }, - 'timeout reached, killing container' - ) + logger.log({ containerId }, 'timeout reached, killing container') container.kill(function () {}) }, timeout) - logger.log({ container_id: containerId }, 'waiting for docker container') + logger.log({ containerId }, 'waiting for docker container') container.wait(function (error, res) { if (error != null) { clearTimeout(timeoutId) - logger.error( - { err: error, container_id: containerId }, - 'error waiting for container' - ) + logger.error({ err: error, containerId }, 'error waiting for container') return callback(error) } if (timedOut) { @@ -518,7 +511,7 @@ module.exports = DockerRunner = { } else { clearTimeout(timeoutId) logger.log( - { container_id: containerId, exitCode: res.StatusCode }, + { containerId, exitCode: res.StatusCode }, 'docker container returned' ) callback(null, res.StatusCode) @@ -546,23 +539,20 @@ module.exports = DockerRunner = { }, _destroyContainer(containerId, shouldForce, callback) { - logger.log({ container_id: containerId }, 'destroying docker container') + logger.log({ containerId }, 'destroying docker container') const container = dockerode.getContainer(containerId) container.remove({ force: shouldForce === true }, function (error) { if (error != null && error.statusCode === 404) { logger.warn( - { err: error, container_id: containerId }, + { err: error, containerId }, 'container not found, continuing' ) error = null } if (error != null) { - logger.error( - { err: error, container_id: containerId }, - 'error destroying container' - ) + logger.error({ err: error, containerId }, 'error destroying container') } else { - logger.log({ container_id: containerId }, 'destroyed container') + logger.log({ containerId }, 'destroyed container') } callback(error) }) From 44bf38d6dbe6c71e7dcd640f5cdc5721f5d5a4ed Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Thu, 3 Sep 2020 15:05:13 -0400 Subject: [PATCH 621/754] Decaf cleanup: convert async function to sync The examineOldContainer() function doesn't need to use callbacks since it only does synchronous work. --- services/clsi/app/js/DockerRunner.js | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index f9977d8090..cfe8d1166a 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -1,5 +1,4 @@ /* eslint-disable - handle-callback-err, no-return-assign, no-unused-vars, */ @@ -574,7 +573,7 @@ module.exports = DockerRunner = { { containerName: name, created, now, age, maxAge, ttl }, 'checking whether to destroy container' ) - callback(null, name, container.Id, ttl) + return { name, id: container.Id, ttl } }, destroyOldContainers(callback) { @@ -584,21 +583,15 @@ module.exports = DockerRunner = { } const jobs = [] for (const container of containers) { - DockerRunner.examineOldContainer(container, function ( - err, - name, - id, - ttl - ) { - if (name.slice(0, 9) === '/project-' && ttl <= 0) { - // strip the / prefix - // the LockManager uses the plain container name - name = name.slice(1) - jobs.push((cb) => - DockerRunner.destroyContainer(name, id, false, () => cb()) - ) - } - }) + const { name, id, ttl } = DockerRunner.examineOldContainer(container) + if (name.slice(0, 9) === '/project-' && ttl <= 0) { + // strip the / prefix + // the LockManager uses the plain container name + const plainName = name.slice(1) + jobs.push((cb) => + DockerRunner.destroyContainer(plainName, id, false, () => cb()) + ) + } } // Ignore errors because some containers get stuck but // will be destroyed next time From 4905e7db2ccdd4bde4585a808dd90915fdd9f7db Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Thu, 3 Sep 2020 15:30:07 -0400 Subject: [PATCH 622/754] Decaf cleanup: unused vars --- services/clsi/app/js/DockerRunner.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index cfe8d1166a..3e635cdb5e 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -1,8 +1,4 @@ -/* eslint-disable - no-return-assign, - no-unused-vars, -*/ -let DockerRunner, oneHour +let DockerRunner const Settings = require('settings-sharelatex') const logger = require('logger-sharelatex') const Docker = require('dockerode') @@ -14,6 +10,7 @@ const fs = require('fs') const Path = require('path') const _ = require('lodash') +const ONE_HOUR_IN_MS = 60 * 60 * 1000 logger.info('using docker runner') const usingSiblingContainers = () => @@ -559,8 +556,7 @@ module.exports = DockerRunner = { // handle expiry of docker containers - MAX_CONTAINER_AGE: - Settings.clsi.docker.maxContainerAge || (oneHour = 60 * 60 * 1000), + MAX_CONTAINER_AGE: Settings.clsi.docker.maxContainerAge || ONE_HOUR_IN_MS, examineOldContainer(container, callback) { const name = container.Name || (container.Names && container.Names[0]) @@ -618,7 +614,7 @@ module.exports = DockerRunner = { logger.error({ err }, 'failed to destroy old containers') } }), - (oneHour = 60 * 60 * 1000) + ONE_HOUR_IN_MS ) }, randomDelay) }, From a282bccd4838d609613a663cc7c050ae40b98cc8 Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Thu, 3 Sep 2020 15:34:19 -0400 Subject: [PATCH 623/754] Do not instantiate errors at module load time This prevents the right stack trace from being captured. --- services/clsi/app/js/DockerRunner.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index 3e635cdb5e..c422f57267 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -22,11 +22,6 @@ let containerMonitorTimeout let containerMonitorInterval module.exports = DockerRunner = { - ERR_NOT_DIRECTORY: new Error('not a directory'), - ERR_TERMINATED: new Error('terminated'), - ERR_EXITED: new Error('exited'), - ERR_TIMED_OUT: new Error('container timed out'), - run( projectId, command, @@ -190,13 +185,13 @@ module.exports = DockerRunner = { } if (exitCode === 137) { // exit status from kill -9 - err = DockerRunner.ERR_TERMINATED + err = new Error('terminated') err.terminated = true return callback(err) } if (exitCode === 1) { // exit status from chktex - err = DockerRunner.ERR_EXITED + err = new Error('exited') err.code = exitCode return callback(err) } @@ -353,7 +348,7 @@ module.exports = DockerRunner = { return cb(err) } if (!stats.isDirectory()) { - return cb(DockerRunner.ERR_NOT_DIRECTORY) + return cb(new Error('not a directory')) } cb() }) @@ -501,7 +496,7 @@ module.exports = DockerRunner = { } if (timedOut) { logger.log({ containerId }, 'docker container timed out') - error = DockerRunner.ERR_TIMED_OUT + error = new Error('container timed out') error.timedout = true callback(error) } else { From 30a44eddedabf756ee1fc6962a975ffe5bf27dfa Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Thu, 3 Sep 2020 15:50:12 -0400 Subject: [PATCH 624/754] Decaf cleanup: simplify variable declarations --- services/clsi/app/js/DockerRunner.js | 41 ++++++++++++---------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index c422f57267..f4e2659800 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -1,4 +1,3 @@ -let DockerRunner const Settings = require('settings-sharelatex') const logger = require('logger-sharelatex') const Docker = require('dockerode') @@ -21,7 +20,7 @@ const usingSiblingContainers = () => let containerMonitorTimeout let containerMonitorInterval -module.exports = DockerRunner = { +const DockerRunner = { run( projectId, command, @@ -32,7 +31,6 @@ module.exports = DockerRunner = { compileGroup, callback ) { - let name if (usingSiblingContainers()) { const _newPath = Settings.path.sandboxedCompilesHostDir logger.log( @@ -49,14 +47,13 @@ module.exports = DockerRunner = { ) } - const volumes = {} - volumes[directory] = '/compile' + const volumes = { [directory]: '/compile' } command = command.map((arg) => arg.toString().replace('$COMPILE_DIR', '/compile') ) if (image == null) { - ;({ image } = Settings.clsi.docker) + image = Settings.clsi.docker.image } if ( @@ -80,7 +77,8 @@ module.exports = DockerRunner = { compileGroup ) const fingerprint = DockerRunner._fingerprintContainer(options) - options.name = name = `project-${projectId}-${fingerprint}` + const name = `project-${projectId}-${fingerprint}` + options.name = name // logOptions = _.clone(options) // logOptions?.HostConfig?.SecurityOpt = "secomp used, removed in logging" @@ -179,19 +177,18 @@ module.exports = DockerRunner = { error, exitCode ) { - let err if (error != null) { return callback(error) } if (exitCode === 137) { // exit status from kill -9 - err = new Error('terminated') + const err = new Error('terminated') err.terminated = true return callback(err) } if (exitCode === 1) { // exit status from chktex - err = new Error('exited') + const err = new Error('exited') err.code = exitCode return callback(err) } @@ -199,7 +196,7 @@ module.exports = DockerRunner = { if (options != null && options.HostConfig != null) { options.HostConfig.SecurityOpt = null } - logger.log({ err, exitCode, options }, 'docker container has exited') + logger.log({ exitCode, options }, 'docker container has exited') callbackIfFinished() }) } @@ -214,13 +211,11 @@ module.exports = DockerRunner = { environment, compileGroup ) { - let m, year - let key, value, hostVol, dockerVol const timeoutInSeconds = timeout / 1000 const dockerVolumes = {} - for (hostVol in volumes) { - dockerVol = volumes[hostVol] + for (const hostVol in volumes) { + const dockerVol = volumes[hostVol] dockerVolumes[dockerVol] = {} if (volumes[hostVol].slice(-3).indexOf(':r') === -1) { @@ -231,17 +226,14 @@ module.exports = DockerRunner = { // merge settings and environment parameter const env = {} for (const src of [Settings.clsi.docker.env, environment || {}]) { - for (key in src) { - value = src[key] + for (const key in src) { + const value = src[key] env[key] = value } } // set the path based on the image year - if ((m = image.match(/:([0-9]+)\.[0-9]+/))) { - year = m[1] - } else { - year = '2014' - } + const match = image.match(/:([0-9]+)\.[0-9]+/) + const year = match ? match[1] : '2014' env.PATH = `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/${year}/bin/x86_64-linux/` const options = { Cmd: command, @@ -296,8 +288,7 @@ module.exports = DockerRunner = { Settings.clsi.docker.compileGroupConfig[compileGroup] ) { const override = Settings.clsi.docker.compileGroupConfig[compileGroup] - let key - for (key in override) { + for (const key in override) { _.set(options, key, override[key]) } } @@ -627,3 +618,5 @@ module.exports = DockerRunner = { } DockerRunner.startContainerMonitor() + +module.exports = DockerRunner From a853950a995d7a415c092efe5f723ecc39533d8f Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Thu, 3 Sep 2020 15:50:45 -0400 Subject: [PATCH 625/754] Fix container monitor cleanup function The intent here is clearly to clear both the timeout and the interval. --- services/clsi/app/js/DockerRunner.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index f4e2659800..33000d7ba6 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -611,8 +611,8 @@ const DockerRunner = { containerMonitorTimeout = undefined } if (containerMonitorInterval) { - clearInterval(containerMonitorTimeout) - containerMonitorTimeout = undefined + clearInterval(containerMonitorInterval) + containerMonitorInterval = undefined } } } From 5cd889038e4820c49aee1fffe560382a5054329c Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Thu, 3 Sep 2020 15:52:09 -0400 Subject: [PATCH 626/754] Use _.once() instead of ad hoc implementation --- services/clsi/app/js/DockerRunner.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index 33000d7ba6..e355f95067 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -137,12 +137,7 @@ const DockerRunner = { }, _runAndWaitForContainer(options, volumes, timeout, _callback) { - const callback = function (...args) { - _callback(...args) - // Only call the callback once - _callback = function () {} - } - + const callback = _.once(_callback) const { name } = options let streamEnded = false @@ -463,11 +458,7 @@ const DockerRunner = { }, waitForContainer(containerId, timeout, _callback) { - const callback = function (...args) { - _callback(...args) - // Only call the callback once - _callback = function () {} - } + const callback = _.once(_callback) const container = dockerode.getContainer(containerId) From 67f4a6eeebd0500c1f7f17a5259c540dfc0b9795 Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Thu, 3 Sep 2020 15:58:16 -0400 Subject: [PATCH 627/754] Decaf cleanup: normalize functions Use function keyword for declarations and arrow functions for callbacks. --- services/clsi/app/js/DockerRunner.js | 109 ++++++++++++++------------- 1 file changed, 57 insertions(+), 52 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index e355f95067..d357a241d0 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -12,10 +12,13 @@ const _ = require('lodash') const ONE_HOUR_IN_MS = 60 * 60 * 1000 logger.info('using docker runner') -const usingSiblingContainers = () => - Settings != null && - Settings.path != null && - Settings.path.sandboxedCompilesHostDir != null +function usingSiblingContainers() { + return ( + Settings != null && + Settings.path != null && + Settings.path.sandboxedCompilesHostDir != null + ) +} let containerMonitorTimeout let containerMonitorInterval @@ -83,30 +86,32 @@ const DockerRunner = { // logOptions = _.clone(options) // logOptions?.HostConfig?.SecurityOpt = "secomp used, removed in logging" logger.log({ projectId }, 'running docker container') - DockerRunner._runAndWaitForContainer(options, volumes, timeout, function ( - error, - output - ) { - if (error && error.statusCode === 500) { - logger.log( - { err: error, projectId }, - 'error running container so destroying and retrying' - ) - DockerRunner.destroyContainer(name, null, true, function (error) { - if (error != null) { - return callback(error) - } - DockerRunner._runAndWaitForContainer( - options, - volumes, - timeout, - callback + DockerRunner._runAndWaitForContainer( + options, + volumes, + timeout, + (error, output) => { + if (error && error.statusCode === 500) { + logger.log( + { err: error, projectId }, + 'error running container so destroying and retrying' ) - }) - } else { - callback(error, output) + DockerRunner.destroyContainer(name, null, true, (error) => { + if (error != null) { + return callback(error) + } + DockerRunner._runAndWaitForContainer( + options, + volumes, + timeout, + callback + ) + }) + } else { + callback(error, output) + } } - }) + ) // pass back the container name to allow it to be killed return name @@ -115,7 +120,7 @@ const DockerRunner = { kill(containerId, callback) { logger.log({ containerId }, 'sending kill signal to container') const container = dockerode.getContainer(containerId) - container.kill(function (error) { + container.kill((error) => { if ( error != null && error.message != null && @@ -144,13 +149,13 @@ const DockerRunner = { let containerReturned = false let output = {} - const callbackIfFinished = function () { + function callbackIfFinished() { if (streamEnded && containerReturned) { callback(null, output) } } - const attachStreamHandler = function (error, _output) { + function attachStreamHandler(error, _output) { if (error != null) { return callback(error) } @@ -163,15 +168,12 @@ const DockerRunner = { options, volumes, attachStreamHandler, - function (error, containerId) { + (error, containerId) => { if (error != null) { return callback(error) } - DockerRunner.waitForContainer(name, timeout, function ( - error, - exitCode - ) { + DockerRunner.waitForContainer(name, timeout, (error, exitCode) => { if (error != null) { return callback(error) } @@ -305,7 +307,7 @@ const DockerRunner = { // When a container is started with volume pointing to a // non-existent directory then docker creates the directory but // with root ownership. - DockerRunner._checkVolumes(options, volumes, function (err) { + DockerRunner._checkVolumes(options, volumes, (err) => { if (err != null) { return releaseLock(err) } @@ -329,7 +331,7 @@ const DockerRunner = { } const checkVolume = (path, cb) => - fs.stat(path, function (err, stats) { + fs.stat(path, (err, stats) => { if (err != null) { return cb(err) } @@ -352,22 +354,24 @@ const DockerRunner = { logger.log({ container_name: name }, 'starting container') const container = dockerode.getContainer(name) - const createAndStartContainer = () => - dockerode.createContainer(options, function (error, container) { + function createAndStartContainer() { + dockerode.createContainer(options, (error, container) => { if (error != null) { return callback(error) } startExistingContainer() }) - var startExistingContainer = () => + } + + function startExistingContainer() { DockerRunner.attachToContainer( options.name, attachStreamHandler, - function (error) { + (error) => { if (error != null) { return callback(error) } - container.start(function (error) { + container.start((error) => { if (error != null && error.statusCode !== 304) { callback(error) } else { @@ -377,7 +381,9 @@ const DockerRunner = { }) } ) - container.inspect(function (error, stats) { + } + + container.inspect((error, stats) => { if (error != null && error.statusCode === 404) { createAndStartContainer() } else if (error != null) { @@ -394,10 +400,7 @@ const DockerRunner = { attachToContainer(containerId, attachStreamHandler, attachStartCallback) { const container = dockerode.getContainer(containerId) - container.attach({ stdout: 1, stderr: 1, stream: 1 }, function ( - error, - stream - ) { + container.attach({ stdout: 1, stderr: 1, stream: 1 }, (error, stream) => { if (error != null) { logger.error( { err: error, containerId }, @@ -411,7 +414,7 @@ const DockerRunner = { logger.log({ containerId }, 'attached to container') const MAX_OUTPUT = 1024 * 1024 // limit output to 1MB - const createStringOutputStream = function (name) { + function createStringOutputStream(name) { return { data: '', overflowed: false, @@ -463,14 +466,16 @@ const DockerRunner = { const container = dockerode.getContainer(containerId) let timedOut = false - const timeoutId = setTimeout(function () { + const timeoutId = setTimeout(() => { timedOut = true logger.log({ containerId }, 'timeout reached, killing container') - container.kill(function () {}) + container.kill((err) => { + logger.warn({ err, containerId }, 'failed to kill container') + }) }, timeout) logger.log({ containerId }, 'waiting for docker container') - container.wait(function (error, res) { + container.wait((error, res) => { if (error != null) { clearTimeout(timeoutId) logger.error({ err: error, containerId }, 'error waiting for container') @@ -514,7 +519,7 @@ const DockerRunner = { _destroyContainer(containerId, shouldForce, callback) { logger.log({ containerId }, 'destroying docker container') const container = dockerode.getContainer(containerId) - container.remove({ force: shouldForce === true }, function (error) { + container.remove({ force: shouldForce === true }, (error) => { if (error != null && error.statusCode === 404) { logger.warn( { err: error, containerId }, @@ -550,7 +555,7 @@ const DockerRunner = { }, destroyOldContainers(callback) { - dockerode.listContainers({ all: true }, function (error, containers) { + dockerode.listContainers({ all: true }, (error, containers) => { if (error != null) { return callback(error) } From 1c13f6fe94a84c4bdcfaa78be7167d8b5fb26e32 Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Thu, 3 Sep 2020 17:10:24 -0400 Subject: [PATCH 628/754] Mount /home/tex in an anonymous volume When we mount the container's root filesystem as read-only, mount an anonymous volume in /home/tex so that it's writable. Our TeX Live images have cached content in /home/tex. This content will automatically get copied by Docker into this anonymous volume. --- services/clsi/app/js/DockerRunner.js | 3 ++- services/clsi/test/unit/js/DockerRunnerTests.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index d357a241d0..0ff8109585 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -277,6 +277,7 @@ const DockerRunner = { if (Settings.clsi.docker.Readonly) { options.HostConfig.ReadonlyRootfs = true options.HostConfig.Tmpfs = { '/tmp': 'rw,noexec,nosuid,size=65536k' } + options.Volumes['/home/tex'] = {} } // Allow per-compile group overriding of individual settings @@ -519,7 +520,7 @@ const DockerRunner = { _destroyContainer(containerId, shouldForce, callback) { logger.log({ containerId }, 'destroying docker container') const container = dockerode.getContainer(containerId) - container.remove({ force: shouldForce === true }, (error) => { + container.remove({ force: shouldForce === true, v: true }, (error) => { if (error != null && error.statusCode === 404) { logger.warn( { err: error, containerId }, diff --git a/services/clsi/test/unit/js/DockerRunnerTests.js b/services/clsi/test/unit/js/DockerRunnerTests.js index 553b20f3ad..ac68c75079 100644 --- a/services/clsi/test/unit/js/DockerRunnerTests.js +++ b/services/clsi/test/unit/js/DockerRunnerTests.js @@ -802,7 +802,7 @@ describe('DockerRunner', function () { (err) => { this.fakeContainer.remove.callCount.should.equal(1) this.fakeContainer.remove - .calledWith({ force: true }) + .calledWithMatch({ force: true }) .should.equal(true) return done() } @@ -816,7 +816,7 @@ describe('DockerRunner', function () { (err) => { this.fakeContainer.remove.callCount.should.equal(1) this.fakeContainer.remove - .calledWith({ force: false }) + .calledWithMatch({ force: false }) .should.equal(true) return done() } From 14e1e02a681764839b1e697caf813cdf2cabbfdd Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Fri, 4 Sep 2020 11:34:08 -0400 Subject: [PATCH 629/754] Test anonymous volumes are removed with containers --- services/clsi/test/unit/js/DockerRunnerTests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/test/unit/js/DockerRunnerTests.js b/services/clsi/test/unit/js/DockerRunnerTests.js index ac68c75079..517179a962 100644 --- a/services/clsi/test/unit/js/DockerRunnerTests.js +++ b/services/clsi/test/unit/js/DockerRunnerTests.js @@ -802,7 +802,7 @@ describe('DockerRunner', function () { (err) => { this.fakeContainer.remove.callCount.should.equal(1) this.fakeContainer.remove - .calledWithMatch({ force: true }) + .calledWith({ force: true, v: true }) .should.equal(true) return done() } @@ -816,7 +816,7 @@ describe('DockerRunner', function () { (err) => { this.fakeContainer.remove.callCount.should.equal(1) this.fakeContainer.remove - .calledWithMatch({ force: false }) + .calledWith({ force: false, v: true }) .should.equal(true) return done() } From f85e2dec5ce8381d1cac4fed65d05a1485ececec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 12 Sep 2020 17:04:49 +0000 Subject: [PATCH 630/754] Bump node-fetch from 2.6.0 to 2.6.1 Bumps [node-fetch](https://github.com/bitinn/node-fetch) from 2.6.0 to 2.6.1. - [Release notes](https://github.com/bitinn/node-fetch/releases) - [Changelog](https://github.com/node-fetch/node-fetch/blob/master/docs/CHANGELOG.md) - [Commits](https://github.com/bitinn/node-fetch/compare/v2.6.0...v2.6.1) Signed-off-by: dependabot[bot] <support@github.com> --- services/clsi/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index e185ad1233..170c1ac3a6 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -4662,9 +4662,9 @@ } }, "node-fetch": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" }, "node-forge": { "version": "0.9.1", From 5b11dc5a7742fd5f934833e25906ea35ba99174b Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Wed, 16 Sep 2020 12:24:42 -0400 Subject: [PATCH 631/754] Bump Node version to 10.22.1 --- services/clsi/.github/dependabot.yml | 6 ++++++ services/clsi/.nvmrc | 2 +- services/clsi/Dockerfile | 2 +- services/clsi/buildscript.txt | 4 ++-- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/services/clsi/.github/dependabot.yml b/services/clsi/.github/dependabot.yml index c6f98d843d..e2c64a3351 100644 --- a/services/clsi/.github/dependabot.yml +++ b/services/clsi/.github/dependabot.yml @@ -15,3 +15,9 @@ updates: # 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 + + # currently assign team-magma to all dependabot PRs - this may change in + # future if we reorganise teams + labels: + - "dependencies" + - "Team-Magma" diff --git a/services/clsi/.nvmrc b/services/clsi/.nvmrc index b61c07ffdd..c2f6421352 100644 --- a/services/clsi/.nvmrc +++ b/services/clsi/.nvmrc @@ -1 +1 @@ -10.21.0 +10.22.1 diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index bbf1efd072..5d702dce79 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -FROM node:10.21.0 as base +FROM node:10.22.1 as base WORKDIR /app COPY install_deps.sh /app diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index b2866578d9..0d61644133 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -4,6 +4,6 @@ clsi --docker-repos=gcr.io/overleaf-ops --env-add= --env-pass-through=TEXLIVE_IMAGE ---node-version=10.21.0 +--node-version=10.22.1 --public-repo=True ---script-version=3.3.2 +--script-version=3.3.3 From aaa0681c39df87c5c97bc2468a125dced0efa663 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly <shane.kilkelly@overleaf.com> Date: Tue, 27 Oct 2020 15:53:10 +0000 Subject: [PATCH 632/754] In example request, show example using curl This clarifies the exact way to construct this request. It seems that some users have taken the existing documentation to mean that the request should be sent from the browser, to the web server. This example clarifies how to send this compile request directly to the clsi server. --- services/clsi/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/services/clsi/README.md b/services/clsi/README.md index 5ae02a866d..492f2fdb6b 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -89,9 +89,16 @@ The CLSI is based on a JSON API. } ``` +With `curl`, if you place the above json in a file called `data.json`, the request would look like this: + +``` shell +$ curl -X POST -d @data.json localhost:3013/project/<id>/compile +``` + You can specify any project-id in the URL, and the files and LaTeX environment will be persisted between requests. URLs will be downloaded and cached until provided with a more recent modified date. + #### Example Response ```javascript From fcc52cb1a0b27c6127956a5adf540bd1d4bef9b3 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly <shane.kilkelly@overleaf.com> Date: Tue, 27 Oct 2020 16:17:22 +0000 Subject: [PATCH 633/754] No more blank line --- services/clsi/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/services/clsi/README.md b/services/clsi/README.md index 492f2fdb6b..02cd0a470f 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -98,7 +98,6 @@ $ curl -X POST -d @data.json localhost:3013/project/<id>/compile You can specify any project-id in the URL, and the files and LaTeX environment will be persisted between requests. URLs will be downloaded and cached until provided with a more recent modified date. - #### Example Response ```javascript From c7fa34a6a8139311ba144965e8c6d1b8104ccf8d Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Mon, 23 Nov 2020 10:56:33 -0500 Subject: [PATCH 634/754] Upgrade build-scripts to 3.4.0 This version fixes docker-compose health checks for dependent services. See https://github.com/overleaf/dev-environment/pull/409 for details. --- services/clsi/buildscript.txt | 2 +- services/clsi/docker-compose.ci.yml | 1 + services/clsi/docker-compose.yml | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index 0d61644133..6794c8cb56 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -6,4 +6,4 @@ clsi --env-pass-through=TEXLIVE_IMAGE --node-version=10.22.1 --public-repo=True ---script-version=3.3.3 +--script-version=3.4.0 diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 35f4878004..2237933754 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -22,6 +22,7 @@ services: environment: ELASTIC_SEARCH_DSN: es:9200 REDIS_HOST: redis + QUEUES_REDIS_HOST: redis MONGO_HOST: mongo POSTGRES_HOST: postgres MOCHA_GREP: ${MOCHA_GREP} diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 052d909832..6688000204 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -31,6 +31,7 @@ services: environment: ELASTIC_SEARCH_DSN: es:9200 REDIS_HOST: redis + QUEUES_REDIS_HOST: redis MONGO_HOST: mongo POSTGRES_HOST: postgres MOCHA_GREP: ${MOCHA_GREP} From 7b7cd8cc8c39bc3d54e5b66128831efac6644286 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Wed, 25 Nov 2020 11:57:25 +0000 Subject: [PATCH 635/754] [misc] bump metrics module to 3.4.1 - renamed package from `metrics-sharelatex` to `@overleaf/metrics` - drop support for statsd backend - decaffeinate - compress `/metrics` response using gzip - bump debugging agents to latest versions - expose prometheus interfaces for custom metrics (custom tags) - cleanup of open sockets metrics - fix deprecation warnings for header access --- services/clsi/app.js | 2 +- services/clsi/app/js/Metrics.js | 2 +- services/clsi/package-lock.json | 1262 ++++++++++++++----------------- services/clsi/package.json | 3 +- 4 files changed, 551 insertions(+), 718 deletions(-) diff --git a/services/clsi/app.js b/services/clsi/app.js index 7ebca04e3b..9402869c04 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -6,7 +6,7 @@ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ const tenMinutes = 10 * 60 * 1000 -const Metrics = require('metrics-sharelatex') +const Metrics = require('@overleaf/metrics') Metrics.initialize('clsi') const CompileController = require('./app/js/CompileController') diff --git a/services/clsi/app/js/Metrics.js b/services/clsi/app/js/Metrics.js index e9676415ea..f0e57794fd 100644 --- a/services/clsi/app/js/Metrics.js +++ b/services/clsi/app/js/Metrics.js @@ -1,3 +1,3 @@ // TODO: This file was created by bulk-decaffeinate. // Sanity-check the conversion and remove this comment. -module.exports = require('metrics-sharelatex') +module.exports = require('@overleaf/metrics') diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index 31171ac40d..23e4179f03 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -215,173 +215,6 @@ } } }, - "@google-cloud/debug-agent": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@google-cloud/debug-agent/-/debug-agent-3.2.0.tgz", - "integrity": "sha512-fP87kYbS6aeDna08BivwQ1J260mwJGchRi99XdWCgqbRwuFac8ul0OT5i2wEeDSc5QaDX8ZuWQQ0igZvh1rTyQ==", - "requires": { - "@google-cloud/common": "^0.32.0", - "@sindresorhus/is": "^0.15.0", - "acorn": "^6.0.0", - "coffeescript": "^2.0.0", - "console-log-level": "^1.4.0", - "extend": "^3.0.1", - "findit2": "^2.2.3", - "gcp-metadata": "^1.0.0", - "lodash.pickby": "^4.6.0", - "p-limit": "^2.2.0", - "pify": "^4.0.1", - "semver": "^6.0.0", - "source-map": "^0.6.1", - "split": "^1.0.0" - }, - "dependencies": { - "@google-cloud/common": { - "version": "0.32.1", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.32.1.tgz", - "integrity": "sha512-bLdPzFvvBMtVkwsoBtygE9oUm3yrNmPa71gvOgucYI/GqvNP2tb6RYsDHPq98kvignhcgHGDI5wyNgxaCo8bKQ==", - "requires": { - "@google-cloud/projectify": "^0.3.3", - "@google-cloud/promisify": "^0.4.0", - "@types/request": "^2.48.1", - "arrify": "^2.0.0", - "duplexify": "^3.6.0", - "ent": "^2.2.0", - "extend": "^3.0.2", - "google-auth-library": "^3.1.1", - "pify": "^4.0.1", - "retry-request": "^4.0.0", - "teeny-request": "^3.11.3" - } - }, - "@google-cloud/projectify": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-0.3.3.tgz", - "integrity": "sha512-7522YHQ4IhaafgSunsFF15nG0TGVmxgXidy9cITMe+256RgqfcrfWphiMufW+Ou4kqagW/u3yxwbzVEW3dk2Uw==" - }, - "@google-cloud/promisify": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-0.4.0.tgz", - "integrity": "sha512-4yAHDC52TEMCNcMzVC8WlqnKKKq+Ssi2lXoUg9zWWkZ6U6tq9ZBRYLHHCRdfU+EU9YJsVmivwGcKYCjRGjnf4Q==" - }, - "coffeescript": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.5.1.tgz", - "integrity": "sha512-J2jRPX0eeFh5VKyVnoLrfVFgLZtnnmp96WQSLAS8OrLm2wtQLcnikYKe1gViJKDH7vucjuhHvBKKBP3rKcD1tQ==" - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "^2.1.1" - } - }, - "gaxios": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-1.8.4.tgz", - "integrity": "sha512-BoENMnu1Gav18HcpV9IleMPZ9exM+AvUjrAOV4Mzs/vfz2Lu/ABv451iEXByKiMPn2M140uul1txXCg83sAENw==", - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^2.2.1", - "node-fetch": "^2.3.0" - } - }, - "gcp-metadata": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-1.0.0.tgz", - "integrity": "sha512-Q6HrgfrCQeEircnNP3rCcEgiDv7eF9+1B+1MMgpE190+/+0mjQR8PxeOaRgxZWmdDAF9EIryHB9g1moPiw1SbQ==", - "requires": { - "gaxios": "^1.0.2", - "json-bigint": "^0.3.0" - } - }, - "google-auth-library": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-3.1.2.tgz", - "integrity": "sha512-cDQMzTotwyWMrg5jRO7q0A4TL/3GWBgO7I7q5xGKNiiFf9SmGY/OJ1YsLMgI2MVHHsEGyrqYnbnmV1AE+Z6DnQ==", - "requires": { - "base64-js": "^1.3.0", - "fast-text-encoding": "^1.0.0", - "gaxios": "^1.2.1", - "gcp-metadata": "^1.0.0", - "gtoken": "^2.3.2", - "https-proxy-agent": "^2.2.1", - "jws": "^3.1.5", - "lru-cache": "^5.0.0", - "semver": "^5.5.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "google-p12-pem": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-1.0.4.tgz", - "integrity": "sha512-SwLAUJqUfTB2iS+wFfSS/G9p7bt4eWcc2LyfvmUXe7cWp6p3mpxDo6LLI29MXdU6wvPcQ/up298X7GMC5ylAlA==", - "requires": { - "node-forge": "^0.8.0", - "pify": "^4.0.0" - } - }, - "gtoken": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-2.3.3.tgz", - "integrity": "sha512-EaB49bu/TCoNeQjhCYKI/CurooBKkGxIqFHsWABW0b25fobBYVTMe84A8EBVVZhl8emiUdNypil9huMOTmyAnw==", - "requires": { - "gaxios": "^1.0.4", - "google-p12-pem": "^1.0.0", - "jws": "^3.1.5", - "mime": "^2.2.0", - "pify": "^4.0.0" - } - }, - "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - } - }, - "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node-forge": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.5.tgz", - "integrity": "sha512-vFMQIWt+J/7FLNyKouZ9TazT74PRV3wgv9UT4cRjC8BffxFbKXkgIWR42URCPSnHm/QDz6BOlb2Q0U4+VQT67Q==" - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, - "teeny-request": { - "version": "3.11.3", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-3.11.3.tgz", - "integrity": "sha512-CKncqSF7sH6p4rzCgkb/z/Pcos5efl0DmolzvlqRQUNcpRIruOhY9+T1FsIlyEbfWd7MsFpodROOwHYh2BaXzw==", - "requires": { - "https-proxy-agent": "^2.2.1", - "node-fetch": "^2.2.0", - "uuid": "^3.3.2" - } - } - } - }, "@google-cloud/logging": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/@google-cloud/logging/-/logging-7.3.0.tgz", @@ -468,173 +301,6 @@ "extend": "^3.0.2" } }, - "@google-cloud/profiler": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@google-cloud/profiler/-/profiler-0.2.3.tgz", - "integrity": "sha512-rNvtrFtIebIxZEJ/O0t8n7HciZGIXBo8DvHxWqAmsCaeLvkTtsaL6HmPkwxrNQ1IhbYWAxF+E/DwCiHyhKmgTg==", - "requires": { - "@google-cloud/common": "^0.26.0", - "@types/console-log-level": "^1.4.0", - "@types/semver": "^5.5.0", - "bindings": "^1.2.1", - "console-log-level": "^1.4.0", - "delay": "^4.0.1", - "extend": "^3.0.1", - "gcp-metadata": "^0.9.0", - "nan": "^2.11.1", - "parse-duration": "^0.1.1", - "pify": "^4.0.0", - "pretty-ms": "^4.0.0", - "protobufjs": "~6.8.6", - "semver": "^5.5.0", - "teeny-request": "^3.3.0" - }, - "dependencies": { - "@google-cloud/common": { - "version": "0.26.2", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.26.2.tgz", - "integrity": "sha512-xJ2M/q3MrUbnYZuFlpF01caAlEhAUoRn0NXp93Hn3pkFpfSOG8YfbKbpBAHvcKVbBOAKVIwPsleNtuyuabUwLQ==", - "requires": { - "@google-cloud/projectify": "^0.3.2", - "@google-cloud/promisify": "^0.3.0", - "@types/duplexify": "^3.5.0", - "@types/request": "^2.47.0", - "arrify": "^1.0.1", - "duplexify": "^3.6.0", - "ent": "^2.2.0", - "extend": "^3.0.1", - "google-auth-library": "^2.0.0", - "pify": "^4.0.0", - "retry-request": "^4.0.0", - "through2": "^3.0.0" - } - }, - "@google-cloud/projectify": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-0.3.3.tgz", - "integrity": "sha512-7522YHQ4IhaafgSunsFF15nG0TGVmxgXidy9cITMe+256RgqfcrfWphiMufW+Ou4kqagW/u3yxwbzVEW3dk2Uw==" - }, - "@google-cloud/promisify": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-0.3.1.tgz", - "integrity": "sha512-QzB0/IMvB0eFxFK7Eqh+bfC8NLv3E9ScjWQrPOk6GgfNroxcVITdTlT8NRsRrcp5+QQJVPLkRqKG0PUdaWXmHw==" - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "^2.1.1" - } - }, - "gaxios": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-1.8.4.tgz", - "integrity": "sha512-BoENMnu1Gav18HcpV9IleMPZ9exM+AvUjrAOV4Mzs/vfz2Lu/ABv451iEXByKiMPn2M140uul1txXCg83sAENw==", - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^2.2.1", - "node-fetch": "^2.3.0" - } - }, - "gcp-metadata": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-0.9.3.tgz", - "integrity": "sha512-caV4S84xAjENtpezLCT/GILEAF5h/bC4cNqZFmt/tjTn8t+JBtTkQrgBrJu3857YdsnlM8rxX/PMcKGtE8hUlw==", - "requires": { - "gaxios": "^1.0.2", - "json-bigint": "^0.3.0" - } - }, - "google-auth-library": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-2.0.2.tgz", - "integrity": "sha512-FURxmo1hBVmcfLauuMRKOPYAPKht3dGuI2wjeJFalDUThO0HoYVjr4yxt5cgYSFm1dgUpmN9G/poa7ceTFAIiA==", - "requires": { - "axios": "^0.18.0", - "gcp-metadata": "^0.7.0", - "gtoken": "^2.3.0", - "https-proxy-agent": "^2.2.1", - "jws": "^3.1.5", - "lru-cache": "^5.0.0", - "semver": "^5.5.0" - }, - "dependencies": { - "gcp-metadata": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-0.7.0.tgz", - "integrity": "sha512-ffjC09amcDWjh3VZdkDngIo7WoluyC5Ag9PAYxZbmQLOLNI8lvPtoKTSCyU54j2gwy5roZh6sSMTfkY2ct7K3g==", - "requires": { - "axios": "^0.18.0", - "extend": "^3.0.1", - "retry-axios": "0.3.2" - } - } - } - }, - "google-p12-pem": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-1.0.4.tgz", - "integrity": "sha512-SwLAUJqUfTB2iS+wFfSS/G9p7bt4eWcc2LyfvmUXe7cWp6p3mpxDo6LLI29MXdU6wvPcQ/up298X7GMC5ylAlA==", - "requires": { - "node-forge": "^0.8.0", - "pify": "^4.0.0" - } - }, - "gtoken": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-2.3.3.tgz", - "integrity": "sha512-EaB49bu/TCoNeQjhCYKI/CurooBKkGxIqFHsWABW0b25fobBYVTMe84A8EBVVZhl8emiUdNypil9huMOTmyAnw==", - "requires": { - "gaxios": "^1.0.4", - "google-p12-pem": "^1.0.0", - "jws": "^3.1.5", - "mime": "^2.2.0", - "pify": "^4.0.0" - } - }, - "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - } - }, - "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node-forge": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.5.tgz", - "integrity": "sha512-vFMQIWt+J/7FLNyKouZ9TazT74PRV3wgv9UT4cRjC8BffxFbKXkgIWR42URCPSnHm/QDz6BOlb2Q0U4+VQT67Q==" - }, - "teeny-request": { - "version": "3.11.3", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-3.11.3.tgz", - "integrity": "sha512-CKncqSF7sH6p4rzCgkb/z/Pcos5efl0DmolzvlqRQUNcpRIruOhY9+T1FsIlyEbfWd7MsFpodROOwHYh2BaXzw==", - "requires": { - "https-proxy-agent": "^2.2.1", - "node-fetch": "^2.2.0", - "uuid": "^3.3.2" - } - } - } - }, "@google-cloud/projectify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-1.0.4.tgz", @@ -645,167 +311,6 @@ "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-1.0.4.tgz", "integrity": "sha512-VccZDcOql77obTnFh0TbNED/6ZbbmHDf8UMNnzO1d5g9V0Htfm4k5cllY8P1tJsRKC3zWYGRLaViiupcgVjBoQ==" }, - "@google-cloud/trace-agent": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/@google-cloud/trace-agent/-/trace-agent-3.6.1.tgz", - "integrity": "sha512-KDo85aPN4gSxJ7oEIOlKd7aGENZFXAM1kbIn1Ds+61gh/K1CQWSyepgJo3nUpAwH6D1ezDWV7Iaf8ueoITc8Uw==", - "requires": { - "@google-cloud/common": "^0.32.1", - "builtin-modules": "^3.0.0", - "console-log-level": "^1.4.0", - "continuation-local-storage": "^3.2.1", - "extend": "^3.0.0", - "gcp-metadata": "^1.0.0", - "hex2dec": "^1.0.1", - "is": "^3.2.0", - "methods": "^1.1.1", - "require-in-the-middle": "^4.0.0", - "semver": "^6.0.0", - "shimmer": "^1.2.0", - "uuid": "^3.0.1" - }, - "dependencies": { - "@google-cloud/common": { - "version": "0.32.1", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.32.1.tgz", - "integrity": "sha512-bLdPzFvvBMtVkwsoBtygE9oUm3yrNmPa71gvOgucYI/GqvNP2tb6RYsDHPq98kvignhcgHGDI5wyNgxaCo8bKQ==", - "requires": { - "@google-cloud/projectify": "^0.3.3", - "@google-cloud/promisify": "^0.4.0", - "@types/request": "^2.48.1", - "arrify": "^2.0.0", - "duplexify": "^3.6.0", - "ent": "^2.2.0", - "extend": "^3.0.2", - "google-auth-library": "^3.1.1", - "pify": "^4.0.1", - "retry-request": "^4.0.0", - "teeny-request": "^3.11.3" - } - }, - "@google-cloud/projectify": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-0.3.3.tgz", - "integrity": "sha512-7522YHQ4IhaafgSunsFF15nG0TGVmxgXidy9cITMe+256RgqfcrfWphiMufW+Ou4kqagW/u3yxwbzVEW3dk2Uw==" - }, - "@google-cloud/promisify": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-0.4.0.tgz", - "integrity": "sha512-4yAHDC52TEMCNcMzVC8WlqnKKKq+Ssi2lXoUg9zWWkZ6U6tq9ZBRYLHHCRdfU+EU9YJsVmivwGcKYCjRGjnf4Q==" - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "^2.1.1" - } - }, - "gaxios": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-1.8.4.tgz", - "integrity": "sha512-BoENMnu1Gav18HcpV9IleMPZ9exM+AvUjrAOV4Mzs/vfz2Lu/ABv451iEXByKiMPn2M140uul1txXCg83sAENw==", - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^2.2.1", - "node-fetch": "^2.3.0" - } - }, - "gcp-metadata": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-1.0.0.tgz", - "integrity": "sha512-Q6HrgfrCQeEircnNP3rCcEgiDv7eF9+1B+1MMgpE190+/+0mjQR8PxeOaRgxZWmdDAF9EIryHB9g1moPiw1SbQ==", - "requires": { - "gaxios": "^1.0.2", - "json-bigint": "^0.3.0" - } - }, - "google-auth-library": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-3.1.2.tgz", - "integrity": "sha512-cDQMzTotwyWMrg5jRO7q0A4TL/3GWBgO7I7q5xGKNiiFf9SmGY/OJ1YsLMgI2MVHHsEGyrqYnbnmV1AE+Z6DnQ==", - "requires": { - "base64-js": "^1.3.0", - "fast-text-encoding": "^1.0.0", - "gaxios": "^1.2.1", - "gcp-metadata": "^1.0.0", - "gtoken": "^2.3.2", - "https-proxy-agent": "^2.2.1", - "jws": "^3.1.5", - "lru-cache": "^5.0.0", - "semver": "^5.5.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "google-p12-pem": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-1.0.4.tgz", - "integrity": "sha512-SwLAUJqUfTB2iS+wFfSS/G9p7bt4eWcc2LyfvmUXe7cWp6p3mpxDo6LLI29MXdU6wvPcQ/up298X7GMC5ylAlA==", - "requires": { - "node-forge": "^0.8.0", - "pify": "^4.0.0" - } - }, - "gtoken": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-2.3.3.tgz", - "integrity": "sha512-EaB49bu/TCoNeQjhCYKI/CurooBKkGxIqFHsWABW0b25fobBYVTMe84A8EBVVZhl8emiUdNypil9huMOTmyAnw==", - "requires": { - "gaxios": "^1.0.4", - "google-p12-pem": "^1.0.0", - "jws": "^3.1.5", - "mime": "^2.2.0", - "pify": "^4.0.0" - } - }, - "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - } - }, - "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node-forge": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.5.tgz", - "integrity": "sha512-vFMQIWt+J/7FLNyKouZ9TazT74PRV3wgv9UT4cRjC8BffxFbKXkgIWR42URCPSnHm/QDz6BOlb2Q0U4+VQT67Q==" - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, - "teeny-request": { - "version": "3.11.3", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-3.11.3.tgz", - "integrity": "sha512-CKncqSF7sH6p4rzCgkb/z/Pcos5efl0DmolzvlqRQUNcpRIruOhY9+T1FsIlyEbfWd7MsFpodROOwHYh2BaXzw==", - "requires": { - "https-proxy-agent": "^2.2.1", - "node-fetch": "^2.2.0", - "uuid": "^3.3.2" - } - } - } - }, "@grpc/grpc-js": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.0.5.tgz", @@ -859,6 +364,374 @@ "uuid": "^3.2.1" } }, + "@overleaf/metrics": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@overleaf/metrics/-/metrics-3.4.1.tgz", + "integrity": "sha512-OgjlzuC+2gPdIEDHhmd9LDMu01tk1ln0cJhw1727BZ+Wgf2Z1hjuHRt4JeCkf+PFTHwJutVYT8v6IGPpNEPtbg==", + "requires": { + "@google-cloud/debug-agent": "^5.1.2", + "@google-cloud/profiler": "^4.0.3", + "@google-cloud/trace-agent": "^5.1.1", + "compression": "^1.7.4", + "prom-client": "^11.1.3", + "underscore": "~1.6.0", + "yn": "^3.1.1" + }, + "dependencies": { + "@google-cloud/common": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.5.0.tgz", + "integrity": "sha512-10d7ZAvKhq47L271AqvHEd8KzJqGU45TY+rwM2Z3JHuB070FeTi7oJJd7elfrnKaEvaktw3hH2wKnRWxk/3oWQ==", + "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.1.1", + "retry-request": "^4.1.1", + "teeny-request": "^7.0.0" + } + }, + "@google-cloud/debug-agent": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@google-cloud/debug-agent/-/debug-agent-5.1.3.tgz", + "integrity": "sha512-WbzeEz4MvPlM7DX2QBsPcWgF62u7LSQv/oMYPl0L+TddTebqjDKiVXwxpzWk61NIfcKiet3dyCbPIt3N5o8XPQ==", + "requires": { + "@google-cloud/common": "^3.0.0", + "acorn": "^8.0.0", + "coffeescript": "^2.0.0", + "console-log-level": "^1.4.0", + "extend": "^3.0.2", + "findit2": "^2.2.3", + "gcp-metadata": "^4.0.0", + "p-limit": "^3.0.1", + "semver": "^7.0.0", + "source-map": "^0.6.1", + "split": "^1.0.0" + } + }, + "@google-cloud/profiler": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@google-cloud/profiler/-/profiler-4.1.0.tgz", + "integrity": "sha512-9e1zXRctLSUHAoAsFGwE4rS28fr0siiG+jXl5OpwTK8ZAUlxb70aosHaZGdsv8YXrYKjuiufjRZ/OXCs0XLI9g==", + "requires": { + "@google-cloud/common": "^3.0.0", + "@types/console-log-level": "^1.4.0", + "@types/semver": "^7.0.0", + "console-log-level": "^1.4.0", + "delay": "^4.0.1", + "extend": "^3.0.2", + "gcp-metadata": "^4.0.0", + "parse-duration": "^0.4.4", + "pprof": "3.0.0", + "pretty-ms": "^7.0.0", + "protobufjs": "~6.10.0", + "semver": "^7.0.0", + "teeny-request": "^7.0.0" + } + }, + "@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.3", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.3.tgz", + "integrity": "sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw==" + }, + "@google-cloud/trace-agent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@google-cloud/trace-agent/-/trace-agent-5.1.1.tgz", + "integrity": "sha512-YTcK0RLN90pLCprg0XC8uV4oAVd79vsXhkcxmEVwiOOYjUDvSrAhb7y/0SY606zgfhJHmUTNb/fZSWEtZP/slQ==", + "requires": { + "@google-cloud/common": "^3.0.0", + "@opencensus/propagation-stackdriver": "0.0.22", + "builtin-modules": "^3.0.0", + "console-log-level": "^1.4.0", + "continuation-local-storage": "^3.2.1", + "extend": "^3.0.2", + "gcp-metadata": "^4.0.0", + "google-auth-library": "^6.0.0", + "hex2dec": "^1.0.1", + "is": "^3.2.0", + "methods": "^1.1.1", + "require-in-the-middle": "^5.0.0", + "semver": "^7.0.0", + "shimmer": "^1.2.0", + "source-map-support": "^0.5.16", + "uuid": "^8.0.0" + } + }, + "@opencensus/core": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/@opencensus/core/-/core-0.0.22.tgz", + "integrity": "sha512-ErazJtivjceNoOZI1bG9giQ6cWS45J4i6iPUtlp7dLNu58OLs/v+CD0FsaPCh47XgPxAI12vbBE8Ec09ViwHNA==", + "requires": { + "continuation-local-storage": "^3.2.1", + "log-driver": "^1.2.7", + "semver": "^7.0.0", + "shimmer": "^1.2.0", + "uuid": "^8.0.0" + } + }, + "@opencensus/propagation-stackdriver": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/@opencensus/propagation-stackdriver/-/propagation-stackdriver-0.0.22.tgz", + "integrity": "sha512-eBvf/ihb1mN8Yz/ASkz8nHzuMKqygu77+VNnUeR0yEh3Nj+ykB8VVR6lK+NAFXo1Rd1cOsTmgvuXAZgDAGleQQ==", + "requires": { + "@opencensus/core": "^0.0.22", + "hex2dec": "^1.0.1", + "uuid": "^8.0.0" + } + }, + "@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": "13.13.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.33.tgz", + "integrity": "sha512-1B3GM1yuYsFyEvBb+ljBqWBOylsWDYioZ5wpu8AhXdIhq20neXS7eaSC8GkwHE0yQYGiOIV43lMsgRYTgKZefQ==" + }, + "@types/semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ==" + }, + "acorn": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.0.4.tgz", + "integrity": "sha512-XNP0PqF1XD19ZlLKvB7cMmnZswW4C/03pRHgirB30uSJTaS3A3V1/P4sS3HPvFmjoriPCJQs+JDSbm4bL1TxGQ==" + }, + "bignumber.js": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", + "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==" + }, + "coffeescript": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.5.1.tgz", + "integrity": "sha512-J2jRPX0eeFh5VKyVnoLrfVFgLZtnnmp96WQSLAS8OrLm2wtQLcnikYKe1gViJKDH7vucjuhHvBKKBP3rKcD1tQ==" + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, + "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" + } + }, + "gaxios": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.0.1.tgz", + "integrity": "sha512-jOin8xRZ/UytQeBpSXFqIzqU7Fi5TqgPNLlUsSB8kjJ76+FiGBfImF8KJu++c6J4jOldfJUtt0YmkRj2ZpSHTQ==", + "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.2.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.1.tgz", + "integrity": "sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw==", + "requires": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + } + }, + "google-auth-library": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.3.tgz", + "integrity": "sha512-m9mwvY3GWbr7ZYEbl61isWmk+fvTmOt0YNUfPOUY2VH8K5pZlAIWJjxEi0PqR3OjMretyiQLI6GURMrPSwHQ2g==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + }, + "google-p12-pem": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", + "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", + "requires": { + "node-forge": "^0.10.0" + } + }, + "gtoken": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.1.0.tgz", + "integrity": "sha512-4d8N6Lk8TEAHl9vVoRVMh9BNOKWVgl2DdNtr3428O75r3QFrF/a5MMu851VmK0AA8+iSvbwRv69k5XnMLURGhg==", + "requires": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.0.3", + "jws": "^4.0.0", + "mime": "^2.2.0" + } + }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "parse-duration": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-0.4.4.tgz", + "integrity": "sha512-KbAJuYGUhZkB9gotDiKLnZ7Z3VTacK3fgwmDdB6ZVDtJbMBT6MfLga0WJaYpPDu0mzqT0NgHtHDt5PY4l0nidg==" + }, + "pretty-ms": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", + "requires": { + "parse-ms": "^2.1.0" + } + }, + "protobufjs": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz", + "integrity": "sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==", + "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.1", + "@types/node": "^13.7.0", + "long": "^4.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" + } + }, + "require-in-the-middle": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.0.3.tgz", + "integrity": "sha512-p/ICV8uMlqC4tjOYabLMxAWCIKa0YUQgZZ6KDM0xgXJNgdGQ1WmL2A07TwmrZw+wi6ITUFKzH5v3n+ENEyXVkA==", + "requires": { + "debug": "^4.1.1", + "module-details-from-path": "^1.0.3", + "resolve": "^1.12.0" + } + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + }, + "teeny-request": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.0.1.tgz", + "integrity": "sha512-sasJmQ37klOlplL4Ia/786M5YlOcoLGQyq2TE4WHSRupbAuDaQW0PfVxV4MtdBtRJ4ngzS+1qim8zP6Zp35qCw==", + "requires": { + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^8.0.0" + } + }, + "uuid": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", + "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, "@overleaf/o-error": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@overleaf/o-error/-/o-error-3.0.0.tgz", @@ -918,11 +791,6 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, - "@sindresorhus/is": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.15.0.tgz", - "integrity": "sha512-lu8BpxjAtRCAo5ifytTpCPCj99LF7o/2Myn+NXyNCBqvPYn7Pjd76AMmUB5l7XF1U6t0hcWrlEM5ESufW7wAeA==" - }, "@sinonjs/commons": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.1.tgz", @@ -989,11 +857,6 @@ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" }, - "@types/caseless": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", - "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" - }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", @@ -1005,14 +868,6 @@ "resolved": "https://registry.npmjs.org/@types/console-log-level/-/console-log-level-1.4.0.tgz", "integrity": "sha512-x+OscEQwcx5Biair4enH7ov9W+clcqUWaZRaxn5IkT4yNWWjRr2oiYDkY/x1uXSTVZOQ2xlbFQySaQGB+VdXGQ==" }, - "@types/duplexify": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@types/duplexify/-/duplexify-3.6.0.tgz", - "integrity": "sha512-5zOA53RUlzN74bvrSGwjudssD9F3a797sDZQkiYpUOxW+WHaXTCPz4/d5Dgi6FKnOqZ2CpaTo0DhgIfsXAOE/A==", - "requires": { - "@types/node": "*" - } - }, "@types/eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", @@ -1043,39 +898,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.15.tgz", "integrity": "sha1-IOhWUbYv2GZW5Xycm8dxqxVwvFk=" }, - "@types/request": { - "version": "2.48.4", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.4.tgz", - "integrity": "sha512-W1t1MTKYR8PxICH+A4HgEIPuAC3sbljoEVfyZbeFJJDbr30guDspJri2XOaM2E+Un7ZjrihaDi7cf6fPa2tbgw==", - "requires": { - "@types/caseless": "*", - "@types/node": "*", - "@types/tough-cookie": "*", - "form-data": "^2.5.0" - }, - "dependencies": { - "form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - } - } - }, - "@types/semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz", - "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==" - }, "@typescript-eslint/experimental-utils": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz", @@ -1154,7 +976,8 @@ "acorn": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==" + "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", + "dev": true }, "acorn-jsx": { "version": "5.1.0", @@ -1162,14 +985,6 @@ "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", "dev": true }, - "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "requires": { - "es6-promisify": "^5.0.0" - } - }, "ajv": { "version": "6.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", @@ -1365,15 +1180,6 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" }, - "axios": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz", - "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==", - "requires": { - "follow-redirects": "1.5.10", - "is-buffer": "^2.0.2" - } - }, "axobject-query": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.1.2.tgz", @@ -1788,6 +1594,35 @@ "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==", "dev": true }, + "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" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + } + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2200,14 +2035,6 @@ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "requires": { - "es6-promise": "^4.0.3" - } - }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -2937,24 +2764,6 @@ "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", "dev": true }, - "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "requires": { - "debug": "=3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - } - } - }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -3776,7 +3585,8 @@ "is-buffer": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true }, "is-callable": { "version": "1.1.5", @@ -3976,25 +3786,6 @@ "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==", "dev": true }, - "jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "requires": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -4087,11 +3878,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "lodash.pickby": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz", - "integrity": "sha1-feoh2MGNdwOifHBMFdO4SmfjOv8=" - }, "lodash.unescape": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", @@ -4208,15 +3994,6 @@ "yallist": "^3.0.2" } }, - "lynx": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.2.0.tgz", - "integrity": "sha1-eeZnRTDaQYPoeVO9aGFx4HDaULk=", - "requires": { - "mersenne": "~0.0.3", - "statsd-parser": "~0.0.4" - } - }, "make-plural": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-4.3.0.tgz", @@ -4267,11 +4044,6 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, - "mersenne": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz", - "integrity": "sha1-QB/ex+whzbngPNPTAhOY2iGycIU=" - }, "messageformat": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/messageformat/-/messageformat-2.3.0.tgz", @@ -4300,37 +4072,6 @@ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, - "metrics-sharelatex": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/metrics-sharelatex/-/metrics-sharelatex-2.6.0.tgz", - "integrity": "sha512-kPWCtgBrRZwLXCxqJVVn3c7g+GHQEBGYBpwCIt0Vqp0NaKvgKiPkJMkoPg9vkCsjsN2AgpGxXcOAdnHAjxfrzA==", - "requires": { - "@google-cloud/debug-agent": "^3.0.0", - "@google-cloud/profiler": "^0.2.3", - "@google-cloud/trace-agent": "^3.2.0", - "coffee-script": "1.6.0", - "lynx": "~0.1.1", - "prom-client": "^11.1.3", - "underscore": "~1.6.0", - "yn": "^3.1.1" - }, - "dependencies": { - "lynx": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.1.1.tgz", - "integrity": "sha1-Mxjc7xaQi4KG6Bisz9sxzXQkj50=", - "requires": { - "mersenne": "~0.0.3", - "statsd-parser": "~0.0.4" - } - }, - "underscore": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", - "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" - } - } - }, "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -4854,6 +4595,11 @@ "ee-first": "1.1.1" } }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4908,6 +4654,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "dev": true, "requires": { "p-try": "^2.0.0" } @@ -4941,7 +4688,8 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, "parent-module": { "version": "1.0.1", @@ -4952,11 +4700,6 @@ "callsites": "^3.0.0" } }, - "parse-duration": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-0.1.2.tgz", - "integrity": "sha512-0qfMZyjOUFBeEIvJ5EayfXJqaEXxQ+Oj2b7tWJM3hvEXvXsYCk05EDVI23oYnEw2NaFYUWdABEVPBvBMh8L/pA==" - }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", @@ -5043,11 +4786,6 @@ "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==", "dev": true }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - }, "pkg-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", @@ -5057,6 +4795,126 @@ "find-up": "^2.1.0" } }, + "pprof": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pprof/-/pprof-3.0.0.tgz", + "integrity": "sha512-uPWbAhoH/zvq1kM3/Fd/wshb4D7sLlGap8t6uCTER4aZRWqqyPYgXzpjWbT0Unn5U25pEy2VREUu27nQ9o9VPA==", + "requires": { + "bindings": "^1.2.1", + "delay": "^4.0.1", + "findit2": "^2.2.3", + "nan": "^2.14.0", + "node-pre-gyp": "^0.16.0", + "p-limit": "^3.0.0", + "pify": "^5.0.0", + "protobufjs": "~6.10.0", + "source-map": "^0.7.3", + "split": "^1.0.1" + }, + "dependencies": { + "@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": "13.13.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.33.tgz", + "integrity": "sha512-1B3GM1yuYsFyEvBb+ljBqWBOylsWDYioZ5wpu8AhXdIhq20neXS7eaSC8GkwHE0yQYGiOIV43lMsgRYTgKZefQ==" + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "needle": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.2.tgz", + "integrity": "sha512-LbRIwS9BfkPvNwNHlsA41Q29kL2L/6VaOJ0qisM5lLWsTV3nP15abO5ITL6L81zqFhzjRKDAYjpcBcwM0AVvLQ==", + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.16.0.tgz", + "integrity": "sha512-4efGA+X/YXAHLi1hN8KaPrILULaUn2nWecFrn1k2I+99HpoyvcOGEbtcOxpDiUwPF2ZANMJDh32qwOUPenuR1g==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.3", + "needle": "^2.5.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4.4.2" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "pify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", + "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==" + }, + "protobufjs": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz", + "integrity": "sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==", + "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.1", + "@types/node": "^13.7.0", + "long": "^4.0.0" + } + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + } + } + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -5694,14 +5552,6 @@ } } }, - "pretty-ms": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-4.0.0.tgz", - "integrity": "sha512-qG66ahoLCwpLXD09ZPHSCbUWYTqdosB7SMP4OffgTgL2PBKXMuUsrk5Bwg8q4qPkjTXsKBMr+YK3Ltd/6F9s/Q==", - "requires": { - "parse-ms": "^2.0.0" - } - }, "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", @@ -6019,31 +5869,6 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, - "require-in-the-middle": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-4.0.1.tgz", - "integrity": "sha512-EfkM2zANyGkrfIExsECMeNn/uzjvHrE9h36yLXSavmrDiH4tgDNvltAmEKnt4PNLbqKPHZz+uszW2wTKrLUX0w==", - "requires": { - "debug": "^4.1.1", - "module-details-from-path": "^1.0.3", - "resolve": "^1.12.0" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, "require-like": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", @@ -6094,11 +5919,6 @@ "any-promise": "^1.3.0" } }, - "retry-axios": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/retry-axios/-/retry-axios-0.3.2.tgz", - "integrity": "sha512-jp4YlI0qyDFfXiXGhkCOliBN1G7fRH03Nqy8YdShzGqbY5/9S2x/IR6C88ls2DFkbWuL3ASkP7QD3pVrNpPgwQ==" - }, "retry-request": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.1.tgz", @@ -6398,6 +6218,15 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -6504,11 +6333,6 @@ "integrity": "sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU=", "dev": true }, - "statsd-parser": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/statsd-parser/-/statsd-parser-0.0.4.tgz", - "integrity": "sha1-y9JDlTzELv/VSLXSI4jtaJ7GOb0=" - }, "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -6934,6 +6758,11 @@ "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", "dev": true }, + "underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -7372,6 +7201,11 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" } } } diff --git a/services/clsi/package.json b/services/clsi/package.json index 8c1e2f84fe..f91ddf0a95 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -19,6 +19,7 @@ }, "author": "James Allen <james@sharelatex.com>", "dependencies": { + "@overleaf/metrics": "^3.4.1", "async": "3.2.0", "body-parser": "^1.19.0", "diskusage": "^1.1.3", @@ -29,8 +30,6 @@ "lockfile": "^1.0.4", "lodash": "^4.17.20", "logger-sharelatex": "^2.2.0", - "lynx": "0.2.0", - "metrics-sharelatex": "^2.6.0", "mysql": "^2.18.1", "request": "^2.88.2", "sequelize": "^5.21.5", From 1b73c6dad28b4d096b66acd023f8bec27b1b74f3 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Wed, 25 Nov 2020 13:33:54 +0000 Subject: [PATCH 636/754] [misc] work around missing stubs --- services/clsi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/package.json b/services/clsi/package.json index f91ddf0a95..b6f4156b86 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -10,7 +10,7 @@ "start": "node $NODE_APP_OPTIONS app.js", "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", "test:acceptance": "npm run test:acceptance:_run -- --grep=$MOCHA_GREP", - "test:unit:_run": "mocha --recursive --reporter spec $@ test/unit/js", + "test:unit:_run": "mocha --exit --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 --max-warnings 0 .", From 5d5241156ca60c30564e8905ac68f18913c2f7b9 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Wed, 2 Dec 2020 12:25:26 +0000 Subject: [PATCH 637/754] [misc] install settings-sharelatex from npm --- services/clsi/package-lock.json | 5 +++-- services/clsi/package.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index 23e4179f03..e3f93b08a2 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -6106,8 +6106,9 @@ "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, "settings-sharelatex": { - "version": "git+https://github.com/sharelatex/settings-sharelatex.git#93f63d029b52fef8825c3a401b2b6a7ba29b4750", - "from": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.1.0", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/settings-sharelatex/-/settings-sharelatex-1.1.0.tgz", + "integrity": "sha512-f7D+0lnlohoteSn6IKTH72NE+JnAdMWTKwQglAuimZWTID2FRRItZSGeYMTRpvEnaQApkoVwRp//WRMsiddnqw==", "requires": { "coffee-script": "1.6.0" } diff --git a/services/clsi/package.json b/services/clsi/package.json index b6f4156b86..8d74faac5f 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -33,7 +33,7 @@ "mysql": "^2.18.1", "request": "^2.88.2", "sequelize": "^5.21.5", - "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.1.0", + "settings-sharelatex": "^1.1.0", "sqlite3": "^4.1.1", "v8-profiler-node8": "^6.1.1", "wrench": "~1.5.9" From f5594e023fdaed12a81afd35f06b69465562c952 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 12 Dec 2020 11:02:53 +0000 Subject: [PATCH 638/754] Bump ini from 1.3.5 to 1.3.8 Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8. - [Release notes](https://github.com/isaacs/ini/releases) - [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8) Signed-off-by: dependabot[bot] <support@github.com> --- services/clsi/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index e3f93b08a2..145ec532bf 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -3417,9 +3417,9 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=" + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "inquirer": { "version": "7.1.0", From 4d8e4d54e53c4326a1fd2f1a8b070ec12b0b7b7b Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Thu, 17 Dec 2020 11:49:02 +0000 Subject: [PATCH 639/754] remove Array.from --- services/clsi/app/js/ResourceStateManager.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/services/clsi/app/js/ResourceStateManager.js b/services/clsi/app/js/ResourceStateManager.js index 07cc78571e..08a31683e3 100644 --- a/services/clsi/app/js/ResourceStateManager.js +++ b/services/clsi/app/js/ResourceStateManager.js @@ -6,7 +6,6 @@ // 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 * DS103: Rewrite code to no longer use __guard__ * DS201: Simplify complex destructure assignments @@ -56,12 +55,10 @@ module.exports = ResourceStateManager = { }) } else { logger.log({ state, basePath }, 'writing sync state') - const resourceList = Array.from(resources).map( - (resource) => resource.path - ) + const resourceList = resources.map((resource) => resource.path) return fs.writeFile( stateFile, - [...Array.from(resourceList), `stateHash:${state}`].join('\n'), + [...resourceList, `stateHash:${state}`].join('\n'), callback ) } @@ -104,7 +101,7 @@ module.exports = ResourceStateManager = { new Errors.FilesOutOfSyncError('invalid state for incremental update') ) } else { - const resources = Array.from(resourceList).map((path) => ({ path })) + const resources = resourceList.map((path) => ({ path })) return callback(null, resources) } }) @@ -116,7 +113,7 @@ module.exports = ResourceStateManager = { if (callback == null) { callback = function (error) {} } - for (file of Array.from(resources || [])) { + for (file of resources || []) { for (const dir of Array.from( __guard__(file != null ? file.path : undefined, (x) => x.split('/')) )) { @@ -127,10 +124,10 @@ module.exports = ResourceStateManager = { } // check if any of the input files are not present in list of files const seenFile = {} - for (file of Array.from(allFiles)) { + for (file of allFiles) { seenFile[file] = true } - const missingFiles = Array.from(resources) + const missingFiles = resources .filter((resource) => !seenFile[resource.path]) .map((resource) => resource.path) if ((missingFiles != null ? missingFiles.length : undefined) > 0) { @@ -146,7 +143,7 @@ module.exports = ResourceStateManager = { } else { return callback() } - } + }, } function __guard__(value, transform) { From 5a539b7f90d312dd340c3bdc7b4910d334a93d27 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Thu, 17 Dec 2020 11:59:15 +0000 Subject: [PATCH 640/754] remove guard function --- services/clsi/app/js/ResourceStateManager.js | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/services/clsi/app/js/ResourceStateManager.js b/services/clsi/app/js/ResourceStateManager.js index 08a31683e3..ef7fd430a7 100644 --- a/services/clsi/app/js/ResourceStateManager.js +++ b/services/clsi/app/js/ResourceStateManager.js @@ -7,7 +7,6 @@ /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns - * DS103: Rewrite code to no longer use __guard__ * DS201: Simplify complex destructure assignments * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md @@ -84,10 +83,7 @@ module.exports = ResourceStateManager = { 'project state file truncated' ) } - const array = - __guard__(result != null ? result.toString() : undefined, (x) => - x.split('\n') - ) || [] + const array = result.toString().split('\n') const adjustedLength = Math.max(array.length, 1) const resourceList = array.slice(0, adjustedLength - 1) const oldState = array[adjustedLength - 1] @@ -114,10 +110,9 @@ module.exports = ResourceStateManager = { callback = function (error) {} } for (file of resources || []) { - for (const dir of Array.from( - __guard__(file != null ? file.path : undefined, (x) => x.split('/')) - )) { - if (dir === '..') { + if (file && file.path) { + const dirs = file.path.split('/') + if (dirs.indexOf('..') !== -1) { return callback(new Error('relative path in resource file list')) } } @@ -145,9 +140,3 @@ module.exports = ResourceStateManager = { } }, } - -function __guard__(value, transform) { - return typeof value !== 'undefined' && value !== null - ? transform(value) - : undefined -} From 299d7cc5fe5927525c3ce9d94886c23846eb064c Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Thu, 17 Dec 2020 12:01:14 +0000 Subject: [PATCH 641/754] remove unnecessary returns --- services/clsi/app/js/ResourceStateManager.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/services/clsi/app/js/ResourceStateManager.js b/services/clsi/app/js/ResourceStateManager.js index ef7fd430a7..c2b65be4cc 100644 --- a/services/clsi/app/js/ResourceStateManager.js +++ b/services/clsi/app/js/ResourceStateManager.js @@ -6,7 +6,6 @@ // Fix any style issues and re-enable lint. /* * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns * DS201: Simplify complex destructure assignments * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md @@ -45,7 +44,7 @@ module.exports = ResourceStateManager = { if (state == null) { // remove the file if no state passed in logger.log({ state, basePath }, 'clearing sync state') - return fs.unlink(stateFile, function (err) { + fs.unlink(stateFile, function (err) { if (err != null && err.code !== 'ENOENT') { return callback(err) } else { @@ -55,7 +54,7 @@ module.exports = ResourceStateManager = { } else { logger.log({ state, basePath }, 'writing sync state') const resourceList = resources.map((resource) => resource.path) - return fs.writeFile( + fs.writeFile( stateFile, [...resourceList, `stateHash:${state}`].join('\n'), callback @@ -69,7 +68,7 @@ module.exports = ResourceStateManager = { } const stateFile = Path.join(basePath, this.SYNC_STATE_FILE) const size = this.SYNC_STATE_MAX_SIZE - return SafeReader.readFile(stateFile, size, 'utf8', function ( + SafeReader.readFile(stateFile, size, 'utf8', function ( err, result, bytesRead @@ -98,7 +97,7 @@ module.exports = ResourceStateManager = { ) } else { const resources = resourceList.map((path) => ({ path })) - return callback(null, resources) + callback(null, resources) } }) }, @@ -136,7 +135,7 @@ module.exports = ResourceStateManager = { ) ) } else { - return callback() + callback() } }, } From 64ea22d259e9d69bfbae6940dafc22cdf590a5b3 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Thu, 17 Dec 2020 12:05:17 +0000 Subject: [PATCH 642/754] remove unnecessary null checks --- services/clsi/app/js/ResourceStateManager.js | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/services/clsi/app/js/ResourceStateManager.js b/services/clsi/app/js/ResourceStateManager.js index c2b65be4cc..42e06d3a24 100644 --- a/services/clsi/app/js/ResourceStateManager.js +++ b/services/clsi/app/js/ResourceStateManager.js @@ -7,7 +7,6 @@ /* * decaffeinate suggestions: * DS201: Simplify complex destructure assignments - * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ let ResourceStateManager @@ -37,15 +36,12 @@ module.exports = ResourceStateManager = { SYNC_STATE_MAX_SIZE: 128 * 1024, saveProjectState(state, resources, basePath, callback) { - if (callback == null) { - callback = function (error) {} - } const stateFile = Path.join(basePath, this.SYNC_STATE_FILE) if (state == null) { // remove the file if no state passed in logger.log({ state, basePath }, 'clearing sync state') fs.unlink(stateFile, function (err) { - if (err != null && err.code !== 'ENOENT') { + if (err && err.code !== 'ENOENT') { return callback(err) } else { return callback() @@ -63,9 +59,6 @@ module.exports = ResourceStateManager = { }, checkProjectStateMatches(state, basePath, callback) { - if (callback == null) { - callback = function (error, resources) {} - } const stateFile = Path.join(basePath, this.SYNC_STATE_FILE) const size = this.SYNC_STATE_MAX_SIZE SafeReader.readFile(stateFile, size, 'utf8', function ( @@ -73,7 +66,7 @@ module.exports = ResourceStateManager = { result, bytesRead ) { - if (err != null) { + if (err) { return callback(err) } if (bytesRead === size) { @@ -105,9 +98,6 @@ module.exports = ResourceStateManager = { checkResourceFiles(resources, allFiles, basePath, callback) { // check the paths are all relative to current directory let file - if (callback == null) { - callback = function (error) {} - } for (file of resources || []) { if (file && file.path) { const dirs = file.path.split('/') @@ -124,7 +114,7 @@ module.exports = ResourceStateManager = { const missingFiles = resources .filter((resource) => !seenFile[resource.path]) .map((resource) => resource.path) - if ((missingFiles != null ? missingFiles.length : undefined) > 0) { + if (missingFiles && missingFiles.length > 0) { logger.err( { missingFiles, basePath, allFiles, resources }, 'missing input files for project' From 11ef3c27ed8fc813f7874c777e4703f47daffd4d Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 18 Dec 2020 14:56:53 +0000 Subject: [PATCH 643/754] use Set instead of object --- services/clsi/app/js/OutputFileFinder.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/services/clsi/app/js/OutputFileFinder.js b/services/clsi/app/js/OutputFileFinder.js index f863e0c1ed..35b8c5bef7 100644 --- a/services/clsi/app/js/OutputFileFinder.js +++ b/services/clsi/app/js/OutputFileFinder.js @@ -26,10 +26,9 @@ module.exports = OutputFileFinder = { if (callback == null) { callback = function (error, outputFiles, allFiles) {} } - const incomingResources = {} - for (const resource of Array.from(resources)) { - incomingResources[resource.path] = true - } + const incomingResources = new Set( + resources.map((resource) => resource.path) + ) return OutputFileFinder._getAllFiles(directory, function (error, allFiles) { if (allFiles == null) { @@ -41,10 +40,10 @@ module.exports = OutputFileFinder = { } const outputFiles = [] for (const file of Array.from(allFiles)) { - if (!incomingResources[file]) { + if (!incomingResources.has(file)) { outputFiles.push({ path: file, - type: __guard__(file.match(/\.([^\.]+)$/), (x) => x[1]) + type: __guard__(file.match(/\.([^\.]+)$/), (x) => x[1]), }) } } @@ -70,7 +69,7 @@ module.exports = OutputFileFinder = { '.archive', '-o', '-name', - '.project-*' + '.project-*', ] const args = [ directory, @@ -81,7 +80,7 @@ module.exports = OutputFileFinder = { '-o', '-type', 'f', - '-print' + '-print', ] logger.log({ args }, 'running find command') @@ -105,7 +104,7 @@ module.exports = OutputFileFinder = { }) return callback(null, fileList) }) - } + }, } function __guard__(value, transform) { From 411d69e36b99be7588375efe50bdb168e83f311a Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 18 Dec 2020 14:59:48 +0000 Subject: [PATCH 644/754] remove unnecessary callback code --- services/clsi/app/js/OutputFileFinder.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/services/clsi/app/js/OutputFileFinder.js b/services/clsi/app/js/OutputFileFinder.js index 35b8c5bef7..736dfedb6f 100644 --- a/services/clsi/app/js/OutputFileFinder.js +++ b/services/clsi/app/js/OutputFileFinder.js @@ -23,9 +23,6 @@ const logger = require('logger-sharelatex') module.exports = OutputFileFinder = { findOutputFiles(resources, directory, callback) { - if (callback == null) { - callback = function (error, outputFiles, allFiles) {} - } const incomingResources = new Set( resources.map((resource) => resource.path) ) @@ -52,14 +49,6 @@ module.exports = OutputFileFinder = { }, _getAllFiles(directory, _callback) { - if (_callback == null) { - _callback = function (error, fileList) {} - } - const callback = function (error, fileList) { - _callback(error, fileList) - return (_callback = function () {}) - } - // don't include clsi-specific files/directories in the output list const EXCLUDE_DIRS = [ '-name', From eaec57cd017989684c51757d74c5ffef384447cc Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 18 Dec 2020 15:01:25 +0000 Subject: [PATCH 645/754] simplify null check --- services/clsi/app/js/OutputFileFinder.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/clsi/app/js/OutputFileFinder.js b/services/clsi/app/js/OutputFileFinder.js index 736dfedb6f..013c6f3555 100644 --- a/services/clsi/app/js/OutputFileFinder.js +++ b/services/clsi/app/js/OutputFileFinder.js @@ -11,7 +11,6 @@ * DS101: Remove unnecessary use of Array.from * DS102: Remove unnecessary code created because of implicit returns * DS103: Rewrite code to no longer use __guard__ - * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ let OutputFileFinder @@ -31,7 +30,7 @@ module.exports = OutputFileFinder = { if (allFiles == null) { allFiles = [] } - if (error != null) { + if (error) { logger.err({ err: error }, 'error finding all output files') return callback(error) } From 0bae53c7f34492b8c81be055e24a8f7e03178a3a Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 18 Dec 2020 15:02:40 +0000 Subject: [PATCH 646/754] remove unnecessary returns --- services/clsi/app/js/OutputFileFinder.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/services/clsi/app/js/OutputFileFinder.js b/services/clsi/app/js/OutputFileFinder.js index 013c6f3555..20d1bf073c 100644 --- a/services/clsi/app/js/OutputFileFinder.js +++ b/services/clsi/app/js/OutputFileFinder.js @@ -9,7 +9,6 @@ /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from - * DS102: Remove unnecessary code created because of implicit returns * DS103: Rewrite code to no longer use __guard__ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ @@ -43,7 +42,7 @@ module.exports = OutputFileFinder = { }) } } - return callback(null, outputFiles, allFiles) + callback(null, outputFiles, allFiles) }) }, @@ -90,7 +89,7 @@ module.exports = OutputFileFinder = { let path return (path = Path.relative(directory, file)) }) - return callback(null, fileList) + callback(null, fileList) }) }, } From c044db489787f3211089e5aa303d4a44ed74bd78 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 18 Dec 2020 15:09:48 +0000 Subject: [PATCH 647/754] remove guard helper --- services/clsi/app/js/OutputFileFinder.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/js/OutputFileFinder.js b/services/clsi/app/js/OutputFileFinder.js index 20d1bf073c..70191a2693 100644 --- a/services/clsi/app/js/OutputFileFinder.js +++ b/services/clsi/app/js/OutputFileFinder.js @@ -36,9 +36,10 @@ module.exports = OutputFileFinder = { const outputFiles = [] for (const file of Array.from(allFiles)) { if (!incomingResources.has(file)) { + const type = Path.extname(file) outputFiles.push({ path: file, - type: __guard__(file.match(/\.([^\.]+)$/), (x) => x[1]), + type: Path.extname(file) || undefined, }) } } From 323890cedb4f57e3a43ad1de94745ae72ddf6b41 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 18 Dec 2020 15:10:53 +0000 Subject: [PATCH 648/754] remove Array.from --- services/clsi/app/js/OutputFileFinder.js | 27 +++--------------------- 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/services/clsi/app/js/OutputFileFinder.js b/services/clsi/app/js/OutputFileFinder.js index 70191a2693..e62284fe41 100644 --- a/services/clsi/app/js/OutputFileFinder.js +++ b/services/clsi/app/js/OutputFileFinder.js @@ -1,17 +1,3 @@ -/* eslint-disable - handle-callback-err, - no-return-assign, - no-unused-vars, - no-useless-escape, -*/ -// 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 - * DS103: Rewrite code to no longer use __guard__ - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ let OutputFileFinder const async = require('async') const fs = require('fs') @@ -34,12 +20,11 @@ module.exports = OutputFileFinder = { return callback(error) } const outputFiles = [] - for (const file of Array.from(allFiles)) { + for (const file of allFiles) { if (!incomingResources.has(file)) { - const type = Path.extname(file) outputFiles.push({ path: file, - type: Path.extname(file) || undefined, + type: Path.extname(file).replace(/^\./, '') || undefined, }) } } @@ -62,7 +47,7 @@ module.exports = OutputFileFinder = { const args = [ directory, '(', - ...Array.from(EXCLUDE_DIRS), + ...EXCLUDE_DIRS, ')', '-prune', '-o', @@ -94,9 +79,3 @@ module.exports = OutputFileFinder = { }) }, } - -function __guard__(value, transform) { - return typeof value !== 'undefined' && value !== null - ? transform(value) - : undefined -} From ae064e899773f5aa51bd360831f92aa287f6a77e Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 18 Dec 2020 15:11:29 +0000 Subject: [PATCH 649/754] lint fix --- services/clsi/app/js/OutputFileFinder.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/clsi/app/js/OutputFileFinder.js b/services/clsi/app/js/OutputFileFinder.js index e62284fe41..933222e093 100644 --- a/services/clsi/app/js/OutputFileFinder.js +++ b/services/clsi/app/js/OutputFileFinder.js @@ -24,7 +24,7 @@ module.exports = OutputFileFinder = { if (!incomingResources.has(file)) { outputFiles.push({ path: file, - type: Path.extname(file).replace(/^\./, '') || undefined, + type: Path.extname(file).replace(/^\./, '') || undefined }) } } @@ -42,7 +42,7 @@ module.exports = OutputFileFinder = { '.archive', '-o', '-name', - '.project-*', + '.project-*' ] const args = [ directory, @@ -53,7 +53,7 @@ module.exports = OutputFileFinder = { '-o', '-type', 'f', - '-print', + '-print' ] logger.log({ args }, 'running find command') @@ -77,5 +77,5 @@ module.exports = OutputFileFinder = { }) callback(null, fileList) }) - }, + } } From e84d6305f309ddcb0d561cf6861693dfb07d507f Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 18 Dec 2020 15:16:07 +0000 Subject: [PATCH 650/754] remove unnecessary return --- services/clsi/app/js/OutputFileFinder.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/js/OutputFileFinder.js b/services/clsi/app/js/OutputFileFinder.js index 933222e093..1f62a7568d 100644 --- a/services/clsi/app/js/OutputFileFinder.js +++ b/services/clsi/app/js/OutputFileFinder.js @@ -11,7 +11,7 @@ module.exports = OutputFileFinder = { resources.map((resource) => resource.path) ) - return OutputFileFinder._getAllFiles(directory, function (error, allFiles) { + OutputFileFinder._getAllFiles(directory, function (error, allFiles) { if (allFiles == null) { allFiles = [] } @@ -61,7 +61,7 @@ module.exports = OutputFileFinder = { let stdout = '' proc.stdout.setEncoding('utf8').on('data', (chunk) => (stdout += chunk)) proc.on('error', callback) - return proc.on('close', function (code) { + proc.on('close', function (code) { if (code !== 0) { logger.warn( { directory, code }, From 4169f7fc73da71f25b2c06cecce39421622a4bf2 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 18 Dec 2020 15:16:46 +0000 Subject: [PATCH 651/754] use once for callback --- services/clsi/app/js/OutputFileFinder.js | 4 +++- services/clsi/test/unit/js/OutputFileFinderTests.js | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/js/OutputFileFinder.js b/services/clsi/app/js/OutputFileFinder.js index 1f62a7568d..38fdc7945e 100644 --- a/services/clsi/app/js/OutputFileFinder.js +++ b/services/clsi/app/js/OutputFileFinder.js @@ -2,6 +2,7 @@ let OutputFileFinder const async = require('async') const fs = require('fs') const Path = require('path') +const _ = require('lodash') const { spawn } = require('child_process') const logger = require('logger-sharelatex') @@ -32,7 +33,8 @@ module.exports = OutputFileFinder = { }) }, - _getAllFiles(directory, _callback) { + _getAllFiles(directory, callback) { + callback = _.once(callback) // don't include clsi-specific files/directories in the output list const EXCLUDE_DIRS = [ '-name', diff --git a/services/clsi/test/unit/js/OutputFileFinderTests.js b/services/clsi/test/unit/js/OutputFileFinderTests.js index 4afa25e83e..8744c6e779 100644 --- a/services/clsi/test/unit/js/OutputFileFinderTests.js +++ b/services/clsi/test/unit/js/OutputFileFinderTests.js @@ -28,6 +28,9 @@ describe('OutputFileFinder', function () { fs: (this.fs = {}), child_process: { spawn: (this.spawn = sinon.stub()) }, 'logger-sharelatex': { log: sinon.stub(), warn: sinon.stub() } + }, + globals: { + Math // used by lodash } }) this.directory = '/test/dir' From cee93a0cd962b1cd21966f524caccee3e80389f5 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 18 Dec 2020 15:17:20 +0000 Subject: [PATCH 652/754] clean up unnecessary var --- services/clsi/app/js/OutputFileFinder.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/clsi/app/js/OutputFileFinder.js b/services/clsi/app/js/OutputFileFinder.js index 38fdc7945e..cd94304766 100644 --- a/services/clsi/app/js/OutputFileFinder.js +++ b/services/clsi/app/js/OutputFileFinder.js @@ -74,8 +74,7 @@ module.exports = OutputFileFinder = { let fileList = stdout.trim().split('\n') fileList = fileList.map(function (file) { // Strip leading directory - let path - return (path = Path.relative(directory, file)) + return Path.relative(directory, file) }) callback(null, fileList) }) From 73e09ff99f1c04ad5c5f12777a2aa474ebeb1b25 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 18 Dec 2020 15:17:48 +0000 Subject: [PATCH 653/754] remove unnecessary requires --- services/clsi/app/js/OutputFileFinder.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/services/clsi/app/js/OutputFileFinder.js b/services/clsi/app/js/OutputFileFinder.js index cd94304766..d9d2499699 100644 --- a/services/clsi/app/js/OutputFileFinder.js +++ b/services/clsi/app/js/OutputFileFinder.js @@ -1,6 +1,4 @@ let OutputFileFinder -const async = require('async') -const fs = require('fs') const Path = require('path') const _ = require('lodash') const { spawn } = require('child_process') From 43b0429c289788f6e2c129808093ae427db651b6 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 18 Dec 2020 14:51:46 +0000 Subject: [PATCH 654/754] clean up relative path checking --- services/clsi/app/js/ResourceStateManager.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/services/clsi/app/js/ResourceStateManager.js b/services/clsi/app/js/ResourceStateManager.js index 42e06d3a24..cd7f164a49 100644 --- a/services/clsi/app/js/ResourceStateManager.js +++ b/services/clsi/app/js/ResourceStateManager.js @@ -98,13 +98,12 @@ module.exports = ResourceStateManager = { checkResourceFiles(resources, allFiles, basePath, callback) { // check the paths are all relative to current directory let file - for (file of resources || []) { - if (file && file.path) { - const dirs = file.path.split('/') - if (dirs.indexOf('..') !== -1) { - return callback(new Error('relative path in resource file list')) - } - } + const containsRelativePath = (resource) => { + const dirs = resource.path.split('/') + return dirs.indexOf('..') !== -1 + } + if (resources.some(containsRelativePath)) { + return callback(new Error('relative path in resource file list')) } // check if any of the input files are not present in list of files const seenFile = {} From ca98ee5cffac62131253d631af581b5d321795f1 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 18 Dec 2020 14:52:04 +0000 Subject: [PATCH 655/754] use a Set instead of an Object --- services/clsi/app/js/ResourceStateManager.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/services/clsi/app/js/ResourceStateManager.js b/services/clsi/app/js/ResourceStateManager.js index cd7f164a49..2508302335 100644 --- a/services/clsi/app/js/ResourceStateManager.js +++ b/services/clsi/app/js/ResourceStateManager.js @@ -106,14 +106,11 @@ module.exports = ResourceStateManager = { return callback(new Error('relative path in resource file list')) } // check if any of the input files are not present in list of files - const seenFile = {} - for (file of allFiles) { - seenFile[file] = true - } + const seenFiles = new Set(allFiles) const missingFiles = resources - .filter((resource) => !seenFile[resource.path]) .map((resource) => resource.path) - if (missingFiles && missingFiles.length > 0) { + .filter((path) => !seenFiles.has(path)) + if (missingFiles.length > 0) { logger.err( { missingFiles, basePath, allFiles, resources }, 'missing input files for project' From df1caa14b800c5555cd75f2911c8d6a5ac680308 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 18 Dec 2020 15:44:25 +0000 Subject: [PATCH 656/754] remove unused require --- services/clsi/app/js/ResourceStateManager.js | 1 - 1 file changed, 1 deletion(-) diff --git a/services/clsi/app/js/ResourceStateManager.js b/services/clsi/app/js/ResourceStateManager.js index 2508302335..76903f17df 100644 --- a/services/clsi/app/js/ResourceStateManager.js +++ b/services/clsi/app/js/ResourceStateManager.js @@ -13,7 +13,6 @@ let ResourceStateManager const Path = require('path') const fs = require('fs') const logger = require('logger-sharelatex') -const settings = require('settings-sharelatex') const Errors = require('./Errors') const SafeReader = require('./SafeReader') From f20a0d1a6968917549c528960897fd65dbf13f14 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 18 Dec 2020 15:45:04 +0000 Subject: [PATCH 657/754] remove unused var --- services/clsi/app/js/ResourceStateManager.js | 1 - 1 file changed, 1 deletion(-) diff --git a/services/clsi/app/js/ResourceStateManager.js b/services/clsi/app/js/ResourceStateManager.js index 76903f17df..3407f04987 100644 --- a/services/clsi/app/js/ResourceStateManager.js +++ b/services/clsi/app/js/ResourceStateManager.js @@ -96,7 +96,6 @@ module.exports = ResourceStateManager = { checkResourceFiles(resources, allFiles, basePath, callback) { // check the paths are all relative to current directory - let file const containsRelativePath = (resource) => { const dirs = resource.path.split('/') return dirs.indexOf('..') !== -1 From 36b646bb84c3c11c3de86b95e604cb76af9a027f Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 18 Dec 2020 15:45:20 +0000 Subject: [PATCH 658/754] remove comments --- services/clsi/app/js/ResourceStateManager.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/services/clsi/app/js/ResourceStateManager.js b/services/clsi/app/js/ResourceStateManager.js index 3407f04987..983672ffda 100644 --- a/services/clsi/app/js/ResourceStateManager.js +++ b/services/clsi/app/js/ResourceStateManager.js @@ -1,14 +1,3 @@ -/* eslint-disable - handle-callback-err, - no-unused-vars, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS201: Simplify complex destructure assignments - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ let ResourceStateManager const Path = require('path') const fs = require('fs') From dafe69b5e6b5ba43e657d5e9881a32efbaccdb36 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 18 Dec 2020 15:46:16 +0000 Subject: [PATCH 659/754] remove unused module var --- services/clsi/app/js/ResourceStateManager.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/clsi/app/js/ResourceStateManager.js b/services/clsi/app/js/ResourceStateManager.js index 983672ffda..7bd9df9d8f 100644 --- a/services/clsi/app/js/ResourceStateManager.js +++ b/services/clsi/app/js/ResourceStateManager.js @@ -1,11 +1,10 @@ -let ResourceStateManager const Path = require('path') const fs = require('fs') const logger = require('logger-sharelatex') const Errors = require('./Errors') const SafeReader = require('./SafeReader') -module.exports = ResourceStateManager = { +module.exports = { // The sync state is an identifier which must match for an // incremental update to be allowed. // From e183fffa4bc3b63a77d83f8dc972f1f5efdae26d Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 18 Dec 2020 15:46:36 +0000 Subject: [PATCH 660/754] format fix --- services/clsi/app/js/ResourceStateManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/js/ResourceStateManager.js b/services/clsi/app/js/ResourceStateManager.js index 7bd9df9d8f..c77c254b05 100644 --- a/services/clsi/app/js/ResourceStateManager.js +++ b/services/clsi/app/js/ResourceStateManager.js @@ -109,5 +109,5 @@ module.exports = { } else { callback() } - }, + } } From 49b764a308a82939d3c3ce2d2d85cfbf9aa464e2 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Mon, 28 Dec 2020 13:16:31 +0000 Subject: [PATCH 661/754] [misc] CompileController: exact match for output.pdf The previous regex could mistake user provided pdf files, like `fake_output.pdf`, as the final output file. The frontend expects to find a `output.pdf` file on success. --- services/clsi/app/js/CompileController.js | 6 +-- .../test/unit/js/CompileControllerTests.js | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/services/clsi/app/js/CompileController.js b/services/clsi/app/js/CompileController.js index fb7367cef3..b0ce3bb664 100644 --- a/services/clsi/app/js/CompileController.js +++ b/services/clsi/app/js/CompileController.js @@ -80,11 +80,7 @@ module.exports = CompileController = { let file status = 'failure' for (file of Array.from(outputFiles)) { - if ( - file.path != null - ? file.path.match(/output\.pdf$/) - : undefined - ) { + if (file.path === 'output.pdf') { status = 'success' } } diff --git a/services/clsi/test/unit/js/CompileControllerTests.js b/services/clsi/test/unit/js/CompileControllerTests.js index 1c93c96ea4..70f67b602f 100644 --- a/services/clsi/test/unit/js/CompileControllerTests.js +++ b/services/clsi/test/unit/js/CompileControllerTests.js @@ -129,6 +129,47 @@ describe('CompileController', function () { }) }) + describe('with user provided fake_output.pdf', function () { + beforeEach(function () { + this.output_files = [ + { + path: 'fake_output.pdf', + type: 'pdf', + build: 1234 + }, + { + path: 'output.log', + type: 'log', + build: 1234 + } + ] + this.CompileManager.doCompileWithLock = sinon + .stub() + .callsArgWith(1, null, this.output_files) + this.CompileController.compile(this.req, this.res) + }) + + it('should return the JSON response with status failure', function () { + this.res.status.calledWith(200).should.equal(true) + this.res.send + .calledWith({ + compile: { + status: 'failure', + error: null, + outputFiles: this.output_files.map((file) => { + return { + url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`, + path: file.path, + type: file.type, + build: file.build + } + }) + } + }) + .should.equal(true) + }) + }) + describe('with an error', function () { beforeEach(function () { this.CompileManager.doCompileWithLock = sinon From cac18ac4b943ed8836eeb26bff9f9db8a252e60b Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 5 Jan 2021 18:26:53 +0000 Subject: [PATCH 662/754] [misc] bump the node version to 10.23.1 --- services/clsi/.nvmrc | 2 +- services/clsi/Dockerfile | 2 +- services/clsi/buildscript.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/services/clsi/.nvmrc b/services/clsi/.nvmrc index c2f6421352..2baa2d433a 100644 --- a/services/clsi/.nvmrc +++ b/services/clsi/.nvmrc @@ -1 +1 @@ -10.22.1 +10.23.1 diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 5d702dce79..4be4353cb9 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -FROM node:10.22.1 as base +FROM node:10.23.1 as base WORKDIR /app COPY install_deps.sh /app diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index 6794c8cb56..b2093150c1 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -4,6 +4,6 @@ clsi --docker-repos=gcr.io/overleaf-ops --env-add= --env-pass-through=TEXLIVE_IMAGE ---node-version=10.22.1 +--node-version=10.23.1 --public-repo=True --script-version=3.4.0 From bdbfe70086c78e98f8ca81c9f65f40888cd5adb6 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Tue, 15 Dec 2020 12:03:25 +0000 Subject: [PATCH 663/754] rename staticServer to staticCompileServer --- services/clsi/app.js | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/services/clsi/app.js b/services/clsi/app.js index 9402869c04..05bcffce32 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -118,21 +118,25 @@ const ForbidSymlinks = require('./app/js/StaticServerForbidSymlinks') // create a static server which does not allow access to any symlinks // avoids possible mismatch of root directory between middleware check // and serving the files -const staticServer = ForbidSymlinks(express.static, Settings.path.compilesDir, { - setHeaders(res, path, stat) { - if (Path.basename(path) === 'output.pdf') { - // Calculate an etag in the same way as nginx - // https://github.com/tj/send/issues/65 - const etag = (path, stat) => - `"${Math.ceil(+stat.mtime / 1000).toString(16)}` + - '-' + - Number(stat.size).toString(16) + - '"' - res.set('Etag', etag(path, stat)) +const staticCompileServer = ForbidSymlinks( + express.static, + Settings.path.compilesDir, + { + setHeaders(res, path, stat) { + if (Path.basename(path) === 'output.pdf') { + // Calculate an etag in the same way as nginx + // https://github.com/tj/send/issues/65 + const etag = (path, stat) => + `"${Math.ceil(+stat.mtime / 1000).toString(16)}` + + '-' + + Number(stat.size).toString(16) + + '"' + res.set('Etag', etag(path, stat)) + } + return res.set('Content-Type', ContentTypeMapper.map(path)) } - return res.set('Content-Type', ContentTypeMapper.map(path)) } -}) +) app.get( '/project/:project_id/user/:user_id/build/:build_id/output/*', @@ -141,7 +145,7 @@ app.get( req.url = `/${req.params.project_id}-${req.params.user_id}/` + OutputCacheManager.path(req.params.build_id, `/${req.params[0]}`) - return staticServer(req, res, next) + return staticCompileServer(req, res, next) } ) @@ -154,7 +158,7 @@ app.get('/project/:project_id/build/:build_id/output/*', function ( req.url = `/${req.params.project_id}/` + OutputCacheManager.path(req.params.build_id, `/${req.params[0]}`) - return staticServer(req, res, next) + return staticCompileServer(req, res, next) }) app.get('/project/:project_id/user/:user_id/output/*', function ( @@ -164,7 +168,7 @@ app.get('/project/:project_id/user/:user_id/output/*', function ( ) { // for specific user get the path to the top level file req.url = `/${req.params.project_id}-${req.params.user_id}/${req.params[0]}` - return staticServer(req, res, next) + return staticCompileServer(req, res, next) }) app.get('/project/:project_id/output/*', function (req, res, next) { @@ -179,7 +183,7 @@ app.get('/project/:project_id/output/*', function (req, res, next) { } else { req.url = `/${req.params.project_id}/${req.params[0]}` } - return staticServer(req, res, next) + return staticCompileServer(req, res, next) }) app.get('/oops', function (req, res, next) { From 692dbc8d6bde57730129b76b4456ca8a47a2dbcb Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Tue, 15 Dec 2020 14:59:05 +0000 Subject: [PATCH 664/754] add output directory --- services/clsi/Dockerfile | 4 ++-- services/clsi/app.js | 24 ++++++++++++++++++++-- services/clsi/app/js/CompileManager.js | 6 ++++++ services/clsi/app/js/OutputCacheManager.js | 24 ++++++++++++++-------- services/clsi/config/settings.defaults.js | 1 + 5 files changed, 47 insertions(+), 12 deletions(-) diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 4be4353cb9..7eaa03f814 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -22,7 +22,7 @@ COPY . /app FROM base COPY --from=app /app /app -RUN mkdir -p cache compiles db \ -&& chown node:node cache compiles db +RUN mkdir -p cache compiles db output \ + && chown node:node cache compiles db output CMD ["node", "--expose-gc", "app.js"] diff --git a/services/clsi/app.js b/services/clsi/app.js index 05bcffce32..c87edeb3f2 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -138,6 +138,26 @@ const staticCompileServer = ForbidSymlinks( } ) +const staticOutputServer = ForbidSymlinks( + express.static, + Settings.path.outputDir, + { + setHeaders(res, path, stat) { + if (Path.basename(path) === 'output.pdf') { + // Calculate an etag in the same way as nginx + // https://github.com/tj/send/issues/65 + const etag = (path, stat) => + `"${Math.ceil(+stat.mtime / 1000).toString(16)}` + + '-' + + Number(stat.size).toString(16) + + '"' + res.set('Etag', etag(path, stat)) + } + return res.set('Content-Type', ContentTypeMapper.map(path)) + } + } +) + app.get( '/project/:project_id/user/:user_id/build/:build_id/output/*', function (req, res, next) { @@ -145,7 +165,7 @@ app.get( req.url = `/${req.params.project_id}-${req.params.user_id}/` + OutputCacheManager.path(req.params.build_id, `/${req.params[0]}`) - return staticCompileServer(req, res, next) + return staticOutputServer(req, res, next) } ) @@ -158,7 +178,7 @@ app.get('/project/:project_id/build/:build_id/output/*', function ( req.url = `/${req.params.project_id}/` + OutputCacheManager.path(req.params.build_id, `/${req.params[0]}`) - return staticCompileServer(req, res, next) + return staticOutputServer(req, res, next) }) app.get('/project/:project_id/user/:user_id/output/*', function ( diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js index 68edde49b0..5dab554ff2 100644 --- a/services/clsi/app/js/CompileManager.js +++ b/services/clsi/app/js/CompileManager.js @@ -46,6 +46,9 @@ const getCompileName = function (project_id, user_id) { const getCompileDir = (project_id, user_id) => Path.join(Settings.path.compilesDir, getCompileName(project_id, user_id)) +const getOutputDir = (project_id, user_id) => + Path.join(Settings.path.outputDir, getCompileName(project_id, user_id)) + module.exports = CompileManager = { doCompileWithLock(request, callback) { if (callback == null) { @@ -72,6 +75,8 @@ module.exports = CompileManager = { callback = function (error, outputFiles) {} } const compileDir = getCompileDir(request.project_id, request.user_id) + const outputDir = getOutputDir(request.project_id, request.user_id) + let timer = new Metrics.Timer('write-to-disk') logger.log( { project_id: request.project_id, user_id: request.user_id }, @@ -294,6 +299,7 @@ module.exports = CompileManager = { return OutputCacheManager.saveOutputFiles( outputFiles, compileDir, + outputDir, (error, newOutputFiles) => callback(null, newOutputFiles) ) } diff --git a/services/clsi/app/js/OutputCacheManager.js b/services/clsi/app/js/OutputCacheManager.js index b34dea7994..8aefb9b77a 100644 --- a/services/clsi/app/js/OutputCacheManager.js +++ b/services/clsi/app/js/OutputCacheManager.js @@ -26,8 +26,8 @@ const crypto = require('crypto') const OutputFileOptimiser = require('./OutputFileOptimiser') module.exports = OutputCacheManager = { - CACHE_SUBDIR: '.cache/clsi', - ARCHIVE_SUBDIR: '.archive/clsi', + CACHE_SUBDIR: 'generated-files', + ARCHIVE_SUBDIR: 'archived-logs', // build id is HEXDATE-HEXRANDOM from Date.now()and RandomBytes // for backwards compatibility, make the randombytes part optional BUILD_REGEX: /^[0-9a-f]+(-[0-9a-f]+)?$/, @@ -59,7 +59,7 @@ module.exports = OutputCacheManager = { }) }, - saveOutputFiles(outputFiles, compileDir, callback) { + saveOutputFiles(outputFiles, compileDir, outputDir, callback) { if (callback == null) { callback = function (error) {} } @@ -70,22 +70,29 @@ module.exports = OutputCacheManager = { return OutputCacheManager.saveOutputFilesInBuildDir( outputFiles, compileDir, + outputDir, buildId, callback ) }) }, - saveOutputFilesInBuildDir(outputFiles, compileDir, buildId, callback) { + saveOutputFilesInBuildDir( + outputFiles, + compileDir, + outputDir, + buildId, + callback + ) { // make a compileDir/CACHE_SUBDIR/build_id directory and // copy all the output files into it if (callback == null) { callback = function (error) {} } - const cacheRoot = Path.join(compileDir, OutputCacheManager.CACHE_SUBDIR) + const cacheRoot = Path.join(outputDir, OutputCacheManager.CACHE_SUBDIR) // Put the files into a new cache subdirectory const cacheDir = Path.join( - compileDir, + outputDir, OutputCacheManager.CACHE_SUBDIR, buildId ) @@ -102,6 +109,7 @@ module.exports = OutputCacheManager = { OutputCacheManager.archiveLogs( outputFiles, compileDir, + outputDir, buildId, function (err) { if (err != null) { @@ -198,12 +206,12 @@ module.exports = OutputCacheManager = { }) }, - archiveLogs(outputFiles, compileDir, buildId, callback) { + archiveLogs(outputFiles, compileDir, outputDir, buildId, callback) { if (callback == null) { callback = function (error) {} } const archiveDir = Path.join( - compileDir, + outputDir, OutputCacheManager.ARCHIVE_SUBDIR, buildId ) diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index 823f1f737f..72c3471ba9 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -27,6 +27,7 @@ module.exports = { path: { compilesDir: Path.resolve(__dirname, '../compiles'), + outputDir: Path.resolve(__dirname, '../output'), clsiCacheDir: Path.resolve(__dirname, '../cache'), synctexBaseDir(projectId) { return Path.join(this.compilesDir, projectId) From 565cd53eb58a18f3f0a7ee71f117a18434fb4202 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Wed, 16 Dec 2020 11:18:18 +0000 Subject: [PATCH 665/754] add git ignore for output directory --- services/clsi/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/.gitignore b/services/clsi/.gitignore index b32ea20954..dee3eca1f5 100644 --- a/services/clsi/.gitignore +++ b/services/clsi/.gitignore @@ -2,6 +2,7 @@ node_modules test/acceptance/fixtures/tmp compiles +output .DS_Store *~ cache From b5346658b066bfa5c99f108a0601f38764ca00ec Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Wed, 16 Dec 2020 15:14:18 +0000 Subject: [PATCH 666/754] clear output directory when clearing project --- services/clsi/app/js/CompileManager.js | 13 +++++++++++-- .../clsi/test/unit/js/CompileManagerTests.js | 18 +++++++++++++----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js index 5dab554ff2..25741005f8 100644 --- a/services/clsi/app/js/CompileManager.js +++ b/services/clsi/app/js/CompileManager.js @@ -329,6 +329,7 @@ module.exports = CompileManager = { } const compileDir = getCompileDir(project_id, user_id) + const outputDir = getOutputDir(project_id, user_id) return CompileManager._checkDirectory(compileDir, function (err, exists) { if (err != null) { @@ -338,7 +339,13 @@ module.exports = CompileManager = { return callback() } // skip removal if no directory present - const proc = child_process.spawn('rm', ['-r', compileDir]) + const proc = child_process.spawn('rm', [ + '-r', + '-f', + '--', + compileDir, + outputDir + ]) proc.on('error', callback) @@ -349,7 +356,9 @@ module.exports = CompileManager = { if (code === 0) { return callback(null) } else { - return callback(new Error(`rm -r ${compileDir} failed: ${stderr}`)) + return callback( + new Error(`rm -r ${compileDir} ${outputDir} failed: ${stderr}`) + ) } }) }) diff --git a/services/clsi/test/unit/js/CompileManagerTests.js b/services/clsi/test/unit/js/CompileManagerTests.js index cb85f2f131..917dfe8cb7 100644 --- a/services/clsi/test/unit/js/CompileManagerTests.js +++ b/services/clsi/test/unit/js/CompileManagerTests.js @@ -34,7 +34,8 @@ describe('CompileManager', function () { './OutputCacheManager': (this.OutputCacheManager = {}), 'settings-sharelatex': (this.Settings = { path: { - compilesDir: '/compiles/dir' + compilesDir: '/compiles/dir', + outputDir: '/output/dir' }, synctexBaseDir() { return '/compile' @@ -166,6 +167,7 @@ describe('CompileManager', function () { this.env = {} this.Settings.compileDir = 'compiles' this.compileDir = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}` + this.outputDir = `${this.Settings.path.outputDir}/${this.project_id}-${this.user_id}` this.ResourceWriter.syncResourcesToDisk = sinon .stub() .callsArgWith(2, null, this.resources) @@ -175,7 +177,7 @@ describe('CompileManager', function () { .callsArgWith(2, null, this.output_files) this.OutputCacheManager.saveOutputFiles = sinon .stub() - .callsArgWith(2, null, this.build_files) + .callsArgWith(3, null, this.build_files) this.DraftModeManager.injectDraftMode = sinon.stub().callsArg(1) return (this.TikzManager.checkMainFile = sinon.stub().callsArg(3, false)) }) @@ -312,7 +314,10 @@ describe('CompileManager', function () { return this.child_process.spawn .calledWith('rm', [ '-r', - `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}` + '-f', + '--', + `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, + `${this.Settings.path.outputDir}/${this.project_id}-${this.user_id}` ]) .should.equal(true) }) @@ -348,7 +353,10 @@ describe('CompileManager', function () { return this.child_process.spawn .calledWith('rm', [ '-r', - `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}` + '-f', + '--', + `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, + `${this.Settings.path.outputDir}/${this.project_id}-${this.user_id}` ]) .should.equal(true) }) @@ -357,7 +365,7 @@ describe('CompileManager', function () { this.callback.calledWithExactly(sinon.match(Error)).should.equal(true) this.callback.args[0][0].message.should.equal( - `rm -r ${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id} failed: ${this.error}` + `rm -r ${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id} ${this.Settings.path.outputDir}/${this.project_id}-${this.user_id} failed: ${this.error}` ) }) }) From 6fa081522d6ec810b0778f06789eedbb57abeda0 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 22 Jan 2021 11:03:47 +0000 Subject: [PATCH 667/754] add a warning for requests without build id --- services/clsi/app.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/clsi/app.js b/services/clsi/app.js index c87edeb3f2..930ebc3e1c 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -187,11 +187,13 @@ app.get('/project/:project_id/user/:user_id/output/*', function ( next ) { // for specific user get the path to the top level file + logger.warn({ url: req.url }, 'direct request for file in compile directory') req.url = `/${req.params.project_id}-${req.params.user_id}/${req.params[0]}` return staticCompileServer(req, res, next) }) app.get('/project/:project_id/output/*', function (req, res, next) { + logger.warn({ url: req.url }, 'direct request for file in compile directory') if ( (req.query != null ? req.query.build : undefined) != null && req.query.build.match(OutputCacheManager.BUILD_REGEX) From 9dc55729ac1e5ec7a04d5566f19eef3e24ff8ab6 Mon Sep 17 00:00:00 2001 From: Brian Gough <briangough@users.noreply.github.com> Date: Mon, 25 Jan 2021 15:26:53 +0000 Subject: [PATCH 668/754] Revert "decaff cleanup ResourceStateManager" --- services/clsi/app/js/ResourceStateManager.js | 85 +++++++++++++++----- 1 file changed, 64 insertions(+), 21 deletions(-) diff --git a/services/clsi/app/js/ResourceStateManager.js b/services/clsi/app/js/ResourceStateManager.js index c77c254b05..07cc78571e 100644 --- a/services/clsi/app/js/ResourceStateManager.js +++ b/services/clsi/app/js/ResourceStateManager.js @@ -1,10 +1,27 @@ +/* eslint-disable + handle-callback-err, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__ + * DS201: Simplify complex destructure assignments + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let ResourceStateManager const Path = require('path') const fs = require('fs') const logger = require('logger-sharelatex') +const settings = require('settings-sharelatex') const Errors = require('./Errors') const SafeReader = require('./SafeReader') -module.exports = { +module.exports = ResourceStateManager = { // The sync state is an identifier which must match for an // incremental update to be allowed. // @@ -23,12 +40,15 @@ module.exports = { SYNC_STATE_MAX_SIZE: 128 * 1024, saveProjectState(state, resources, basePath, callback) { + if (callback == null) { + callback = function (error) {} + } const stateFile = Path.join(basePath, this.SYNC_STATE_FILE) if (state == null) { // remove the file if no state passed in logger.log({ state, basePath }, 'clearing sync state') - fs.unlink(stateFile, function (err) { - if (err && err.code !== 'ENOENT') { + return fs.unlink(stateFile, function (err) { + if (err != null && err.code !== 'ENOENT') { return callback(err) } else { return callback() @@ -36,24 +56,29 @@ module.exports = { }) } else { logger.log({ state, basePath }, 'writing sync state') - const resourceList = resources.map((resource) => resource.path) - fs.writeFile( + const resourceList = Array.from(resources).map( + (resource) => resource.path + ) + return fs.writeFile( stateFile, - [...resourceList, `stateHash:${state}`].join('\n'), + [...Array.from(resourceList), `stateHash:${state}`].join('\n'), callback ) } }, checkProjectStateMatches(state, basePath, callback) { + if (callback == null) { + callback = function (error, resources) {} + } const stateFile = Path.join(basePath, this.SYNC_STATE_FILE) const size = this.SYNC_STATE_MAX_SIZE - SafeReader.readFile(stateFile, size, 'utf8', function ( + return SafeReader.readFile(stateFile, size, 'utf8', function ( err, result, bytesRead ) { - if (err) { + if (err != null) { return callback(err) } if (bytesRead === size) { @@ -62,7 +87,10 @@ module.exports = { 'project state file truncated' ) } - const array = result.toString().split('\n') + const array = + __guard__(result != null ? result.toString() : undefined, (x) => + x.split('\n') + ) || [] const adjustedLength = Math.max(array.length, 1) const resourceList = array.slice(0, adjustedLength - 1) const oldState = array[adjustedLength - 1] @@ -76,27 +104,36 @@ module.exports = { new Errors.FilesOutOfSyncError('invalid state for incremental update') ) } else { - const resources = resourceList.map((path) => ({ path })) - callback(null, resources) + const resources = Array.from(resourceList).map((path) => ({ path })) + return callback(null, resources) } }) }, checkResourceFiles(resources, allFiles, basePath, callback) { // check the paths are all relative to current directory - const containsRelativePath = (resource) => { - const dirs = resource.path.split('/') - return dirs.indexOf('..') !== -1 + let file + if (callback == null) { + callback = function (error) {} } - if (resources.some(containsRelativePath)) { - return callback(new Error('relative path in resource file list')) + for (file of Array.from(resources || [])) { + for (const dir of Array.from( + __guard__(file != null ? file.path : undefined, (x) => x.split('/')) + )) { + if (dir === '..') { + return callback(new Error('relative path in resource file list')) + } + } } // check if any of the input files are not present in list of files - const seenFiles = new Set(allFiles) - const missingFiles = resources + const seenFile = {} + for (file of Array.from(allFiles)) { + seenFile[file] = true + } + const missingFiles = Array.from(resources) + .filter((resource) => !seenFile[resource.path]) .map((resource) => resource.path) - .filter((path) => !seenFiles.has(path)) - if (missingFiles.length > 0) { + if ((missingFiles != null ? missingFiles.length : undefined) > 0) { logger.err( { missingFiles, basePath, allFiles, resources }, 'missing input files for project' @@ -107,7 +144,13 @@ module.exports = { ) ) } else { - callback() + return callback() } } } + +function __guard__(value, transform) { + return typeof value !== 'undefined' && value !== null + ? transform(value) + : undefined +} From a86e521ac08b77800ce9df325c876a8276145baa Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Tue, 26 Jan 2021 11:03:18 +0000 Subject: [PATCH 669/754] add unit test for non-existent state file --- .../test/unit/js/ResourceStateManagerTests.js | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/services/clsi/test/unit/js/ResourceStateManagerTests.js b/services/clsi/test/unit/js/ResourceStateManagerTests.js index ca2b625f33..e2bc073844 100644 --- a/services/clsi/test/unit/js/ResourceStateManagerTests.js +++ b/services/clsi/test/unit/js/ResourceStateManagerTests.js @@ -122,6 +122,32 @@ describe('ResourceStateManager', function () { }) }) + describe('when the state file is not present', function () { + beforeEach(function () { + this.SafeReader.readFile = sinon.stub().callsArg(3) + return this.ResourceStateManager.checkProjectStateMatches( + this.state, + this.basePath, + this.callback + ) + }) + + it('should read the resource file', function () { + return this.SafeReader.readFile + .calledWith(this.resourceFileName) + .should.equal(true) + }) + + it('should call the callback with an error', function () { + this.callback + .calledWith(sinon.match(Errors.FilesOutOfSyncError)) + .should.equal(true) + + const message = this.callback.args[0][0].message + expect(message).to.include('invalid state for incremental update') + }) + }) + return describe('when the state does not match', function () { beforeEach(function () { this.SafeReader.readFile = sinon From a8e47da9e98f3c461893233603296253ea18b77e Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Tue, 26 Jan 2021 11:04:33 +0000 Subject: [PATCH 670/754] Revert "Merge pull request #205 from overleaf/revert-200-bg-decaff-cleanup" This reverts commit 76d8d3181b9464d1e1bbc713a2729ca269d9c047, reversing changes made to 31a8dc3a98d73c2707d633712f0ef7207013e78b. --- services/clsi/app/js/ResourceStateManager.js | 85 +++++--------------- 1 file changed, 21 insertions(+), 64 deletions(-) diff --git a/services/clsi/app/js/ResourceStateManager.js b/services/clsi/app/js/ResourceStateManager.js index 07cc78571e..c77c254b05 100644 --- a/services/clsi/app/js/ResourceStateManager.js +++ b/services/clsi/app/js/ResourceStateManager.js @@ -1,27 +1,10 @@ -/* eslint-disable - handle-callback-err, - no-unused-vars, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS101: Remove unnecessary use of Array.from - * DS102: Remove unnecessary code created because of implicit returns - * DS103: Rewrite code to no longer use __guard__ - * DS201: Simplify complex destructure assignments - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -let ResourceStateManager const Path = require('path') const fs = require('fs') const logger = require('logger-sharelatex') -const settings = require('settings-sharelatex') const Errors = require('./Errors') const SafeReader = require('./SafeReader') -module.exports = ResourceStateManager = { +module.exports = { // The sync state is an identifier which must match for an // incremental update to be allowed. // @@ -40,15 +23,12 @@ module.exports = ResourceStateManager = { SYNC_STATE_MAX_SIZE: 128 * 1024, saveProjectState(state, resources, basePath, callback) { - if (callback == null) { - callback = function (error) {} - } const stateFile = Path.join(basePath, this.SYNC_STATE_FILE) if (state == null) { // remove the file if no state passed in logger.log({ state, basePath }, 'clearing sync state') - return fs.unlink(stateFile, function (err) { - if (err != null && err.code !== 'ENOENT') { + fs.unlink(stateFile, function (err) { + if (err && err.code !== 'ENOENT') { return callback(err) } else { return callback() @@ -56,29 +36,24 @@ module.exports = ResourceStateManager = { }) } else { logger.log({ state, basePath }, 'writing sync state') - const resourceList = Array.from(resources).map( - (resource) => resource.path - ) - return fs.writeFile( + const resourceList = resources.map((resource) => resource.path) + fs.writeFile( stateFile, - [...Array.from(resourceList), `stateHash:${state}`].join('\n'), + [...resourceList, `stateHash:${state}`].join('\n'), callback ) } }, checkProjectStateMatches(state, basePath, callback) { - if (callback == null) { - callback = function (error, resources) {} - } const stateFile = Path.join(basePath, this.SYNC_STATE_FILE) const size = this.SYNC_STATE_MAX_SIZE - return SafeReader.readFile(stateFile, size, 'utf8', function ( + SafeReader.readFile(stateFile, size, 'utf8', function ( err, result, bytesRead ) { - if (err != null) { + if (err) { return callback(err) } if (bytesRead === size) { @@ -87,10 +62,7 @@ module.exports = ResourceStateManager = { 'project state file truncated' ) } - const array = - __guard__(result != null ? result.toString() : undefined, (x) => - x.split('\n') - ) || [] + const array = result.toString().split('\n') const adjustedLength = Math.max(array.length, 1) const resourceList = array.slice(0, adjustedLength - 1) const oldState = array[adjustedLength - 1] @@ -104,36 +76,27 @@ module.exports = ResourceStateManager = { new Errors.FilesOutOfSyncError('invalid state for incremental update') ) } else { - const resources = Array.from(resourceList).map((path) => ({ path })) - return callback(null, resources) + const resources = resourceList.map((path) => ({ path })) + callback(null, resources) } }) }, checkResourceFiles(resources, allFiles, basePath, callback) { // check the paths are all relative to current directory - let file - if (callback == null) { - callback = function (error) {} + const containsRelativePath = (resource) => { + const dirs = resource.path.split('/') + return dirs.indexOf('..') !== -1 } - for (file of Array.from(resources || [])) { - for (const dir of Array.from( - __guard__(file != null ? file.path : undefined, (x) => x.split('/')) - )) { - if (dir === '..') { - return callback(new Error('relative path in resource file list')) - } - } + if (resources.some(containsRelativePath)) { + return callback(new Error('relative path in resource file list')) } // check if any of the input files are not present in list of files - const seenFile = {} - for (file of Array.from(allFiles)) { - seenFile[file] = true - } - const missingFiles = Array.from(resources) - .filter((resource) => !seenFile[resource.path]) + const seenFiles = new Set(allFiles) + const missingFiles = resources .map((resource) => resource.path) - if ((missingFiles != null ? missingFiles.length : undefined) > 0) { + .filter((path) => !seenFiles.has(path)) + if (missingFiles.length > 0) { logger.err( { missingFiles, basePath, allFiles, resources }, 'missing input files for project' @@ -144,13 +107,7 @@ module.exports = ResourceStateManager = { ) ) } else { - return callback() + callback() } } } - -function __guard__(value, transform) { - return typeof value !== 'undefined' && value !== null - ? transform(value) - : undefined -} From 865e68051e46a54abc1ae8a6cf82f86176417bf8 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Tue, 26 Jan 2021 13:59:28 +0000 Subject: [PATCH 671/754] include fallback for missing state file --- services/clsi/app/js/ResourceStateManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/js/ResourceStateManager.js b/services/clsi/app/js/ResourceStateManager.js index c77c254b05..e36f19ed67 100644 --- a/services/clsi/app/js/ResourceStateManager.js +++ b/services/clsi/app/js/ResourceStateManager.js @@ -62,7 +62,7 @@ module.exports = { 'project state file truncated' ) } - const array = result.toString().split('\n') + const array = result ? result.toString().split('\n') : [] const adjustedLength = Math.max(array.length, 1) const resourceList = array.slice(0, adjustedLength - 1) const oldState = array[adjustedLength - 1] From fc1157469830b666e7829327c5e6211c605afdf6 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Tue, 26 Jan 2021 14:08:29 +0000 Subject: [PATCH 672/754] add uncaughtException handler --- services/clsi/app.js | 9 +++++++++ services/clsi/config/settings.defaults.js | 2 ++ 2 files changed, 11 insertions(+) diff --git a/services/clsi/app.js b/services/clsi/app.js index 930ebc3e1c..280b869472 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -339,6 +339,15 @@ const loadHttpPort = Settings.internal.load_balancer_agent.local_port if (!module.parent) { // Called directly + + // handle uncaught exceptions when running in production + if (Settings.catchErrors) { + process.removeAllListeners('uncaughtException') + process.on('uncaughtException', (error) => + logger.error({ err: error }, 'uncaughtException') + ) + } + app.listen(port, host, (error) => { if (error) { logger.fatal({ error }, `Error starting CLSI on ${host}:${port}`) diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index 72c3471ba9..a0ad8433aa 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -25,6 +25,8 @@ module.exports = { processLifespanLimitMs: parseInt(process.env.PROCESS_LIFE_SPAN_LIMIT_MS) || 60 * 60 * 24 * 1000 * 2, + catchErrors: process.env.CATCH_ERRORS === 'true', + path: { compilesDir: Path.resolve(__dirname, '../compiles'), outputDir: Path.resolve(__dirname, '../output'), From f0b4f1238b4c608a0754ee9c36ce2df7f6e2bb80 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Tue, 26 Jan 2021 16:35:39 +0000 Subject: [PATCH 673/754] provide a /oops-internal endpoint for testing uncaughtExceptions --- services/clsi/app.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/services/clsi/app.js b/services/clsi/app.js index 280b869472..aee8d44832 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -213,6 +213,12 @@ app.get('/oops', function (req, res, next) { return res.send('error\n') }) +app.get('/oops-internal', function (req, res, next) { + setTimeout(function () { + throw new Error('Test error') + }, 1) +}) + app.get('/status', (req, res, next) => res.send('CLSI is alive\n')) Settings.processTooOld = false From 498ca80cd35308068d8cc6bb76b14e727b9c1665 Mon Sep 17 00:00:00 2001 From: Alf Eaton <alf.eaton@overleaf.com> Date: Thu, 18 Feb 2021 15:09:48 +0000 Subject: [PATCH 674/754] Ensure that app folders exist before running chown --- services/clsi/entrypoint.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh index 2696574873..14921d1745 100755 --- a/services/clsi/entrypoint.sh +++ b/services/clsi/entrypoint.sh @@ -8,9 +8,9 @@ groupadd --non-unique --gid ${DOCKER_GROUP} dockeronhost usermod -aG dockeronhost node # compatibility: initial volume setup -chown node:node /app/cache -chown node:node /app/compiles -chown node:node /app/db +mkdir -p /app/cache && chown node:node /app/cache +mkdir -p /app/compiles && chown node:node /app/compiles +mkdir -p /app/db && chown node:node /app/db # make synctex available for remount in compiles cp /app/bin/synctex /app/bin/synctex-mount/synctex From 18dbb124340010748d02e5ceb2e979e8a5ddee86 Mon Sep 17 00:00:00 2001 From: Alf Eaton <alf.eaton@overleaf.com> Date: Thu, 18 Feb 2021 15:33:16 +0000 Subject: [PATCH 675/754] Add /app/output --- services/clsi/entrypoint.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh index 14921d1745..b05a773ea1 100755 --- a/services/clsi/entrypoint.sh +++ b/services/clsi/entrypoint.sh @@ -11,6 +11,7 @@ usermod -aG dockeronhost node mkdir -p /app/cache && chown node:node /app/cache mkdir -p /app/compiles && chown node:node /app/compiles mkdir -p /app/db && chown node:node /app/db +mkdir -p /app/output && chown node:node /app/output # make synctex available for remount in compiles cp /app/bin/synctex /app/bin/synctex-mount/synctex From e7be75a4b7e68498bb92bfc28d15e34e5e499565 Mon Sep 17 00:00:00 2001 From: Alf Eaton <alf.eaton@overleaf.com> Date: Wed, 10 Mar 2021 22:36:33 +0000 Subject: [PATCH 676/754] Update README.md --- services/clsi/README.md | 69 ++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/services/clsi/README.md b/services/clsi/README.md index 02cd0a470f..2cc946cb50 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -5,7 +5,7 @@ A web api for compiling LaTeX documents in the cloud The Common LaTeX Service Interface (CLSI) provides a RESTful interface to traditional LaTeX tools (or, more generally, any command line tool for composing marked-up documents into a display format such as PDF or HTML). The CLSI listens on the following ports by default: -* TCP/3009 - the RESTful interface +* TCP/3013 - the RESTful interface * TCP/3048 - reports load information * TCP/3049 - HTTP interface to control the CLSI service @@ -33,25 +33,41 @@ Installation The CLSI can be installed and set up as part of the entire [Overleaf stack](https://github.com/overleaf/overleaf) (complete with front end editor and document storage), or it can be run as a standalone service. To run is as a standalone service, first checkout this repository: $ git clone git@github.com:overleaf/clsi.git - + Then install the require npm modules: $ npm install - -Then compile the coffee script source files: - $ grunt install - -Finally, (after configuring your local database - see the Config section), run the CLSI service: +Then build the Docker image: + + $ docker build . -t overleaf/clsi + +Then pull the TeXLive image: + + $ docker pull texlive/texlive + +Then start the Docker container: + + $ docker run --rm \ + -p 127.0.0.1:3013:3013 \ + -e LISTEN_ADDRESS=0.0.0.0 \ + -e DOCKER_RUNNER=true \ + -e TEXLIVE_IMAGE=texlive/texlive \ + -e TEXLIVE_IMAGE_USER=root \ + -e COMPILES_HOST_DIR="$PWD/compiles" \ + -v "$PWD/compiles:/app/compiles" \ + -v "$PWD/cache:/app/cache" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + overleaf/clsi + +Note: if you're running the CLSI in macOS you may need to use `-v /var/run/docker.sock.raw:/var/run/docker.sock` instead. + +The CLSI should then be running at <http://localhost:3013> - $ grunt run - -The CLSI should then be running at http://localhost:3013. - Config ------ -You will need to set up a database in mysql to use with the CLSI, and then fill in the database name, username and password in the config file at `config/settings.development.coffee`. +The CLSI will use a SQLite database by default, but you can optionally set up a MySQL database and then fill in the database name, username and password in the config file at `config/settings.development.coffee`. API --- @@ -64,35 +80,38 @@ The CLSI is based on a JSON API. POST /project/<project-id>/compile -```javascript +```json5 { "compile": { "options": { // Which compiler to use. Can be latex, pdflatex, xelatex or lualatex "compiler": "lualatex", // How many seconds to wait before killing the process. Default is 60. - "timeout": 40 + "timeout": 40 }, // The main file to run LaTeX on - "rootResourcePath": "main.tex", + "rootResourcePath": "main.tex", // An array of files to include in the compilation. May have either the content // passed directly, or a URL where it can be downloaded. - "resources": [{ + "resources": [ + { "path": "main.tex", "content": "\\documentclass{article}\n\\begin{document}\nHello World\n\\end{document}" - }, { - "path": "image.png", - "url": "www.example.com/image.png", - "modified": 123456789 // Unix time since epoch - }] + } + // ,{ + // "path": "image.png", + // "url": "www.example.com/image.png", + // "modified": 123456789 // Unix time since epoch + // } + ] } } ``` -With `curl`, if you place the above json in a file called `data.json`, the request would look like this: +With `curl`, if you place the above JSON in a file called `data.json`, the request would look like this: ``` shell -$ curl -X POST -d @data.json localhost:3013/project/<id>/compile +$ curl -X POST -H 'Content-Type: application/json' -d @data.json http://localhost:3013/project/<id>/compile ``` You can specify any project-id in the URL, and the files and LaTeX environment will be persisted between requests. @@ -100,7 +119,7 @@ URLs will be downloaded and cached until provided with a more recent modified da #### Example Response -```javascript +```json { "compile": { "status": "success", @@ -120,4 +139,4 @@ License The code in this repository is released under the GNU AFFERO GENERAL PUBLIC LICENSE, version 3. A copy can be found in the `LICENSE` file. -Copyright (c) Overleaf, 2014-2019. +Copyright (c) Overleaf, 2014-2021. From 9c596fb17dd17916948d45fb390ad4df9817be6c Mon Sep 17 00:00:00 2001 From: Alf Eaton <alf.eaton@overleaf.com> Date: Wed, 10 Mar 2021 22:38:34 +0000 Subject: [PATCH 677/754] Update README.md --- services/clsi/README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/services/clsi/README.md b/services/clsi/README.md index 2cc946cb50..f8f4ef3abf 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -34,10 +34,6 @@ The CLSI can be installed and set up as part of the entire [Overleaf stack](http $ git clone git@github.com:overleaf/clsi.git -Then install the require npm modules: - - $ npm install - Then build the Docker image: $ docker build . -t overleaf/clsi From f38cef6e569f38a15fccedcb832cf5bcf56877ea Mon Sep 17 00:00:00 2001 From: Alf Eaton <75253002+aeaton-overleaf@users.noreply.github.com> Date: Thu, 11 Mar 2021 11:50:47 +0000 Subject: [PATCH 678/754] Change settings file .coffee to .js --- services/clsi/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/clsi/README.md b/services/clsi/README.md index f8f4ef3abf..c4c1eabf0e 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -9,7 +9,7 @@ The Common LaTeX Service Interface (CLSI) provides a RESTful interface to tradit * TCP/3048 - reports load information * TCP/3049 - HTTP interface to control the CLSI service -These defaults can be modified in `config/settings.defaults.coffee`. +These defaults can be modified in `config/settings.defaults.js`. The provided `Dockerfile` builds a docker image which has the docker command line tools installed. The configuration in `docker-compose-config.yml` mounts the docker socket, in order that the CLSI container can talk to the docker host it is running in. This allows it to spin up `sibling containers` running an image with a TeX distribution installed to perform the actual compiles. @@ -63,7 +63,7 @@ The CLSI should then be running at <http://localhost:3013> Config ------ -The CLSI will use a SQLite database by default, but you can optionally set up a MySQL database and then fill in the database name, username and password in the config file at `config/settings.development.coffee`. +The CLSI will use a SQLite database by default, but you can optionally set up a MySQL database and then fill in the database name, username and password in the config file at `config/settings.development.js`. API --- From 9f2d219102de777c8a6e2f32655294dad0fa777a Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Fri, 12 Mar 2021 16:18:36 -0500 Subject: [PATCH 679/754] Add a global test setup file Set up generally useful stuff: * chai.should() * logger stubs * globals in SandboxedModule, including Buffer and process, which are now required in Node 12 --- services/clsi/.mocharc.json | 3 +++ .../acceptance/js/BrokenLatexFileTests.js | 1 - .../test/acceptance/js/DeleteOldFilesTest.js | 1 - .../acceptance/js/ExampleDocumentTests.js | 1 - .../acceptance/js/SimpleLatexFileTests.js | 1 - .../clsi/test/acceptance/js/SynctexTests.js | 1 - .../clsi/test/acceptance/js/TimeoutTests.js | 1 - .../test/acceptance/js/UrlCachingTests.js | 1 - .../clsi/test/acceptance/js/WordcountTests.js | 1 - services/clsi/test/setup.js | 19 +++++++++++++++++++ .../test/unit/js/CompileControllerTests.js | 9 +-------- .../clsi/test/unit/js/CompileManagerTests.js | 2 -- .../test/unit/js/ContentTypeMapperTests.js | 1 - .../test/unit/js/DockerLockManagerTests.js | 7 +------ .../clsi/test/unit/js/DockerRunnerTests.js | 7 ------- .../test/unit/js/DraftModeManagerTests.js | 4 +--- .../clsi/test/unit/js/LatexRunnerTests.js | 5 ----- .../clsi/test/unit/js/LockManagerTests.js | 6 ------ .../test/unit/js/OutputFileFinderTests.js | 4 +--- .../test/unit/js/OutputFileOptimiserTests.js | 2 -- .../unit/js/ProjectPersistenceManagerTests.js | 6 ------ .../clsi/test/unit/js/RequestParserTests.js | 1 - .../test/unit/js/ResourceStateManagerTests.js | 2 -- .../clsi/test/unit/js/ResourceWriterTests.js | 4 +--- .../js/StaticServerForbidSymlinksTests.js | 6 ------ services/clsi/test/unit/js/TikzManager.js | 4 +--- services/clsi/test/unit/js/UrlCacheTests.js | 2 -- services/clsi/test/unit/js/UrlFetcherTests.js | 5 ----- 28 files changed, 28 insertions(+), 79 deletions(-) create mode 100644 services/clsi/.mocharc.json create mode 100644 services/clsi/test/setup.js diff --git a/services/clsi/.mocharc.json b/services/clsi/.mocharc.json new file mode 100644 index 0000000000..dc3280aa96 --- /dev/null +++ b/services/clsi/.mocharc.json @@ -0,0 +1,3 @@ +{ + "require": "test/setup.js" +} diff --git a/services/clsi/test/acceptance/js/BrokenLatexFileTests.js b/services/clsi/test/acceptance/js/BrokenLatexFileTests.js index c26e0c4018..34d3b37c92 100644 --- a/services/clsi/test/acceptance/js/BrokenLatexFileTests.js +++ b/services/clsi/test/acceptance/js/BrokenLatexFileTests.js @@ -10,7 +10,6 @@ */ const Client = require('./helpers/Client') const request = require('request') -require('chai').should() const ClsiApp = require('./helpers/ClsiApp') describe('Broken LaTeX file', function () { diff --git a/services/clsi/test/acceptance/js/DeleteOldFilesTest.js b/services/clsi/test/acceptance/js/DeleteOldFilesTest.js index ae2d7c791e..064f6be67d 100644 --- a/services/clsi/test/acceptance/js/DeleteOldFilesTest.js +++ b/services/clsi/test/acceptance/js/DeleteOldFilesTest.js @@ -10,7 +10,6 @@ */ const Client = require('./helpers/Client') const request = require('request') -require('chai').should() const ClsiApp = require('./helpers/ClsiApp') describe('Deleting Old Files', function () { diff --git a/services/clsi/test/acceptance/js/ExampleDocumentTests.js b/services/clsi/test/acceptance/js/ExampleDocumentTests.js index 5a67a0febc..92bab4b6cc 100644 --- a/services/clsi/test/acceptance/js/ExampleDocumentTests.js +++ b/services/clsi/test/acceptance/js/ExampleDocumentTests.js @@ -17,7 +17,6 @@ */ const Client = require('./helpers/Client') const request = require('request') -require('chai').should() const fs = require('fs') const fsExtra = require('fs-extra') const ChildProcess = require('child_process') diff --git a/services/clsi/test/acceptance/js/SimpleLatexFileTests.js b/services/clsi/test/acceptance/js/SimpleLatexFileTests.js index dd45802692..7377afdaf1 100644 --- a/services/clsi/test/acceptance/js/SimpleLatexFileTests.js +++ b/services/clsi/test/acceptance/js/SimpleLatexFileTests.js @@ -10,7 +10,6 @@ */ const Client = require('./helpers/Client') const request = require('request') -require('chai').should() const ClsiApp = require('./helpers/ClsiApp') describe('Simple LaTeX file', function () { diff --git a/services/clsi/test/acceptance/js/SynctexTests.js b/services/clsi/test/acceptance/js/SynctexTests.js index afe6330df3..e636912bea 100644 --- a/services/clsi/test/acceptance/js/SynctexTests.js +++ b/services/clsi/test/acceptance/js/SynctexTests.js @@ -11,7 +11,6 @@ */ const Client = require('./helpers/Client') const request = require('request') -require('chai').should() const { expect } = require('chai') const ClsiApp = require('./helpers/ClsiApp') const crypto = require('crypto') diff --git a/services/clsi/test/acceptance/js/TimeoutTests.js b/services/clsi/test/acceptance/js/TimeoutTests.js index 36bcf9aa24..0d359bec0a 100644 --- a/services/clsi/test/acceptance/js/TimeoutTests.js +++ b/services/clsi/test/acceptance/js/TimeoutTests.js @@ -10,7 +10,6 @@ */ const Client = require('./helpers/Client') const request = require('request') -require('chai').should() const ClsiApp = require('./helpers/ClsiApp') describe('Timed out compile', function () { diff --git a/services/clsi/test/acceptance/js/UrlCachingTests.js b/services/clsi/test/acceptance/js/UrlCachingTests.js index 03927ea1ef..9528efba95 100644 --- a/services/clsi/test/acceptance/js/UrlCachingTests.js +++ b/services/clsi/test/acceptance/js/UrlCachingTests.js @@ -11,7 +11,6 @@ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ const Client = require('./helpers/Client') -require('chai').should() const sinon = require('sinon') const ClsiApp = require('./helpers/ClsiApp') diff --git a/services/clsi/test/acceptance/js/WordcountTests.js b/services/clsi/test/acceptance/js/WordcountTests.js index 73a11f7693..837eb47a2a 100644 --- a/services/clsi/test/acceptance/js/WordcountTests.js +++ b/services/clsi/test/acceptance/js/WordcountTests.js @@ -11,7 +11,6 @@ */ const Client = require('./helpers/Client') const request = require('request') -require('chai').should() const { expect } = require('chai') const path = require('path') const fs = require('fs') diff --git a/services/clsi/test/setup.js b/services/clsi/test/setup.js new file mode 100644 index 0000000000..87532f0a26 --- /dev/null +++ b/services/clsi/test/setup.js @@ -0,0 +1,19 @@ +const chai = require('chai') +const SandboxedModule = require('sandboxed-module') + +// Setup should interface +chai.should() + +// Global SandboxedModule settings +SandboxedModule.configure({ + requires: { + 'logger-sharelatex': { + log() {}, + info() {}, + warn() {}, + error() {}, + err() {} + } + }, + globals: { Buffer, console, process } +}) diff --git a/services/clsi/test/unit/js/CompileControllerTests.js b/services/clsi/test/unit/js/CompileControllerTests.js index 70f67b602f..d8c7c12609 100644 --- a/services/clsi/test/unit/js/CompileControllerTests.js +++ b/services/clsi/test/unit/js/CompileControllerTests.js @@ -11,7 +11,6 @@ */ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') -require('chai').should() const { expect } = require('chai') const modulePath = require('path').join( __dirname, @@ -32,13 +31,7 @@ describe('CompileController', function () { } } }), - './ProjectPersistenceManager': (this.ProjectPersistenceManager = {}), - 'logger-sharelatex': (this.logger = { - log: sinon.stub(), - error: sinon.stub(), - err: sinon.stub(), - warn: sinon.stub() - }) + './ProjectPersistenceManager': (this.ProjectPersistenceManager = {}) } }) this.Settings.externalUrl = 'http://www.example.com' diff --git a/services/clsi/test/unit/js/CompileManagerTests.js b/services/clsi/test/unit/js/CompileManagerTests.js index 917dfe8cb7..e33783a185 100644 --- a/services/clsi/test/unit/js/CompileManagerTests.js +++ b/services/clsi/test/unit/js/CompileManagerTests.js @@ -15,7 +15,6 @@ */ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') -require('chai').should() const modulePath = require('path').join( __dirname, '../../../app/js/CompileManager' @@ -47,7 +46,6 @@ describe('CompileManager', function () { } }), - 'logger-sharelatex': (this.logger = { log: sinon.stub(), info() {} }), child_process: (this.child_process = {}), './CommandRunner': (this.CommandRunner = {}), './DraftModeManager': (this.DraftModeManager = {}), diff --git a/services/clsi/test/unit/js/ContentTypeMapperTests.js b/services/clsi/test/unit/js/ContentTypeMapperTests.js index 21173bc526..b6363623d2 100644 --- a/services/clsi/test/unit/js/ContentTypeMapperTests.js +++ b/services/clsi/test/unit/js/ContentTypeMapperTests.js @@ -12,7 +12,6 @@ */ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') -require('chai').should() const modulePath = require('path').join( __dirname, '../../../app/js/ContentTypeMapper' diff --git a/services/clsi/test/unit/js/DockerLockManagerTests.js b/services/clsi/test/unit/js/DockerLockManagerTests.js index 19c289a128..6adfa1231a 100644 --- a/services/clsi/test/unit/js/DockerLockManagerTests.js +++ b/services/clsi/test/unit/js/DockerLockManagerTests.js @@ -11,7 +11,6 @@ */ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') -require('chai').should() const modulePath = require('path').join( __dirname, '../../../app/js/DockerLockManager' @@ -21,11 +20,7 @@ describe('LockManager', function () { beforeEach(function () { return (this.LockManager = SandboxedModule.require(modulePath, { requires: { - 'settings-sharelatex': (this.Settings = { clsi: { docker: {} } }), - 'logger-sharelatex': (this.logger = { - log: sinon.stub(), - error: sinon.stub() - }) + 'settings-sharelatex': (this.Settings = { clsi: { docker: {} } }) } })) }) diff --git a/services/clsi/test/unit/js/DockerRunnerTests.js b/services/clsi/test/unit/js/DockerRunnerTests.js index 517179a962..67290e440d 100644 --- a/services/clsi/test/unit/js/DockerRunnerTests.js +++ b/services/clsi/test/unit/js/DockerRunnerTests.js @@ -15,7 +15,6 @@ */ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') -require('chai').should() const { expect } = require('chai') const modulePath = require('path').join( __dirname, @@ -33,12 +32,6 @@ describe('DockerRunner', function () { clsi: { docker: {} }, path: {} }), - 'logger-sharelatex': (this.logger = { - log: sinon.stub(), - error: sinon.stub(), - info: sinon.stub(), - warn: sinon.stub() - }), dockerode: (Docker = (function () { Docker = class Docker { static initClass() { diff --git a/services/clsi/test/unit/js/DraftModeManagerTests.js b/services/clsi/test/unit/js/DraftModeManagerTests.js index 937cde90d9..13291e0262 100644 --- a/services/clsi/test/unit/js/DraftModeManagerTests.js +++ b/services/clsi/test/unit/js/DraftModeManagerTests.js @@ -10,7 +10,6 @@ */ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') -require('chai').should() const modulePath = require('path').join( __dirname, '../../../app/js/DraftModeManager' @@ -20,8 +19,7 @@ describe('DraftModeManager', function () { beforeEach(function () { return (this.DraftModeManager = SandboxedModule.require(modulePath, { requires: { - fs: (this.fs = {}), - 'logger-sharelatex': (this.logger = { log() {} }) + fs: (this.fs = {}) } })) }) diff --git a/services/clsi/test/unit/js/LatexRunnerTests.js b/services/clsi/test/unit/js/LatexRunnerTests.js index 9cd0d0ac95..a9116d0fd7 100644 --- a/services/clsi/test/unit/js/LatexRunnerTests.js +++ b/services/clsi/test/unit/js/LatexRunnerTests.js @@ -11,7 +11,6 @@ */ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') -require('chai').should() const modulePath = require('path').join( __dirname, '../../../app/js/LatexRunner' @@ -28,10 +27,6 @@ describe('LatexRunner', function () { socketPath: '/var/run/docker.sock' } }), - 'logger-sharelatex': (this.logger = { - log: sinon.stub(), - error: sinon.stub() - }), './Metrics': { Timer: (Timer = class Timer { done() {} diff --git a/services/clsi/test/unit/js/LockManagerTests.js b/services/clsi/test/unit/js/LockManagerTests.js index 16a43ade48..58580583ca 100644 --- a/services/clsi/test/unit/js/LockManagerTests.js +++ b/services/clsi/test/unit/js/LockManagerTests.js @@ -11,7 +11,6 @@ */ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') -require('chai').should() const modulePath = require('path').join( __dirname, '../../../app/js/LockManager' @@ -24,11 +23,6 @@ describe('DockerLockManager', function () { this.LockManager = SandboxedModule.require(modulePath, { requires: { 'settings-sharelatex': {}, - 'logger-sharelatex': (this.logger = { - log: sinon.stub(), - error: sinon.stub(), - err() {} - }), fs: { lstat: sinon.stub().callsArgWith(1), readdir: sinon.stub().callsArgWith(1) diff --git a/services/clsi/test/unit/js/OutputFileFinderTests.js b/services/clsi/test/unit/js/OutputFileFinderTests.js index 8744c6e779..d094f7c66d 100644 --- a/services/clsi/test/unit/js/OutputFileFinderTests.js +++ b/services/clsi/test/unit/js/OutputFileFinderTests.js @@ -12,7 +12,6 @@ */ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') -require('chai').should() const modulePath = require('path').join( __dirname, '../../../app/js/OutputFileFinder' @@ -26,8 +25,7 @@ describe('OutputFileFinder', function () { this.OutputFileFinder = SandboxedModule.require(modulePath, { requires: { fs: (this.fs = {}), - child_process: { spawn: (this.spawn = sinon.stub()) }, - 'logger-sharelatex': { log: sinon.stub(), warn: sinon.stub() } + child_process: { spawn: (this.spawn = sinon.stub()) } }, globals: { Math // used by lodash diff --git a/services/clsi/test/unit/js/OutputFileOptimiserTests.js b/services/clsi/test/unit/js/OutputFileOptimiserTests.js index e7f7b8b0c8..4c78666084 100644 --- a/services/clsi/test/unit/js/OutputFileOptimiserTests.js +++ b/services/clsi/test/unit/js/OutputFileOptimiserTests.js @@ -12,7 +12,6 @@ */ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') -require('chai').should() const modulePath = require('path').join( __dirname, '../../../app/js/OutputFileOptimiser' @@ -28,7 +27,6 @@ describe('OutputFileOptimiser', function () { fs: (this.fs = {}), path: (this.Path = {}), child_process: { spawn: (this.spawn = sinon.stub()) }, - 'logger-sharelatex': { log: sinon.stub(), warn: sinon.stub() }, './Metrics': {} }, globals: { Math } // used by lodash diff --git a/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js b/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js index 5e4368fd40..5fb1827206 100644 --- a/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js +++ b/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js @@ -13,7 +13,6 @@ */ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') -require('chai').should() const assert = require('chai').assert const modulePath = require('path').join( __dirname, @@ -28,11 +27,6 @@ describe('ProjectPersistenceManager', function () { './UrlCache': (this.UrlCache = {}), './CompileManager': (this.CompileManager = {}), diskusage: (this.diskusage = { check: sinon.stub() }), - 'logger-sharelatex': (this.logger = { - log: sinon.stub(), - warn: sinon.stub(), - err: sinon.stub() - }), 'settings-sharelatex': (this.settings = { project_cache_length_ms: 1000 }), diff --git a/services/clsi/test/unit/js/RequestParserTests.js b/services/clsi/test/unit/js/RequestParserTests.js index 6e6c922571..ff2ef7a9d1 100644 --- a/services/clsi/test/unit/js/RequestParserTests.js +++ b/services/clsi/test/unit/js/RequestParserTests.js @@ -11,7 +11,6 @@ */ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') -require('chai').should() const { expect } = require('chai') const modulePath = require('path').join( __dirname, diff --git a/services/clsi/test/unit/js/ResourceStateManagerTests.js b/services/clsi/test/unit/js/ResourceStateManagerTests.js index e2bc073844..1536299c2d 100644 --- a/services/clsi/test/unit/js/ResourceStateManagerTests.js +++ b/services/clsi/test/unit/js/ResourceStateManagerTests.js @@ -12,7 +12,6 @@ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') const { expect } = require('chai') -const should = require('chai').should() const modulePath = require('path').join( __dirname, '../../../app/js/ResourceStateManager' @@ -26,7 +25,6 @@ describe('ResourceStateManager', function () { singleOnly: true, requires: { fs: (this.fs = {}), - 'logger-sharelatex': { log: sinon.stub(), err: sinon.stub() }, './SafeReader': (this.SafeReader = {}) } }) diff --git a/services/clsi/test/unit/js/ResourceWriterTests.js b/services/clsi/test/unit/js/ResourceWriterTests.js index 030fe70eb2..267fc2c3a8 100644 --- a/services/clsi/test/unit/js/ResourceWriterTests.js +++ b/services/clsi/test/unit/js/ResourceWriterTests.js @@ -13,7 +13,6 @@ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') const { expect } = require('chai') -const should = require('chai').should() const modulePath = require('path').join( __dirname, '../../../app/js/ResourceWriter' @@ -34,7 +33,6 @@ describe('ResourceWriter', function () { wrench: (this.wrench = {}), './UrlCache': (this.UrlCache = {}), './OutputFileFinder': (this.OutputFileFinder = {}), - 'logger-sharelatex': { log: sinon.stub(), err: sinon.stub() }, './Metrics': (this.Metrics = { inc: sinon.stub(), Timer: (Timer = (function () { @@ -407,7 +405,7 @@ describe('ResourceWriter', function () { }) return it('should not return an error if the resource writer errored', function () { - return should.not.exist(this.callback.args[0][0]) + return expect(this.callback.args[0][0]).not.to.exist }) }) diff --git a/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js b/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js index 9a72168f1f..65a66f3dc3 100644 --- a/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js +++ b/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js @@ -9,7 +9,6 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const should = require('chai').should() const SandboxedModule = require('sandboxed-module') const assert = require('assert') const path = require('path') @@ -32,11 +31,6 @@ describe('StaticServerForbidSymlinks', function () { this.ForbidSymlinks = SandboxedModule.require(modulePath, { requires: { 'settings-sharelatex': this.settings, - 'logger-sharelatex': { - log() {}, - warn() {}, - error() {} - }, fs: this.fs } }) diff --git a/services/clsi/test/unit/js/TikzManager.js b/services/clsi/test/unit/js/TikzManager.js index 4304edc028..30a3807552 100644 --- a/services/clsi/test/unit/js/TikzManager.js +++ b/services/clsi/test/unit/js/TikzManager.js @@ -10,7 +10,6 @@ */ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') -require('chai').should() const modulePath = require('path').join( __dirname, '../../../app/js/TikzManager' @@ -22,8 +21,7 @@ describe('TikzManager', function () { requires: { './ResourceWriter': (this.ResourceWriter = {}), './SafeReader': (this.SafeReader = {}), - fs: (this.fs = {}), - 'logger-sharelatex': (this.logger = { log() {} }) + fs: (this.fs = {}) } })) }) diff --git a/services/clsi/test/unit/js/UrlCacheTests.js b/services/clsi/test/unit/js/UrlCacheTests.js index 023b92de51..2b991245a9 100644 --- a/services/clsi/test/unit/js/UrlCacheTests.js +++ b/services/clsi/test/unit/js/UrlCacheTests.js @@ -12,7 +12,6 @@ */ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') -require('chai').should() const modulePath = require('path').join(__dirname, '../../../app/js/UrlCache') const { EventEmitter } = require('events') @@ -25,7 +24,6 @@ describe('UrlCache', function () { requires: { './db': {}, './UrlFetcher': (this.UrlFetcher = {}), - 'logger-sharelatex': (this.logger = { log: sinon.stub() }), 'settings-sharelatex': (this.Settings = { path: { clsiCacheDir: '/cache/dir' } }), diff --git a/services/clsi/test/unit/js/UrlFetcherTests.js b/services/clsi/test/unit/js/UrlFetcherTests.js index 96ef457433..238e5d8aa9 100644 --- a/services/clsi/test/unit/js/UrlFetcherTests.js +++ b/services/clsi/test/unit/js/UrlFetcherTests.js @@ -11,7 +11,6 @@ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') const { expect } = require('chai') -require('chai').should() const modulePath = require('path').join(__dirname, '../../../app/js/UrlFetcher') const { EventEmitter } = require('events') @@ -25,10 +24,6 @@ describe('UrlFetcher', function () { defaults: (this.defaults = sinon.stub().returns((this.request = {}))) }, fs: (this.fs = {}), - 'logger-sharelatex': (this.logger = { - log: sinon.stub(), - error: sinon.stub() - }), 'settings-sharelatex': (this.settings = {}) } })) From 3eca5053191c017eb65ec2958df17060f40a5ded Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <eric.mcsween@overleaf.com> Date: Fri, 12 Mar 2021 15:00:19 -0500 Subject: [PATCH 680/754] Upgrade to Node 12 --- services/clsi/.dockerignore | 1 + services/clsi/.nvmrc | 2 +- services/clsi/Dockerfile | 4 ++-- services/clsi/Makefile | 6 ++++-- services/clsi/buildscript.txt | 6 +++--- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/services/clsi/.dockerignore b/services/clsi/.dockerignore index 74fdc35e80..bcbd758418 100644 --- a/services/clsi/.dockerignore +++ b/services/clsi/.dockerignore @@ -8,3 +8,4 @@ nodemon.json cache/ compiles/ db/ +output/ diff --git a/services/clsi/.nvmrc b/services/clsi/.nvmrc index 2baa2d433a..e68b860383 100644 --- a/services/clsi/.nvmrc +++ b/services/clsi/.nvmrc @@ -1 +1 @@ -10.23.1 +12.21.0 diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 7eaa03f814..b02e828a4d 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -FROM node:10.23.1 as base +FROM node:12.21.0 as base WORKDIR /app COPY install_deps.sh /app @@ -23,6 +23,6 @@ FROM base COPY --from=app /app /app RUN mkdir -p cache compiles db output \ - && chown node:node cache compiles db output +&& chown node:node cache compiles db output CMD ["node", "--expose-gc", "app.js"] diff --git a/services/clsi/Makefile b/services/clsi/Makefile index 040a9315e3..45a4bc0400 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -21,8 +21,10 @@ DOCKER_COMPOSE_TEST_UNIT = \ COMPOSE_PROJECT_NAME=test_unit_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) clean: - docker rmi ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) - docker rmi gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) + -docker rmi ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) + -docker rmi gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) + -$(DOCKER_COMPOSE_TEST_UNIT) down --rmi local + -$(DOCKER_COMPOSE_TEST_ACCEPTANCE) down --rmi local format: $(DOCKER_COMPOSE) run --rm test_unit npm run --silent format diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index b2093150c1..a86911dc61 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -1,9 +1,9 @@ clsi ---data-dirs=cache,compiles,db +--data-dirs=cache,compiles,db,output --dependencies= --docker-repos=gcr.io/overleaf-ops --env-add= --env-pass-through=TEXLIVE_IMAGE ---node-version=10.23.1 +--node-version=12.21.0 --public-repo=True ---script-version=3.4.0 +--script-version=3.7.0 From 7cd36e139a173f7a860b4c425a4ecf2b4f072540 Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Tue, 16 Mar 2021 10:24:59 +0000 Subject: [PATCH 681/754] support post and get for getting status of clsi project --- services/clsi/app.js | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/app.js b/services/clsi/app.js index aee8d44832..f24ce5f924 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -87,6 +87,7 @@ app.get('/project/:project_id/sync/code', CompileController.syncFromCode) app.get('/project/:project_id/sync/pdf', CompileController.syncFromPdf) app.get('/project/:project_id/wordcount', CompileController.wordcount) app.get('/project/:project_id/status', CompileController.status) +app.post('/project/:project_id/status', CompileController.status) // Per-user containers app.post( From 17a83bc1698f5dc58d48a3fa3aa67094337e2868 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Tue, 16 Mar 2021 12:00:48 +0000 Subject: [PATCH 682/754] Expand list of environment variables --- services/clsi/README.md | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/services/clsi/README.md b/services/clsi/README.md index c4c1eabf0e..6f67355ba0 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -15,17 +15,29 @@ The provided `Dockerfile` builds a docker image which has the docker command lin The CLSI can be configured through the following environment variables: - * `DOCKER_RUNNER` - Set to true to use sibling containers - * `SYNCTEX_BIN_HOST_PATH` - Path to SyncTeX binary - * `COMPILES_HOST_DIR` - Working directory for LaTeX compiles - * `SQLITE_PATH` - Path to SQLite database - * `TEXLIVE_IMAGE` - The TEXLIVE docker image to use for sibling containers, e.g. `gcr.io/overleaf-ops/texlive-full:2017.1` - * `TEXLIVE_IMAGE_USER` - When using sibling containers, the user to run as in the TEXLIVE image. Defaults to `tex` - * `TEX_LIVE_IMAGE_NAME_OVERRIDE` - The name of the registry for the docker image e.g. `gcr.io/overleaf-ops` - * `FILESTORE_DOMAIN_OVERRIDE` - The url for the filestore service e.g.`http://$FILESTORE_HOST:3009` - * `STATSD_HOST` - The address of the Statsd service (used by the metrics module) - * `LISTEN_ADDRESS` - The address for the RESTful service to listen on. Set to `0.0.0.0` to listen on all network interfaces - * `SMOKE_TEST` - Whether to run smoke tests +* `ALLOWED_COMPILE_GROUPS` +* `ALLOWED_IMAGES` +* `CATCH_ERRORS` +* `COMPILE_GROUP_DOCKER_CONFIGS` +* `COMPILES_HOST_DIR` - Working directory for LaTeX compiles +* `COMPILE_SIZE_LIMIT` +* `DOCKER_RUNNER` - Set to true to use sibling containers +* `DOCKER_RUNTIME` +* `FILESTORE_DOMAIN_OVERRIDE` - The url for the filestore service e.g.`http://$FILESTORE_HOST:3009` +* `FILESTORE_PARALLEL_FILE_DOWNLOADS` +* `FILESTORE_PARALLEL_SQL_QUERY_LIMIT` +* `LISTEN_ADDRESS` - The address for the RESTful service to listen on. Set to `0.0.0.0` to listen on all network interfaces +* `PROCESS_LIFE_SPAN_LIMIT_MS` +* `SENTRY_DSN` +* `SMOKE_TEST` - Whether to run smoke tests +* `SQLITE_PATH` - Path to SQLite database +* `SYNCTEX_BIN_HOST_PATH` - Path to SyncTeX binary +* `TEXLIVE_IMAGE` - The TEXLIVE docker image to use for sibling containers, e.g. `gcr.io/overleaf-ops/texlive-full:2017.1` +* `TEX_LIVE_IMAGE_NAME_OVERRIDE` - The name of the registry for the docker image e.g. `gcr.io/overleaf-ops` +* `TEXLIVE_IMAGE_USER` - When using sibling containers, the user to run as in the TEXLIVE image. Defaults to `tex` +* `TEXLIVE_OPENOUT_ANY` + +Further environment variables configure the [metrics module](https://github.com/overleaf/metrics-module) Installation ------------ From 0a89b6537c17161333b903f8d343bf26218488ad Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Tue, 16 Mar 2021 12:30:06 +0000 Subject: [PATCH 683/754] Fill in missing text for environment variables --- services/clsi/README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/services/clsi/README.md b/services/clsi/README.md index 6f67355ba0..b82efa2be4 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -15,27 +15,27 @@ The provided `Dockerfile` builds a docker image which has the docker command lin The CLSI can be configured through the following environment variables: -* `ALLOWED_COMPILE_GROUPS` -* `ALLOWED_IMAGES` -* `CATCH_ERRORS` -* `COMPILE_GROUP_DOCKER_CONFIGS` +* `ALLOWED_COMPILE_GROUPS` - Space separated list of allowed compile groups +* `ALLOWED_IMAGES` - Space separated list of allowed Docker TeX Live images +* `CATCH_ERRORS` - Set to `true` to log uncaught exceptions +* `COMPILE_GROUP_DOCKER_CONFIGS` - JSON string of Docker configs for compile groups * `COMPILES_HOST_DIR` - Working directory for LaTeX compiles -* `COMPILE_SIZE_LIMIT` +* `COMPILE_SIZE_LIMIT` - Sets the body-parser [limit](https://github.com/expressjs/body-parser#limit) * `DOCKER_RUNNER` - Set to true to use sibling containers -* `DOCKER_RUNTIME` +* `DOCKER_RUNTIME` - * `FILESTORE_DOMAIN_OVERRIDE` - The url for the filestore service e.g.`http://$FILESTORE_HOST:3009` -* `FILESTORE_PARALLEL_FILE_DOWNLOADS` -* `FILESTORE_PARALLEL_SQL_QUERY_LIMIT` +* `FILESTORE_PARALLEL_FILE_DOWNLOADS` - Number of parallel file downloads +* `FILESTORE_PARALLEL_SQL_QUERY_LIMIT` - Number of parallel SQL queries * `LISTEN_ADDRESS` - The address for the RESTful service to listen on. Set to `0.0.0.0` to listen on all network interfaces -* `PROCESS_LIFE_SPAN_LIMIT_MS` -* `SENTRY_DSN` +* `PROCESS_LIFE_SPAN_LIMIT_MS` - Process life span limit in milliseconds +* `SENTRY_DSN` - Sentry [Data Source Name](https://docs.sentry.io/product/sentry-basics/dsn-explainer/) * `SMOKE_TEST` - Whether to run smoke tests * `SQLITE_PATH` - Path to SQLite database * `SYNCTEX_BIN_HOST_PATH` - Path to SyncTeX binary * `TEXLIVE_IMAGE` - The TEXLIVE docker image to use for sibling containers, e.g. `gcr.io/overleaf-ops/texlive-full:2017.1` * `TEX_LIVE_IMAGE_NAME_OVERRIDE` - The name of the registry for the docker image e.g. `gcr.io/overleaf-ops` * `TEXLIVE_IMAGE_USER` - When using sibling containers, the user to run as in the TEXLIVE image. Defaults to `tex` -* `TEXLIVE_OPENOUT_ANY` +* `TEXLIVE_OPENOUT_ANY` - Sets the `openout_any` environment variable for Tex Live Further environment variables configure the [metrics module](https://github.com/overleaf/metrics-module) From b8d0389a53d30c9ed20d8c18f726b5315a2f9ce0 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Tue, 16 Mar 2021 15:29:53 +0000 Subject: [PATCH 684/754] README typos --- services/clsi/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/services/clsi/README.md b/services/clsi/README.md index b82efa2be4..54f6e0a820 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -11,7 +11,7 @@ The Common LaTeX Service Interface (CLSI) provides a RESTful interface to tradit These defaults can be modified in `config/settings.defaults.js`. -The provided `Dockerfile` builds a docker image which has the docker command line tools installed. The configuration in `docker-compose-config.yml` mounts the docker socket, in order that the CLSI container can talk to the docker host it is running in. This allows it to spin up `sibling containers` running an image with a TeX distribution installed to perform the actual compiles. +The provided `Dockerfile` builds a Docker image which has the Docker command line tools installed. The configuration in `docker-compose-config.yml` mounts the Docker socket, in order that the CLSI container can talk to the Docker host it is running in. This allows it to spin up `sibling containers` running an image with a TeX distribution installed to perform the actual compiles. The CLSI can be configured through the following environment variables: @@ -32,10 +32,10 @@ The CLSI can be configured through the following environment variables: * `SMOKE_TEST` - Whether to run smoke tests * `SQLITE_PATH` - Path to SQLite database * `SYNCTEX_BIN_HOST_PATH` - Path to SyncTeX binary -* `TEXLIVE_IMAGE` - The TEXLIVE docker image to use for sibling containers, e.g. `gcr.io/overleaf-ops/texlive-full:2017.1` -* `TEX_LIVE_IMAGE_NAME_OVERRIDE` - The name of the registry for the docker image e.g. `gcr.io/overleaf-ops` -* `TEXLIVE_IMAGE_USER` - When using sibling containers, the user to run as in the TEXLIVE image. Defaults to `tex` -* `TEXLIVE_OPENOUT_ANY` - Sets the `openout_any` environment variable for Tex Live +* `TEXLIVE_IMAGE` - The TeX Live Docker image to use for sibling containers, e.g. `gcr.io/overleaf-ops/texlive-full:2017.1` +* `TEX_LIVE_IMAGE_NAME_OVERRIDE` - The name of the registry for the Docker image e.g. `gcr.io/overleaf-ops` +* `TEXLIVE_IMAGE_USER` - When using sibling containers, the user to run as in the TeX Live image. Defaults to `tex` +* `TEXLIVE_OPENOUT_ANY` - Sets the `openout_any` environment variable for TeX Live Further environment variables configure the [metrics module](https://github.com/overleaf/metrics-module) @@ -50,7 +50,7 @@ Then build the Docker image: $ docker build . -t overleaf/clsi -Then pull the TeXLive image: +Then pull the TeX Live image: $ docker pull texlive/texlive From 984766a9fb1e679e9ef2881bbae5ac7cb530c96a Mon Sep 17 00:00:00 2001 From: Alf Eaton <alf.eaton@overleaf.com> Date: Thu, 18 Mar 2021 09:55:31 +0000 Subject: [PATCH 685/754] Make TEXLIVE_IMAGE_USER instruction macOS only --- services/clsi/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/clsi/README.md b/services/clsi/README.md index 54f6e0a820..90dee749a9 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -61,14 +61,13 @@ Then start the Docker container: -e LISTEN_ADDRESS=0.0.0.0 \ -e DOCKER_RUNNER=true \ -e TEXLIVE_IMAGE=texlive/texlive \ - -e TEXLIVE_IMAGE_USER=root \ -e COMPILES_HOST_DIR="$PWD/compiles" \ -v "$PWD/compiles:/app/compiles" \ -v "$PWD/cache:/app/cache" \ -v /var/run/docker.sock:/var/run/docker.sock \ overleaf/clsi -Note: if you're running the CLSI in macOS you may need to use `-v /var/run/docker.sock.raw:/var/run/docker.sock` instead. +Note: if you're running the CLSI in macOS you may need to add `-e TEXLIVE_IMAGE_USER=root` and use `-v /var/run/docker.sock.raw:/var/run/docker.sock` instead. The CLSI should then be running at <http://localhost:3013> From 638e72295be476de7cbc44e1e916bbe30bbe0cb4 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Fri, 19 Mar 2021 10:34:52 +0000 Subject: [PATCH 686/754] Add link to \openout primitive docs --- services/clsi/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/README.md b/services/clsi/README.md index 54f6e0a820..e822d92e2c 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -35,7 +35,7 @@ The CLSI can be configured through the following environment variables: * `TEXLIVE_IMAGE` - The TeX Live Docker image to use for sibling containers, e.g. `gcr.io/overleaf-ops/texlive-full:2017.1` * `TEX_LIVE_IMAGE_NAME_OVERRIDE` - The name of the registry for the Docker image e.g. `gcr.io/overleaf-ops` * `TEXLIVE_IMAGE_USER` - When using sibling containers, the user to run as in the TeX Live image. Defaults to `tex` -* `TEXLIVE_OPENOUT_ANY` - Sets the `openout_any` environment variable for TeX Live +* `TEXLIVE_OPENOUT_ANY` - Sets the `openout_any` environment variable for TeX Live (see the `\openout` primitive [documentation](http://tug.org/texinfohtml/web2c.html#tex-invocation)) Further environment variables configure the [metrics module](https://github.com/overleaf/metrics-module) From 072d0a9dabef6e1393843e36fcac98543b4fe0e4 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly <shane.kilkelly@overleaf.com> Date: Fri, 19 Mar 2021 11:35:59 +0000 Subject: [PATCH 687/754] Add flag to qpdf, to preserve PDF/A compliance --- services/clsi/app/js/OutputFileOptimiser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/js/OutputFileOptimiser.js b/services/clsi/app/js/OutputFileOptimiser.js index 852737620f..0b835a42c3 100644 --- a/services/clsi/app/js/OutputFileOptimiser.js +++ b/services/clsi/app/js/OutputFileOptimiser.js @@ -71,7 +71,7 @@ module.exports = OutputFileOptimiser = { callback = function (error) {} } const tmpOutput = dst + '.opt' - const args = ['--linearize', src, tmpOutput] + const args = ['--linearize', '--newline-before-endstream', src, tmpOutput] logger.log({ args }, 'running qpdf command') const timer = new Metrics.Timer('qpdf') From d4b4cb399a4681440ed7eee076d5a2d57161259f Mon Sep 17 00:00:00 2001 From: Alf Eaton <alf.eaton@overleaf.com> Date: Fri, 19 Mar 2021 12:00:47 +0000 Subject: [PATCH 688/754] Revert "Make TEXLIVE_IMAGE_USER instruction macOS only" This reverts commit ab6fe228cadd3329885fdc60aea867d601579759. --- services/clsi/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/clsi/README.md b/services/clsi/README.md index 416d773f0a..e822d92e2c 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -61,13 +61,14 @@ Then start the Docker container: -e LISTEN_ADDRESS=0.0.0.0 \ -e DOCKER_RUNNER=true \ -e TEXLIVE_IMAGE=texlive/texlive \ + -e TEXLIVE_IMAGE_USER=root \ -e COMPILES_HOST_DIR="$PWD/compiles" \ -v "$PWD/compiles:/app/compiles" \ -v "$PWD/cache:/app/cache" \ -v /var/run/docker.sock:/var/run/docker.sock \ overleaf/clsi -Note: if you're running the CLSI in macOS you may need to add `-e TEXLIVE_IMAGE_USER=root` and use `-v /var/run/docker.sock.raw:/var/run/docker.sock` instead. +Note: if you're running the CLSI in macOS you may need to use `-v /var/run/docker.sock.raw:/var/run/docker.sock` instead. The CLSI should then be running at <http://localhost:3013> From 7e3edcee2c19012d9d25a85cca79c02abbf498b4 Mon Sep 17 00:00:00 2001 From: Alf Eaton <alf.eaton@overleaf.com> Date: Fri, 19 Mar 2021 12:05:22 +0000 Subject: [PATCH 689/754] Add instructions for Linux --- services/clsi/README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/services/clsi/README.md b/services/clsi/README.md index e822d92e2c..d8e189dc58 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -16,13 +16,13 @@ The provided `Dockerfile` builds a Docker image which has the Docker command lin The CLSI can be configured through the following environment variables: * `ALLOWED_COMPILE_GROUPS` - Space separated list of allowed compile groups -* `ALLOWED_IMAGES` - Space separated list of allowed Docker TeX Live images +* `ALLOWED_IMAGES` - Space separated list of allowed Docker TeX Live images * `CATCH_ERRORS` - Set to `true` to log uncaught exceptions * `COMPILE_GROUP_DOCKER_CONFIGS` - JSON string of Docker configs for compile groups * `COMPILES_HOST_DIR` - Working directory for LaTeX compiles * `COMPILE_SIZE_LIMIT` - Sets the body-parser [limit](https://github.com/expressjs/body-parser#limit) * `DOCKER_RUNNER` - Set to true to use sibling containers -* `DOCKER_RUNTIME` - +* `DOCKER_RUNTIME` - * `FILESTORE_DOMAIN_OVERRIDE` - The url for the filestore service e.g.`http://$FILESTORE_HOST:3009` * `FILESTORE_PARALLEL_FILE_DOWNLOADS` - Number of parallel file downloads * `FILESTORE_PARALLEL_SQL_QUERY_LIMIT` - Number of parallel SQL queries @@ -70,6 +70,13 @@ Then start the Docker container: Note: if you're running the CLSI in macOS you may need to use `-v /var/run/docker.sock.raw:/var/run/docker.sock` instead. +Note: if you're running the CLSI in Linux you may need to adjust the permissions of the `compiles` folder to match your local user: + +```shell +sudo chown -R $(whoami):root compiles/ +sudo chmod g+w -R compiles/ +``` + The CLSI should then be running at <http://localhost:3013> Config From 6ca016bb050d909162eff6e74461185587888531 Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <christopher.hoskin@gmail.com> Date: Fri, 19 Mar 2021 14:32:54 +0000 Subject: [PATCH 690/754] Explain the situation with permissions on Linux --- services/clsi/README.md | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/services/clsi/README.md b/services/clsi/README.md index d8e189dc58..302cb34932 100644 --- a/services/clsi/README.md +++ b/services/clsi/README.md @@ -66,18 +66,45 @@ Then start the Docker container: -v "$PWD/compiles:/app/compiles" \ -v "$PWD/cache:/app/cache" \ -v /var/run/docker.sock:/var/run/docker.sock \ + --name clsi \ overleaf/clsi Note: if you're running the CLSI in macOS you may need to use `-v /var/run/docker.sock.raw:/var/run/docker.sock` instead. -Note: if you're running the CLSI in Linux you may need to adjust the permissions of the `compiles` folder to match your local user: +The CLSI should then be running at <http://localhost:3013> -```shell -sudo chown -R $(whoami):root compiles/ -sudo chmod g+w -R compiles/ +Important note for Linux users +============================== + +The Node application runs as user `node` in the CLSI, which has uid `1000`. As a consequence of this, the `compiles` folder gets created on your host with `uid` and `gid` set to `1000`. +``` +ls -lnd compiles +drwxr-xr-x 2 1000 1000 4096 Mar 19 12:41 compiles ``` -The CLSI should then be running at <http://localhost:3013> +If there is a user/group on your host which also happens to have `uid` / `gid` `1000` then that user/group will have ownership of the compiles folder on your host. + +LaTeX runs in the sibling containers as the user specified in the `TEXLIVE_IMAGE_USER` environment variable. In the example above this is set to `root`, which has uid `0`. This creates a problem with the above permissions, as the root user does not have permission to write to subfolders of `compiles`. + +A quick fix is to give the `root` group ownership and read write permissions to `compiles`, with `setgid` set so that new subfolders also inherit this ownership: +``` +sudo chown -R 1000:root compiles +sudo chmod -R g+w compiles +sudo chmod g+s compiles +``` +Another solution is to create a `sharelatex` group and add both `root` and the user with `uid` `1000` to it. If the host does not have a user with that `uid`, you will need to create one first. +``` +sudo useradd --uid 1000 host-node-user # If required +sudo groupadd sharelatex +sudo usermod -a -G sharelatex root +sudo usermod -a -G sharelatex $(id -nu 1000) +sudo chown -R 1000:sharelatex compiles +sudo chmod -R g+w compiles +sudo chmod g+s compiles +``` + +This is a facet of the way docker works on Linux. See this [upstream issue](https://github.com/moby/moby/issues/7198) + Config ------ From b563deef14b76bd4b9ea482f97763b433be2d7cb Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 16 Feb 2021 15:10:09 +0000 Subject: [PATCH 691/754] [misc] bump the version of the metrics module to 3.5.1 --- services/clsi/package-lock.json | 1091 ++++++++++++++++++++----------- services/clsi/package.json | 4 +- 2 files changed, 716 insertions(+), 379 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index 145ec532bf..a4fb21c0f9 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -215,6 +215,202 @@ } } }, + "@google-cloud/debug-agent": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@google-cloud/debug-agent/-/debug-agent-5.1.3.tgz", + "integrity": "sha512-WbzeEz4MvPlM7DX2QBsPcWgF62u7LSQv/oMYPl0L+TddTebqjDKiVXwxpzWk61NIfcKiet3dyCbPIt3N5o8XPQ==", + "requires": { + "@google-cloud/common": "^3.0.0", + "acorn": "^8.0.0", + "coffeescript": "^2.0.0", + "console-log-level": "^1.4.0", + "extend": "^3.0.2", + "findit2": "^2.2.3", + "gcp-metadata": "^4.0.0", + "p-limit": "^3.0.1", + "semver": "^7.0.0", + "source-map": "^0.6.1", + "split": "^1.0.0" + }, + "dependencies": { + "@google-cloud/common": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.5.0.tgz", + "integrity": "sha512-10d7ZAvKhq47L271AqvHEd8KzJqGU45TY+rwM2Z3JHuB070FeTi7oJJd7elfrnKaEvaktw3hH2wKnRWxk/3oWQ==", + "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.1.1", + "retry-request": "^4.1.1", + "teeny-request": "^7.0.0" + } + }, + "@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.3", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.3.tgz", + "integrity": "sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw==" + }, + "acorn": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.0.5.tgz", + "integrity": "sha512-v+DieK/HJkJOpFBETDJioequtc3PfxsWMaxIdIwujtF7FEV/MAyDQLlm6/zPvr7Mix07mLh6ccVwIsloceodlg==" + }, + "bignumber.js": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", + "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==" + }, + "coffeescript": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.5.1.tgz", + "integrity": "sha512-J2jRPX0eeFh5VKyVnoLrfVFgLZtnnmp96WQSLAS8OrLm2wtQLcnikYKe1gViJKDH7vucjuhHvBKKBP3rKcD1tQ==" + }, + "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" + } + }, + "gaxios": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.1.0.tgz", + "integrity": "sha512-vb0to8xzGnA2qcgywAjtshOKKVDf2eQhJoiL6fHhgW5tVN7wNk7egnYIO9zotfn3lQ3De1VPdf7V5/BWfCtCmg==", + "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.2.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.1.tgz", + "integrity": "sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw==", + "requires": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + } + }, + "google-auth-library": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", + "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + }, + "google-p12-pem": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", + "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", + "requires": { + "node-forge": "^0.10.0" + } + }, + "gtoken": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.2.1.tgz", + "integrity": "sha512-OY0BfPKe3QnMsY9MzTHTSKn+Vl2l1CcLe6BwDEQj00mbbkl5nyQ/7EUREstg4fQNZ8iYE7br4JJ7TdKeDOPWmw==", + "requires": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.0.3", + "jws": "^4.0.0" + } + }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "requires": { + "yocto-queue": "^0.1.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" + } + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "teeny-request": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.0.1.tgz", + "integrity": "sha512-sasJmQ37klOlplL4Ia/786M5YlOcoLGQyq2TE4WHSRupbAuDaQW0PfVxV4MtdBtRJ4ngzS+1qim8zP6Zp35qCw==", + "requires": { + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^8.0.0" + } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, "@google-cloud/logging": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/@google-cloud/logging/-/logging-7.3.0.tgz", @@ -301,6 +497,216 @@ "extend": "^3.0.2" } }, + "@google-cloud/profiler": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@google-cloud/profiler/-/profiler-4.1.0.tgz", + "integrity": "sha512-9e1zXRctLSUHAoAsFGwE4rS28fr0siiG+jXl5OpwTK8ZAUlxb70aosHaZGdsv8YXrYKjuiufjRZ/OXCs0XLI9g==", + "requires": { + "@google-cloud/common": "^3.0.0", + "@types/console-log-level": "^1.4.0", + "@types/semver": "^7.0.0", + "console-log-level": "^1.4.0", + "delay": "^4.0.1", + "extend": "^3.0.2", + "gcp-metadata": "^4.0.0", + "parse-duration": "^0.4.4", + "pprof": "3.0.0", + "pretty-ms": "^7.0.0", + "protobufjs": "~6.10.0", + "semver": "^7.0.0", + "teeny-request": "^7.0.0" + }, + "dependencies": { + "@google-cloud/common": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.5.0.tgz", + "integrity": "sha512-10d7ZAvKhq47L271AqvHEd8KzJqGU45TY+rwM2Z3JHuB070FeTi7oJJd7elfrnKaEvaktw3hH2wKnRWxk/3oWQ==", + "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.1.1", + "retry-request": "^4.1.1", + "teeny-request": "^7.0.0" + } + }, + "@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.3", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.3.tgz", + "integrity": "sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw==" + }, + "@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": "13.13.42", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.42.tgz", + "integrity": "sha512-g+w2QgbW7k2CWLOXzQXbO37a7v5P9ObPvYahKphdBLV5aqpbVZRhTpWCT0SMRqX1i30Aig791ZmIM2fJGL2S8A==" + }, + "bignumber.js": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", + "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==" + }, + "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" + } + }, + "gaxios": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.1.0.tgz", + "integrity": "sha512-vb0to8xzGnA2qcgywAjtshOKKVDf2eQhJoiL6fHhgW5tVN7wNk7egnYIO9zotfn3lQ3De1VPdf7V5/BWfCtCmg==", + "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.2.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.1.tgz", + "integrity": "sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw==", + "requires": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + } + }, + "google-auth-library": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", + "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + }, + "google-p12-pem": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", + "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", + "requires": { + "node-forge": "^0.10.0" + } + }, + "gtoken": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.2.1.tgz", + "integrity": "sha512-OY0BfPKe3QnMsY9MzTHTSKn+Vl2l1CcLe6BwDEQj00mbbkl5nyQ/7EUREstg4fQNZ8iYE7br4JJ7TdKeDOPWmw==", + "requires": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.0.3", + "jws": "^4.0.0" + } + }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + }, + "protobufjs": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz", + "integrity": "sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==", + "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.1", + "@types/node": "^13.7.0", + "long": "^4.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" + } + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "teeny-request": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.0.1.tgz", + "integrity": "sha512-sasJmQ37klOlplL4Ia/786M5YlOcoLGQyq2TE4WHSRupbAuDaQW0PfVxV4MtdBtRJ4ngzS+1qim8zP6Zp35qCw==", + "requires": { + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^8.0.0" + } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, "@google-cloud/projectify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-1.0.4.tgz", @@ -311,6 +717,229 @@ "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-1.0.4.tgz", "integrity": "sha512-VccZDcOql77obTnFh0TbNED/6ZbbmHDf8UMNnzO1d5g9V0Htfm4k5cllY8P1tJsRKC3zWYGRLaViiupcgVjBoQ==" }, + "@google-cloud/trace-agent": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@google-cloud/trace-agent/-/trace-agent-5.1.3.tgz", + "integrity": "sha512-f+5DX7n6QpDlHA+4kr81z69SLAdrlvd9T8skqCMgnYvtXx14AwzXZyzEDf3jppOYzYoqPPJv8XYiyYHHmYD0BA==", + "requires": { + "@google-cloud/common": "^3.0.0", + "@opencensus/propagation-stackdriver": "0.0.22", + "builtin-modules": "^3.0.0", + "console-log-level": "^1.4.0", + "continuation-local-storage": "^3.2.1", + "extend": "^3.0.2", + "gcp-metadata": "^4.0.0", + "google-auth-library": "^7.0.0", + "hex2dec": "^1.0.1", + "is": "^3.2.0", + "methods": "^1.1.1", + "require-in-the-middle": "^5.0.0", + "semver": "^7.0.0", + "shimmer": "^1.2.0", + "source-map-support": "^0.5.16", + "uuid": "^8.0.0" + }, + "dependencies": { + "@google-cloud/common": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.5.0.tgz", + "integrity": "sha512-10d7ZAvKhq47L271AqvHEd8KzJqGU45TY+rwM2Z3JHuB070FeTi7oJJd7elfrnKaEvaktw3hH2wKnRWxk/3oWQ==", + "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.1.1", + "retry-request": "^4.1.1", + "teeny-request": "^7.0.0" + }, + "dependencies": { + "google-auth-library": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", + "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + } + } + }, + "@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.3", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.3.tgz", + "integrity": "sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw==" + }, + "@opencensus/core": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/@opencensus/core/-/core-0.0.22.tgz", + "integrity": "sha512-ErazJtivjceNoOZI1bG9giQ6cWS45J4i6iPUtlp7dLNu58OLs/v+CD0FsaPCh47XgPxAI12vbBE8Ec09ViwHNA==", + "requires": { + "continuation-local-storage": "^3.2.1", + "log-driver": "^1.2.7", + "semver": "^7.0.0", + "shimmer": "^1.2.0", + "uuid": "^8.0.0" + } + }, + "@opencensus/propagation-stackdriver": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/@opencensus/propagation-stackdriver/-/propagation-stackdriver-0.0.22.tgz", + "integrity": "sha512-eBvf/ihb1mN8Yz/ASkz8nHzuMKqygu77+VNnUeR0yEh3Nj+ykB8VVR6lK+NAFXo1Rd1cOsTmgvuXAZgDAGleQQ==", + "requires": { + "@opencensus/core": "^0.0.22", + "hex2dec": "^1.0.1", + "uuid": "^8.0.0" + } + }, + "bignumber.js": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", + "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==" + }, + "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" + } + }, + "gaxios": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.1.0.tgz", + "integrity": "sha512-vb0to8xzGnA2qcgywAjtshOKKVDf2eQhJoiL6fHhgW5tVN7wNk7egnYIO9zotfn3lQ3De1VPdf7V5/BWfCtCmg==", + "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.2.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.1.tgz", + "integrity": "sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw==", + "requires": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + } + }, + "google-auth-library": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.0.2.tgz", + "integrity": "sha512-vjyNZR3pDLC0u7GHLfj+Hw9tGprrJwoMwkYGqURCXYITjCrP9HprOyxVV+KekdLgATtWGuDkQG2MTh0qpUPUgg==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + }, + "google-p12-pem": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", + "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", + "requires": { + "node-forge": "^0.10.0" + } + }, + "gtoken": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.2.1.tgz", + "integrity": "sha512-OY0BfPKe3QnMsY9MzTHTSKn+Vl2l1CcLe6BwDEQj00mbbkl5nyQ/7EUREstg4fQNZ8iYE7br4JJ7TdKeDOPWmw==", + "requires": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.0.3", + "jws": "^4.0.0" + } + }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + }, + "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" + } + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "teeny-request": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.0.1.tgz", + "integrity": "sha512-sasJmQ37klOlplL4Ia/786M5YlOcoLGQyq2TE4WHSRupbAuDaQW0PfVxV4MtdBtRJ4ngzS+1qim8zP6Zp35qCw==", + "requires": { + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^8.0.0" + } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, "@grpc/grpc-js": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.0.5.tgz", @@ -365,9 +994,9 @@ } }, "@overleaf/metrics": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@overleaf/metrics/-/metrics-3.4.1.tgz", - "integrity": "sha512-OgjlzuC+2gPdIEDHhmd9LDMu01tk1ln0cJhw1727BZ+Wgf2Z1hjuHRt4JeCkf+PFTHwJutVYT8v6IGPpNEPtbg==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@overleaf/metrics/-/metrics-3.5.1.tgz", + "integrity": "sha512-RLHxkMF7Y3725L3QwXo9cIn2gGobsMYUGuxKxg7PVMrPTMsomHEMeG7StOxCO7ML1Z/BwB/9nsVYNrsRdAJtKg==", "requires": { "@google-cloud/debug-agent": "^5.1.2", "@google-cloud/profiler": "^4.0.3", @@ -376,360 +1005,6 @@ "prom-client": "^11.1.3", "underscore": "~1.6.0", "yn": "^3.1.1" - }, - "dependencies": { - "@google-cloud/common": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.5.0.tgz", - "integrity": "sha512-10d7ZAvKhq47L271AqvHEd8KzJqGU45TY+rwM2Z3JHuB070FeTi7oJJd7elfrnKaEvaktw3hH2wKnRWxk/3oWQ==", - "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.1.1", - "retry-request": "^4.1.1", - "teeny-request": "^7.0.0" - } - }, - "@google-cloud/debug-agent": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@google-cloud/debug-agent/-/debug-agent-5.1.3.tgz", - "integrity": "sha512-WbzeEz4MvPlM7DX2QBsPcWgF62u7LSQv/oMYPl0L+TddTebqjDKiVXwxpzWk61NIfcKiet3dyCbPIt3N5o8XPQ==", - "requires": { - "@google-cloud/common": "^3.0.0", - "acorn": "^8.0.0", - "coffeescript": "^2.0.0", - "console-log-level": "^1.4.0", - "extend": "^3.0.2", - "findit2": "^2.2.3", - "gcp-metadata": "^4.0.0", - "p-limit": "^3.0.1", - "semver": "^7.0.0", - "source-map": "^0.6.1", - "split": "^1.0.0" - } - }, - "@google-cloud/profiler": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@google-cloud/profiler/-/profiler-4.1.0.tgz", - "integrity": "sha512-9e1zXRctLSUHAoAsFGwE4rS28fr0siiG+jXl5OpwTK8ZAUlxb70aosHaZGdsv8YXrYKjuiufjRZ/OXCs0XLI9g==", - "requires": { - "@google-cloud/common": "^3.0.0", - "@types/console-log-level": "^1.4.0", - "@types/semver": "^7.0.0", - "console-log-level": "^1.4.0", - "delay": "^4.0.1", - "extend": "^3.0.2", - "gcp-metadata": "^4.0.0", - "parse-duration": "^0.4.4", - "pprof": "3.0.0", - "pretty-ms": "^7.0.0", - "protobufjs": "~6.10.0", - "semver": "^7.0.0", - "teeny-request": "^7.0.0" - } - }, - "@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.3", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.3.tgz", - "integrity": "sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw==" - }, - "@google-cloud/trace-agent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@google-cloud/trace-agent/-/trace-agent-5.1.1.tgz", - "integrity": "sha512-YTcK0RLN90pLCprg0XC8uV4oAVd79vsXhkcxmEVwiOOYjUDvSrAhb7y/0SY606zgfhJHmUTNb/fZSWEtZP/slQ==", - "requires": { - "@google-cloud/common": "^3.0.0", - "@opencensus/propagation-stackdriver": "0.0.22", - "builtin-modules": "^3.0.0", - "console-log-level": "^1.4.0", - "continuation-local-storage": "^3.2.1", - "extend": "^3.0.2", - "gcp-metadata": "^4.0.0", - "google-auth-library": "^6.0.0", - "hex2dec": "^1.0.1", - "is": "^3.2.0", - "methods": "^1.1.1", - "require-in-the-middle": "^5.0.0", - "semver": "^7.0.0", - "shimmer": "^1.2.0", - "source-map-support": "^0.5.16", - "uuid": "^8.0.0" - } - }, - "@opencensus/core": { - "version": "0.0.22", - "resolved": "https://registry.npmjs.org/@opencensus/core/-/core-0.0.22.tgz", - "integrity": "sha512-ErazJtivjceNoOZI1bG9giQ6cWS45J4i6iPUtlp7dLNu58OLs/v+CD0FsaPCh47XgPxAI12vbBE8Ec09ViwHNA==", - "requires": { - "continuation-local-storage": "^3.2.1", - "log-driver": "^1.2.7", - "semver": "^7.0.0", - "shimmer": "^1.2.0", - "uuid": "^8.0.0" - } - }, - "@opencensus/propagation-stackdriver": { - "version": "0.0.22", - "resolved": "https://registry.npmjs.org/@opencensus/propagation-stackdriver/-/propagation-stackdriver-0.0.22.tgz", - "integrity": "sha512-eBvf/ihb1mN8Yz/ASkz8nHzuMKqygu77+VNnUeR0yEh3Nj+ykB8VVR6lK+NAFXo1Rd1cOsTmgvuXAZgDAGleQQ==", - "requires": { - "@opencensus/core": "^0.0.22", - "hex2dec": "^1.0.1", - "uuid": "^8.0.0" - } - }, - "@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": "13.13.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.33.tgz", - "integrity": "sha512-1B3GM1yuYsFyEvBb+ljBqWBOylsWDYioZ5wpu8AhXdIhq20neXS7eaSC8GkwHE0yQYGiOIV43lMsgRYTgKZefQ==" - }, - "@types/semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ==" - }, - "acorn": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.0.4.tgz", - "integrity": "sha512-XNP0PqF1XD19ZlLKvB7cMmnZswW4C/03pRHgirB30uSJTaS3A3V1/P4sS3HPvFmjoriPCJQs+JDSbm4bL1TxGQ==" - }, - "bignumber.js": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", - "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==" - }, - "coffeescript": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.5.1.tgz", - "integrity": "sha512-J2jRPX0eeFh5VKyVnoLrfVFgLZtnnmp96WQSLAS8OrLm2wtQLcnikYKe1gViJKDH7vucjuhHvBKKBP3rKcD1tQ==" - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "requires": { - "ms": "2.1.2" - } - }, - "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" - } - }, - "gaxios": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.0.1.tgz", - "integrity": "sha512-jOin8xRZ/UytQeBpSXFqIzqU7Fi5TqgPNLlUsSB8kjJ76+FiGBfImF8KJu++c6J4jOldfJUtt0YmkRj2ZpSHTQ==", - "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.2.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.1.tgz", - "integrity": "sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw==", - "requires": { - "gaxios": "^4.0.0", - "json-bigint": "^1.0.0" - } - }, - "google-auth-library": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.3.tgz", - "integrity": "sha512-m9mwvY3GWbr7ZYEbl61isWmk+fvTmOt0YNUfPOUY2VH8K5pZlAIWJjxEi0PqR3OjMretyiQLI6GURMrPSwHQ2g==", - "requires": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - } - }, - "google-p12-pem": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", - "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", - "requires": { - "node-forge": "^0.10.0" - } - }, - "gtoken": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.1.0.tgz", - "integrity": "sha512-4d8N6Lk8TEAHl9vVoRVMh9BNOKWVgl2DdNtr3428O75r3QFrF/a5MMu851VmK0AA8+iSvbwRv69k5XnMLURGhg==", - "requires": { - "gaxios": "^4.0.0", - "google-p12-pem": "^3.0.3", - "jws": "^4.0.0", - "mime": "^2.2.0" - } - }, - "json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "requires": { - "bignumber.js": "^9.0.0" - } - }, - "jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "requires": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "parse-duration": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-0.4.4.tgz", - "integrity": "sha512-KbAJuYGUhZkB9gotDiKLnZ7Z3VTacK3fgwmDdB6ZVDtJbMBT6MfLga0WJaYpPDu0mzqT0NgHtHDt5PY4l0nidg==" - }, - "pretty-ms": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", - "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", - "requires": { - "parse-ms": "^2.1.0" - } - }, - "protobufjs": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz", - "integrity": "sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==", - "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.1", - "@types/node": "^13.7.0", - "long": "^4.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" - } - }, - "require-in-the-middle": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.0.3.tgz", - "integrity": "sha512-p/ICV8uMlqC4tjOYabLMxAWCIKa0YUQgZZ6KDM0xgXJNgdGQ1WmL2A07TwmrZw+wi6ITUFKzH5v3n+ENEyXVkA==", - "requires": { - "debug": "^4.1.1", - "module-details-from-path": "^1.0.3", - "resolve": "^1.12.0" - } - }, - "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" - }, - "teeny-request": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.0.1.tgz", - "integrity": "sha512-sasJmQ37klOlplL4Ia/786M5YlOcoLGQyq2TE4WHSRupbAuDaQW0PfVxV4MtdBtRJ4ngzS+1qim8zP6Zp35qCw==", - "requires": { - "http-proxy-agent": "^4.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "stream-events": "^1.0.5", - "uuid": "^8.0.0" - } - }, - "uuid": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } } }, "@overleaf/o-error": { @@ -898,6 +1173,11 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.15.tgz", "integrity": "sha1-IOhWUbYv2GZW5Xycm8dxqxVwvFk=" }, + "@types/semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ==" + }, "@typescript-eslint/experimental-utils": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz", @@ -1351,9 +1631,9 @@ "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==" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", + "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==" }, "bunyan": { "version": "1.8.12", @@ -1797,9 +2077,9 @@ } }, "delay": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/delay/-/delay-4.3.0.tgz", - "integrity": "sha512-Lwaf3zVFDMBop1yDuFZ19F9WyGcZcGacsbdlZtWjQmM50tOcMntm1njF/Nb/Vjij3KaSvCF+sEYGKrrjObu2NA==" + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/delay/-/delay-4.4.1.tgz", + "integrity": "sha512-aL3AhqtfhOlT/3ai6sWXeqwnw63ATNpnUiN4HL7x9q+My5QtHlO3OIkasmug9LKzpheLdmUKGRKnYXYAS7FQkQ==" }, "delayed-stream": { "version": "1.0.0", @@ -3786,6 +4066,25 @@ "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==", "dev": true }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -4700,6 +4999,11 @@ "callsites": "^3.0.0" } }, + "parse-duration": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-0.4.4.tgz", + "integrity": "sha512-KbAJuYGUhZkB9gotDiKLnZ7Z3VTacK3fgwmDdB6ZVDtJbMBT6MfLga0WJaYpPDu0mzqT0NgHtHDt5PY4l0nidg==" + }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", @@ -4786,6 +5090,11 @@ "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==", "dev": true }, + "pify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", + "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==" + }, "pkg-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", @@ -4818,9 +5127,9 @@ "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" }, "@types/node": { - "version": "13.13.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.33.tgz", - "integrity": "sha512-1B3GM1yuYsFyEvBb+ljBqWBOylsWDYioZ5wpu8AhXdIhq20neXS7eaSC8GkwHE0yQYGiOIV43lMsgRYTgKZefQ==" + "version": "13.13.42", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.42.tgz", + "integrity": "sha512-g+w2QgbW7k2CWLOXzQXbO37a7v5P9ObPvYahKphdBLV5aqpbVZRhTpWCT0SMRqX1i30Aig791ZmIM2fJGL2S8A==" }, "debug": { "version": "3.2.7", @@ -4844,14 +5153,14 @@ } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "needle": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.2.tgz", - "integrity": "sha512-LbRIwS9BfkPvNwNHlsA41Q29kL2L/6VaOJ0qisM5lLWsTV3nP15abO5ITL6L81zqFhzjRKDAYjpcBcwM0AVvLQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.6.0.tgz", + "integrity": "sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg==", "requires": { "debug": "^3.2.6", "iconv-lite": "^0.4.4", @@ -4883,11 +5192,6 @@ "yocto-queue": "^0.1.0" } }, - "pify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", - "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==" - }, "protobufjs": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz", @@ -5552,6 +5856,14 @@ } } }, + "pretty-ms": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", + "requires": { + "parse-ms": "^2.1.0" + } + }, "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", @@ -5869,6 +6181,31 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, + "require-in-the-middle": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.1.0.tgz", + "integrity": "sha512-M2rLKVupQfJ5lf9OvqFGIT+9iVLnTmjgbOmpil12hiSQNn5zJTKGPoIisETNjfK+09vP3rpm1zJajmErpr2sEQ==", + "requires": { + "debug": "^4.1.1", + "module-details-from-path": "^1.0.3", + "resolve": "^1.12.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "require-like": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", diff --git a/services/clsi/package.json b/services/clsi/package.json index 8d74faac5f..eb194d9e49 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -10,7 +10,7 @@ "start": "node $NODE_APP_OPTIONS app.js", "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", "test:acceptance": "npm run test:acceptance:_run -- --grep=$MOCHA_GREP", - "test:unit:_run": "mocha --exit --recursive --reporter spec $@ test/unit/js", + "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 --max-warnings 0 .", @@ -19,7 +19,7 @@ }, "author": "James Allen <james@sharelatex.com>", "dependencies": { - "@overleaf/metrics": "^3.4.1", + "@overleaf/metrics": "^3.5.1", "async": "3.2.0", "body-parser": "^1.19.0", "diskusage": "^1.1.3", From 6cb4246eb1676269149d7c4b4c913b2f78a18505 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 30 Mar 2021 13:22:11 +0100 Subject: [PATCH 692/754] [misc] consume and validate a custom imageName for synctex requests --- services/clsi/app/js/CompileController.js | 27 ++++-- services/clsi/app/js/CompileManager.js | 84 ++++++++++------- .../test/acceptance/js/AllowedImageNames.js | 75 ++++++++++++++- .../clsi/test/acceptance/js/SynctexTests.js | 16 +--- .../clsi/test/acceptance/js/helpers/Client.js | 18 +++- .../test/unit/js/CompileControllerTests.js | 92 ++++++++++--------- .../clsi/test/unit/js/CompileManagerTests.js | 76 ++++++++++++++- 7 files changed, 290 insertions(+), 98 deletions(-) diff --git a/services/clsi/app/js/CompileController.js b/services/clsi/app/js/CompileController.js index b0ce3bb664..ddf83d7c8e 100644 --- a/services/clsi/app/js/CompileController.js +++ b/services/clsi/app/js/CompileController.js @@ -21,6 +21,12 @@ const ProjectPersistenceManager = require('./ProjectPersistenceManager') const logger = require('logger-sharelatex') const Errors = require('./Errors') +function isImageNameAllowed(imageName) { + const ALLOWED_IMAGES = + Settings.clsi && Settings.clsi.docker && Settings.clsi.docker.allowedImages + return !ALLOWED_IMAGES || ALLOWED_IMAGES.includes(imageName) +} + module.exports = CompileController = { compile(req, res, next) { if (next == null) { @@ -165,14 +171,21 @@ module.exports = CompileController = { const { file } = req.query const line = parseInt(req.query.line, 10) const column = parseInt(req.query.column, 10) + const { imageName } = req.query const { project_id } = req.params const { user_id } = req.params + + if (imageName && !isImageNameAllowed(imageName)) { + return res.status(400).send('invalid image') + } + return CompileManager.syncFromCode( project_id, user_id, file, line, column, + imageName, function (error, pdfPositions) { if (error != null) { return next(error) @@ -191,14 +204,20 @@ module.exports = CompileController = { const page = parseInt(req.query.page, 10) const h = parseFloat(req.query.h) const v = parseFloat(req.query.v) + const { imageName } = req.query const { project_id } = req.params const { user_id } = req.params + + if (imageName && !isImageNameAllowed(imageName)) { + return res.status(400).send('invalid image') + } return CompileManager.syncFromPdf( project_id, user_id, page, h, v, + imageName, function (error, codePositions) { if (error != null) { return next(error) @@ -218,13 +237,7 @@ module.exports = CompileController = { const { project_id } = req.params const { user_id } = req.params const { image } = req.query - if ( - image && - Settings.clsi && - Settings.clsi.docker && - Settings.clsi.docker.allowedImages && - !Settings.clsi.docker.allowedImages.includes(image) - ) { + if (image && !isImageNameAllowed(image)) { return res.status(400).send('invalid image') } logger.log({ image, file, project_id }, 'word count request') diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js index 25741005f8..c801062595 100644 --- a/services/clsi/app/js/CompileManager.js +++ b/services/clsi/app/js/CompileManager.js @@ -431,7 +431,15 @@ module.exports = CompileManager = { }) }, // directory exists - syncFromCode(project_id, user_id, file_name, line, column, callback) { + syncFromCode( + project_id, + user_id, + file_name, + line, + column, + imageName, + callback + ) { // If LaTeX was run in a virtual environment, the file path that synctex expects // might not match the file path on the host. The .synctex.gz file however, will be accessed // wherever it is on the host. @@ -444,22 +452,28 @@ module.exports = CompileManager = { const compileDir = getCompileDir(project_id, user_id) const synctex_path = `${base_dir}/output.pdf` const command = ['code', synctex_path, file_path, line, column] - CompileManager._runSynctex(project_id, user_id, command, function ( - error, - stdout - ) { - if (error != null) { - return callback(error) + CompileManager._runSynctex( + project_id, + user_id, + command, + imageName, + function (error, stdout) { + if (error != null) { + return callback(error) + } + logger.log( + { project_id, user_id, file_name, line, column, command, stdout }, + 'synctex code output' + ) + return callback( + null, + CompileManager._parseSynctexFromCodeOutput(stdout) + ) } - logger.log( - { project_id, user_id, file_name, line, column, command, stdout }, - 'synctex code output' - ) - return callback(null, CompileManager._parseSynctexFromCodeOutput(stdout)) - }) + ) }, - syncFromPdf(project_id, user_id, page, h, v, callback) { + syncFromPdf(project_id, user_id, page, h, v, imageName, callback) { if (callback == null) { callback = function (error, filePositions) {} } @@ -468,22 +482,25 @@ module.exports = CompileManager = { const base_dir = Settings.path.synctexBaseDir(compileName) const synctex_path = `${base_dir}/output.pdf` const command = ['pdf', synctex_path, page, h, v] - CompileManager._runSynctex(project_id, user_id, command, function ( - error, - stdout - ) { - if (error != null) { - return callback(error) + CompileManager._runSynctex( + project_id, + user_id, + command, + imageName, + function (error, stdout) { + if (error != null) { + return callback(error) + } + logger.log( + { project_id, user_id, page, h, v, stdout }, + 'synctex pdf output' + ) + return callback( + null, + CompileManager._parseSynctexFromPdfOutput(stdout, base_dir) + ) } - logger.log( - { project_id, user_id, page, h, v, stdout }, - 'synctex pdf output' - ) - return callback( - null, - CompileManager._parseSynctexFromPdfOutput(stdout, base_dir) - ) - }) + ) }, _checkFileExists(dir, filename, callback) { @@ -513,7 +530,7 @@ module.exports = CompileManager = { }) }, - _runSynctex(project_id, user_id, command, callback) { + _runSynctex(project_id, user_id, command, imageName, callback) { if (callback == null) { callback = function (error, stdout) {} } @@ -533,9 +550,10 @@ module.exports = CompileManager = { compileName, command, directory, - Settings.clsi && Settings.clsi.docker - ? Settings.clsi.docker.image - : undefined, + imageName || + (Settings.clsi && Settings.clsi.docker + ? Settings.clsi.docker.image + : undefined), timeout, {}, compileGroup, diff --git a/services/clsi/test/acceptance/js/AllowedImageNames.js b/services/clsi/test/acceptance/js/AllowedImageNames.js index 7e38954e9c..8107273fe0 100644 --- a/services/clsi/test/acceptance/js/AllowedImageNames.js +++ b/services/clsi/test/acceptance/js/AllowedImageNames.js @@ -71,6 +71,78 @@ Hello world }) }) + describe('syncToCode', function () { + beforeEach(function (done) { + Client.compile(this.project_id, this.request, done) + }) + it('should error out with an invalid imageName', function () { + Client.syncFromCodeWithImage( + this.project_id, + 'main.tex', + 3, + 5, + 'something/evil:1337', + (error, body) => { + expect(String(error)).to.include('statusCode=400') + expect(body).to.equal('invalid image') + } + ) + }) + + it('should produce a mapping a valid imageName', function () { + Client.syncFromCodeWithImage( + this.project_id, + 'main.tex', + 3, + 5, + process.env.TEXLIVE_IMAGE, + (error, result) => { + expect(error).to.not.exist + expect(result).to.deep.equal({ + pdf: [ + { page: 1, h: 133.77, v: 134.76, height: 6.92, width: 343.71 } + ] + }) + } + ) + }) + }) + + describe('syncToPdf', function () { + beforeEach(function (done) { + Client.compile(this.project_id, this.request, done) + }) + it('should error out with an invalid imageName', function () { + Client.syncFromPdfWithImage( + this.project_id, + 'main.tex', + 100, + 200, + 'something/evil:1337', + (error, body) => { + expect(String(error)).to.include('statusCode=400') + expect(body).to.equal('invalid image') + } + ) + }) + + it('should produce a mapping a valid imageName', function () { + Client.syncFromPdfWithImage( + this.project_id, + 1, + 100, + 200, + process.env.TEXLIVE_IMAGE, + (error, result) => { + expect(error).to.not.exist + expect(result).to.deep.equal({ + code: [{ file: 'main.tex', line: 3, column: -1 }] + }) + } + ) + }) + }) + describe('wordcount', function () { beforeEach(function (done) { Client.compile(this.project_id, this.request, done) @@ -80,8 +152,9 @@ Hello world this.project_id, 'main.tex', 'something/evil:1337', - (error, result) => { + (error, body) => { expect(String(error)).to.include('statusCode=400') + expect(body).to.equal('invalid image') } ) }) diff --git a/services/clsi/test/acceptance/js/SynctexTests.js b/services/clsi/test/acceptance/js/SynctexTests.js index e636912bea..206ada45d1 100644 --- a/services/clsi/test/acceptance/js/SynctexTests.js +++ b/services/clsi/test/acceptance/js/SynctexTests.js @@ -100,9 +100,7 @@ Hello world 3, 5, (error, body) => { - if (error != null) { - throw error - } + expect(String(error)).to.include('statusCode=404') expect(body).to.equal('Not Found') return done() } @@ -117,9 +115,7 @@ Hello world 100, 200, (error, body) => { - if (error != null) { - throw error - } + expect(String(error)).to.include('statusCode=404') expect(body).to.equal('Not Found') return done() } @@ -160,9 +156,7 @@ Hello world 3, 5, (error, body) => { - if (error != null) { - throw error - } + expect(String(error)).to.include('statusCode=404') expect(body).to.equal('Not Found') return done() } @@ -177,9 +171,7 @@ Hello world 100, 200, (error, body) => { - if (error != null) { - throw error - } + expect(String(error)).to.include('statusCode=404') expect(body).to.equal('Not Found') return done() } diff --git a/services/clsi/test/acceptance/js/helpers/Client.js b/services/clsi/test/acceptance/js/helpers/Client.js index 7cd0c1148b..43825ae415 100644 --- a/services/clsi/test/acceptance/js/helpers/Client.js +++ b/services/clsi/test/acceptance/js/helpers/Client.js @@ -69,6 +69,10 @@ module.exports = Client = { }, syncFromCode(project_id, file, line, column, callback) { + Client.syncFromCodeWithImage(project_id, file, line, column, '', callback) + }, + + syncFromCodeWithImage(project_id, file, line, column, imageName, callback) { if (callback == null) { callback = function (error, pdfPositions) {} } @@ -76,6 +80,7 @@ module.exports = Client = { { url: `${this.host}/project/${project_id}/sync/code`, qs: { + imageName, file, line, column @@ -86,12 +91,19 @@ module.exports = Client = { if (error != null) { return callback(error) } + if (response.statusCode !== 200) { + return callback(new Error(`statusCode=${response.statusCode}`), body) + } return callback(null, body) } ) }, syncFromPdf(project_id, page, h, v, callback) { + Client.syncFromPdfWithImage(project_id, page, h, v, '', callback) + }, + + syncFromPdfWithImage(project_id, page, h, v, imageName, callback) { if (callback == null) { callback = function (error, pdfPositions) {} } @@ -99,6 +111,7 @@ module.exports = Client = { { url: `${this.host}/project/${project_id}/sync/pdf`, qs: { + imageName, page, h, v @@ -109,6 +122,9 @@ module.exports = Client = { if (error != null) { return callback(error) } + if (response.statusCode !== 200) { + return callback(new Error(`statusCode=${response.statusCode}`), body) + } return callback(null, body) } ) @@ -208,7 +224,7 @@ module.exports = Client = { return callback(error) } if (response.statusCode !== 200) { - return callback(new Error(`statusCode=${response.statusCode}`)) + return callback(new Error(`statusCode=${response.statusCode}`), body) } return callback(null, JSON.parse(body)) } diff --git a/services/clsi/test/unit/js/CompileControllerTests.js b/services/clsi/test/unit/js/CompileControllerTests.js index d8c7c12609..6f256d2955 100644 --- a/services/clsi/test/unit/js/CompileControllerTests.js +++ b/services/clsi/test/unit/js/CompileControllerTests.js @@ -18,6 +18,48 @@ const modulePath = require('path').join( ) const tk = require('timekeeper') +function tryImageNameValidation(method, imageNameField) { + describe('when allowedImages is set', function () { + beforeEach(function () { + this.Settings.clsi = { docker: {} } + this.Settings.clsi.docker.allowedImages = [ + 'repo/image:tag1', + 'repo/image:tag2' + ] + this.res.send = sinon.stub() + this.res.status = sinon.stub().returns({ send: this.res.send }) + + this.CompileManager[method].reset() + }) + + describe('with an invalid image', function () { + beforeEach(function () { + this.req.query[imageNameField] = 'something/evil:1337' + this.CompileController[method](this.req, this.res, this.next) + }) + it('should return a 400', function () { + expect(this.res.status.calledWith(400)).to.equal(true) + }) + it('should not run the query', function () { + expect(this.CompileManager[method].called).to.equal(false) + }) + }) + + describe('with a valid image', function () { + beforeEach(function () { + this.req.query[imageNameField] = 'repo/image:tag1' + this.CompileController[method](this.req, this.res, this.next) + }) + it('should not return a 400', function () { + expect(this.res.status.calledWith(400)).to.equal(false) + }) + it('should run the query', function () { + expect(this.CompileManager[method].called).to.equal(true) + }) + }) + }) +} + describe('CompileController', function () { beforeEach(function () { this.CompileController = SandboxedModule.require(modulePath, { @@ -248,7 +290,7 @@ describe('CompileController', function () { this.CompileManager.syncFromCode = sinon .stub() - .callsArgWith(5, null, (this.pdfPositions = ['mock-positions'])) + .yields(null, (this.pdfPositions = ['mock-positions'])) return this.CompileController.syncFromCode(this.req, this.res, this.next) }) @@ -264,13 +306,15 @@ describe('CompileController', function () { .should.equal(true) }) - return it('should return the positions', function () { + it('should return the positions', function () { return this.res.json .calledWith({ pdf: this.pdfPositions }) .should.equal(true) }) + + tryImageNameValidation('syncFromCode', 'imageName') }) describe('syncFromPdf', function () { @@ -289,7 +333,7 @@ describe('CompileController', function () { this.CompileManager.syncFromPdf = sinon .stub() - .callsArgWith(5, null, (this.codePositions = ['mock-positions'])) + .yields(null, (this.codePositions = ['mock-positions'])) return this.CompileController.syncFromPdf(this.req, this.res, this.next) }) @@ -299,13 +343,15 @@ describe('CompileController', function () { .should.equal(true) }) - return it('should return the positions', function () { + it('should return the positions', function () { return this.res.json .calledWith({ code: this.codePositions }) .should.equal(true) }) + + tryImageNameValidation('syncFromPdf', 'imageName') }) return describe('wordcount', function () { @@ -340,42 +386,6 @@ describe('CompileController', function () { .should.equal(true) }) - describe('when allowedImages is set', function () { - beforeEach(function () { - this.Settings.clsi = { docker: {} } - this.Settings.clsi.docker.allowedImages = [ - 'repo/image:tag1', - 'repo/image:tag2' - ] - this.res.send = sinon.stub() - this.res.status = sinon.stub().returns({ send: this.res.send }) - }) - - describe('with an invalid image', function () { - beforeEach(function () { - this.req.query.image = 'something/evil:1337' - this.CompileController.wordcount(this.req, this.res, this.next) - }) - it('should return a 400', function () { - expect(this.res.status.calledWith(400)).to.equal(true) - }) - it('should not run the query', function () { - expect(this.CompileManager.wordcount.called).to.equal(false) - }) - }) - - describe('with a valid image', function () { - beforeEach(function () { - this.req.query.image = 'repo/image:tag1' - this.CompileController.wordcount(this.req, this.res, this.next) - }) - it('should not return a 400', function () { - expect(this.res.status.calledWith(400)).to.equal(false) - }) - it('should run the query', function () { - expect(this.CompileManager.wordcount.called).to.equal(true) - }) - }) - }) + tryImageNameValidation('wordcount', 'image') }) }) diff --git a/services/clsi/test/unit/js/CompileManagerTests.js b/services/clsi/test/unit/js/CompileManagerTests.js index e33783a185..2d40bdd64a 100644 --- a/services/clsi/test/unit/js/CompileManagerTests.js +++ b/services/clsi/test/unit/js/CompileManagerTests.js @@ -394,13 +394,14 @@ describe('CompileManager', function () { this.stdout = `NODE\t${this.page}\t${this.h}\t${this.v}\t${this.width}\t${this.height}\n` this.CommandRunner.run = sinon .stub() - .callsArgWith(7, null, { stdout: this.stdout }) + .yields(null, { stdout: this.stdout }) return this.CompileManager.syncFromCode( this.project_id, this.user_id, this.file_name, this.line, this.column, + '', this.callback ) }) @@ -428,7 +429,7 @@ describe('CompileManager', function () { .should.equal(true) }) - return it('should call the callback with the parsed output', function () { + it('should call the callback with the parsed output', function () { return this.callback .calledWith(null, [ { @@ -441,6 +442,44 @@ describe('CompileManager', function () { ]) .should.equal(true) }) + + describe('with a custom imageName', function () { + const customImageName = 'foo/bar:tag-0' + beforeEach(function () { + this.CommandRunner.run.reset() + this.CompileManager.syncFromCode( + this.project_id, + this.user_id, + this.file_name, + this.line, + this.column, + customImageName, + this.callback + ) + }) + + it('should execute the synctex binary in a custom docker image', function () { + const synctex_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/output.pdf` + const file_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/${this.file_name}` + this.CommandRunner.run + .calledWith( + `${this.project_id}-${this.user_id}`, + [ + '/opt/synctex', + 'code', + synctex_path, + file_path, + this.line, + this.column + ], + `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, + customImageName, + 60000, + {} + ) + .should.equal(true) + }) + }) }) return describe('syncFromPdf', function () { @@ -460,6 +499,7 @@ describe('CompileManager', function () { this.page, this.h, this.v, + '', this.callback ) }) @@ -479,7 +519,7 @@ describe('CompileManager', function () { .should.equal(true) }) - return it('should call the callback with the parsed output', function () { + it('should call the callback with the parsed output', function () { return this.callback .calledWith(null, [ { @@ -490,6 +530,36 @@ describe('CompileManager', function () { ]) .should.equal(true) }) + + describe('with a custom imageName', function () { + const customImageName = 'foo/bar:tag-1' + beforeEach(function () { + this.CommandRunner.run.reset() + this.CompileManager.syncFromPdf( + this.project_id, + this.user_id, + this.page, + this.h, + this.v, + customImageName, + this.callback + ) + }) + + it('should execute the synctex binary in a custom docker image', function () { + const synctex_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/output.pdf` + this.CommandRunner.run + .calledWith( + `${this.project_id}-${this.user_id}`, + ['/opt/synctex', 'pdf', synctex_path, this.page, this.h, this.v], + `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, + customImageName, + 60000, + {} + ) + .should.equal(true) + }) + }) }) }) From ab57729f982f9c5bc51645f832f7a394ad8cb7d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Mar 2021 17:52:26 +0000 Subject: [PATCH 693/754] Bump y18n from 4.0.0 to 4.0.1 Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.1. - [Release notes](https://github.com/yargs/y18n/releases) - [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md) - [Commits](https://github.com/yargs/y18n/commits) Signed-off-by: dependabot[bot] <support@github.com> --- services/clsi/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index a4fb21c0f9..04e7b8ddf0 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -7418,9 +7418,9 @@ } }, "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==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", "dev": true }, "yallist": { From cfa8127c1b6b018454fb9f31dbd8ec91769ee9eb Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Thu, 29 Apr 2021 15:30:54 +0100 Subject: [PATCH 694/754] [misc] add linting for missing explicit dependencies and fix any errors --- services/clsi/.eslintrc | 13 +++++++++++-- services/clsi/app/js/LocalCommandRunner.js | 2 +- services/clsi/buildscript.txt | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/services/clsi/.eslintrc b/services/clsi/.eslintrc index 76dad1561d..321353f971 100644 --- a/services/clsi/.eslintrc +++ b/services/clsi/.eslintrc @@ -22,7 +22,10 @@ "rules": { // Swap the no-unused-expressions rule with a more chai-friendly one "no-unused-expressions": 0, - "chai-friendly/no-unused-expressions": "error" + "chai-friendly/no-unused-expressions": "error", + + // Do not allow importing of implicit dependencies. + "import/no-extraneous-dependencies": "error" }, "overrides": [ { @@ -57,7 +60,13 @@ "files": ["app/**/*.js", "app.js", "index.js"], "rules": { // don't allow console.log in backend code - "no-console": "error" + "no-console": "error", + + // Do not allow importing of implicit dependencies. + "import/no-extraneous-dependencies": ["error", { + // Do not allow importing of devDependencies. + "devDependencies": false + }] } } ] diff --git a/services/clsi/app/js/LocalCommandRunner.js b/services/clsi/app/js/LocalCommandRunner.js index 6f57731c9c..d5fd3090a7 100644 --- a/services/clsi/app/js/LocalCommandRunner.js +++ b/services/clsi/app/js/LocalCommandRunner.js @@ -15,7 +15,7 @@ */ let CommandRunner const { spawn } = require('child_process') -const _ = require('underscore') +const _ = require('lodash') const logger = require('logger-sharelatex') logger.info('using standard command runner') diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index a86911dc61..84a43ecb83 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -6,4 +6,4 @@ clsi --env-pass-through=TEXLIVE_IMAGE --node-version=12.21.0 --public-repo=True ---script-version=3.7.0 +--script-version=3.8.0 From e66ff7a2ca245515da670f73e9df188e9636ebef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 May 2021 14:32:37 +0000 Subject: [PATCH 695/754] Bump hosted-git-info from 2.8.5 to 2.8.9 Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.5 to 2.8.9. - [Release notes](https://github.com/npm/hosted-git-info/releases) - [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md) - [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.5...v2.8.9) Signed-off-by: dependabot[bot] <support@github.com> --- services/clsi/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index a4fb21c0f9..2f94be1042 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -3536,9 +3536,9 @@ "integrity": "sha512-Yu+q/XWr2fFQ11tHxPq4p4EiNkb2y+lAacJNhAdRXVfRIcDH6gi7htWFnnlIzvqHMHoWeIsfXlNAjZInpAOJDA==" }, "hosted-git-info": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", - "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "http-errors": { From b456ea726d6eb8e186a9982f896a08bfd5c8e3f1 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Thu, 13 May 2021 15:07:54 +0200 Subject: [PATCH 696/754] [misc] merge pdf caching into main (#226) * wip generate directory for hash content * cleanup, remove console logging * add content caching module * Return PDF stream ranges with compile response * Return the PDF file size in the compile response * PDF range endpoint * [misc] WIP: pdf caching: preserve the m-time on static content files * [misc] WIP: pdf caching: improve browser caching, emit caching headers * [misc] WIP: pdf caching: do not emit very small chunks <1kB * [misc] keep up with moving output files into a separate directory * [OutputCacheManager] add global feature flag for enabling pdf caching * [misc] add contentId into the URL for protecting PDF stream contents * [misc] support PDF stream caching for anonymous users * [misc] add per-request feature flag for enabling PDF stream caching * [misc] enable pdf caching in CI and emit metrics at the end of run * [misc] expose compile stats and timings to the frontend * [misc] log an error in case saving output files fails * [misc] add metrics for pdf bandwidth and pdf caching performance * [misc] add a dark mode to the pdf caching for computing ranges only * [misc] move pdf caching metrics into ContentCacheMetrics * [misc] add a config option for the min chunk size of pdf ranges Co-authored-by: Brian Gough <brian.gough@overleaf.com> Co-authored-by: Eric Mc Sween <eric.mcsween@overleaf.com> --- services/clsi/app.js | 10 ++ services/clsi/app/js/CompileController.js | 40 +++-- services/clsi/app/js/CompileManager.js | 28 +++- services/clsi/app/js/ContentCacheManager.js | 118 +++++++++++++++ services/clsi/app/js/ContentCacheMetrics.js | 80 ++++++++++ services/clsi/app/js/ContentController.js | 24 +++ services/clsi/app/js/OutputCacheManager.js | 138 +++++++++++++++++- services/clsi/app/js/RequestParser.js | 8 + services/clsi/config/settings.defaults.js | 7 +- services/clsi/docker-compose.ci.yml | 1 + services/clsi/docker-compose.yml | 1 + services/clsi/test/acceptance/js/Stats.js | 16 ++ .../clsi/test/acceptance/js/helpers/Client.js | 4 + .../test/unit/js/CompileControllerTests.js | 33 ++++- .../clsi/test/unit/js/CompileManagerTests.js | 6 +- 15 files changed, 487 insertions(+), 27 deletions(-) create mode 100644 services/clsi/app/js/ContentCacheManager.js create mode 100644 services/clsi/app/js/ContentCacheMetrics.js create mode 100644 services/clsi/app/js/ContentController.js create mode 100644 services/clsi/test/acceptance/js/Stats.js diff --git a/services/clsi/app.js b/services/clsi/app.js index f24ce5f924..6266c86b16 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -10,6 +10,7 @@ const Metrics = require('@overleaf/metrics') Metrics.initialize('clsi') const CompileController = require('./app/js/CompileController') +const ContentController = require('./app/js/ContentController') const Settings = require('settings-sharelatex') const logger = require('logger-sharelatex') logger.initialize('clsi') @@ -170,6 +171,15 @@ app.get( } ) +app.get( + '/project/:projectId/content/:contentId/:hash', + ContentController.getPdfRange +) +app.get( + '/project/:projectId/user/:userId/content/:contentId/:hash', + ContentController.getPdfRange +) + app.get('/project/:project_id/build/:build_id/output/*', function ( req, res, diff --git a/services/clsi/app/js/CompileController.js b/services/clsi/app/js/CompileController.js index ddf83d7c8e..2813bca984 100644 --- a/services/clsi/app/js/CompileController.js +++ b/services/clsi/app/js/CompileController.js @@ -49,7 +49,9 @@ module.exports = CompileController = { } return CompileManager.doCompileWithLock(request, function ( error, - outputFiles + outputFiles, + stats, + timings ) { let code, status if (outputFiles == null) { @@ -118,18 +120,30 @@ module.exports = CompileController = { compile: { status, error: (error != null ? error.message : undefined) || error, - outputFiles: outputFiles.map((file) => ({ - url: - `${Settings.apis.clsi.url}/project/${request.project_id}` + - (request.user_id != null - ? `/user/${request.user_id}` - : '') + - (file.build != null ? `/build/${file.build}` : '') + - `/output/${file.path}`, - path: file.path, - type: file.type, - build: file.build - })) + stats, + timings, + outputFiles: outputFiles.map((file) => { + const record = { + url: + `${Settings.apis.clsi.url}/project/${request.project_id}` + + (request.user_id != null + ? `/user/${request.user_id}` + : '') + + (file.build != null ? `/build/${file.build}` : '') + + `/output/${file.path}`, + path: file.path, + type: file.type, + build: file.build, + contentId: file.contentId + } + if (file.ranges != null) { + record.ranges = file.ranges + } + if (file.size != null) { + record.size = file.size + } + return record + }) } }) }) diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js index c801062595..c771082bfd 100644 --- a/services/clsi/app/js/CompileManager.js +++ b/services/clsi/app/js/CompileManager.js @@ -34,6 +34,7 @@ const os = require('os') const async = require('async') const Errors = require('./Errors') const CommandRunner = require('./CommandRunner') +const { emitPdfStats } = require('./ContentCacheMetrics') const getCompileName = function (project_id, user_id) { if (user_id != null) { @@ -77,6 +78,7 @@ module.exports = CompileManager = { const compileDir = getCompileDir(request.project_id, request.user_id) const outputDir = getOutputDir(request.project_id, request.user_id) + const timerE2E = new Metrics.Timer('compile-e2e') let timer = new Metrics.Timer('write-to-disk') logger.log( { project_id: request.project_id, user_id: request.user_id }, @@ -249,11 +251,13 @@ module.exports = CompileManager = { return callback(error) } Metrics.inc('compiles-succeeded') + stats = stats || {} const object = stats || {} for (metric_key in object) { metric_value = object[metric_key] Metrics.count(metric_key, metric_value) } + timings = timings || {} const object1 = timings || {} for (metric_key in object1) { metric_value = object1[metric_key] @@ -297,10 +301,32 @@ module.exports = CompileManager = { return callback(error) } return OutputCacheManager.saveOutputFiles( + { request, stats, timings }, outputFiles, compileDir, outputDir, - (error, newOutputFiles) => callback(null, newOutputFiles) + (err, newOutputFiles) => { + if (err) { + const { + project_id: projectId, + user_id: userId + } = request + logger.err( + { projectId, userId, err }, + 'failed to save output files' + ) + } + + // Emit compile time. + timings.compile = ts + timings.compileE2E = timerE2E.done() + + if (stats['pdf-size']) { + emitPdfStats(stats, timings) + } + + callback(null, newOutputFiles, stats, timings) + } ) } ) diff --git a/services/clsi/app/js/ContentCacheManager.js b/services/clsi/app/js/ContentCacheManager.js new file mode 100644 index 0000000000..94ed9ebc00 --- /dev/null +++ b/services/clsi/app/js/ContentCacheManager.js @@ -0,0 +1,118 @@ +/** + * ContentCacheManager - maintains a cache of stream hashes from a PDF file + */ + +const { callbackify } = require('util') +const fs = require('fs') +const crypto = require('crypto') +const Path = require('path') +const Settings = require('settings-sharelatex') + +const MIN_CHUNK_SIZE = Settings.pdfCachingMinChunkSize + +/** + * + * @param {String} contentDir path to directory where content hash files are cached + * @param {String} filePath the pdf file to scan for streams + */ +async function update(contentDir, filePath) { + const stream = fs.createReadStream(filePath) + const extractor = new PdfStreamsExtractor() + const ranges = [] + const newRanges = [] + for await (const chunk of stream) { + const pdfStreams = extractor.consume(chunk) + for (const pdfStream of pdfStreams) { + if (pdfStream.end - pdfStream.start < MIN_CHUNK_SIZE) continue + const hash = pdfStreamHash(pdfStream.buffers) + const range = { start: pdfStream.start, end: pdfStream.end, hash } + ranges.push(range) + if (await writePdfStream(contentDir, hash, pdfStream.buffers)) { + newRanges.push(range) + } + } + } + return [ranges, newRanges] +} + +class PdfStreamsExtractor { + constructor() { + this.fileIndex = 0 + this.inStream = false + this.streamStartIndex = 0 + this.buffers = [] + } + + consume(chunk) { + let chunkIndex = 0 + const pdfStreams = [] + while (true) { + if (!this.inStream) { + // Not in a stream, look for stream start + const index = chunk.indexOf('stream', chunkIndex) + if (index === -1) { + // Couldn't find stream start + break + } + // Found stream start, start a stream + this.inStream = true + this.streamStartIndex = this.fileIndex + index + chunkIndex = index + } else { + // In a stream, look for stream end + const index = chunk.indexOf('endstream', chunkIndex) + if (index === -1) { + this.buffers.push(chunk.slice(chunkIndex)) + break + } + // add "endstream" part + const endIndex = index + 9 + this.buffers.push(chunk.slice(chunkIndex, endIndex)) + pdfStreams.push({ + start: this.streamStartIndex, + end: this.fileIndex + endIndex, + buffers: this.buffers + }) + this.inStream = false + this.buffers = [] + chunkIndex = endIndex + } + } + this.fileIndex += chunk.length + return pdfStreams + } +} + +function pdfStreamHash(buffers) { + const hash = crypto.createHash('sha256') + for (const buffer of buffers) { + hash.update(buffer) + } + return hash.digest('hex') +} + +async function writePdfStream(dir, hash, buffers) { + const filename = Path.join(dir, hash) + try { + await fs.promises.stat(filename) + // The file exists. Do not rewrite the content. + // It would change the modified-time of the file and hence invalidate the + // ETags used for client side caching via browser internals. + return false + } catch (e) {} + const file = await fs.promises.open(filename, 'w') + if (Settings.enablePdfCachingDark) { + // Write an empty file in dark mode. + buffers = [] + } + try { + for (const buffer of buffers) { + await file.write(buffer) + } + } finally { + await file.close() + } + return true +} + +module.exports = { update: callbackify(update) } diff --git a/services/clsi/app/js/ContentCacheMetrics.js b/services/clsi/app/js/ContentCacheMetrics.js new file mode 100644 index 0000000000..04b475f827 --- /dev/null +++ b/services/clsi/app/js/ContentCacheMetrics.js @@ -0,0 +1,80 @@ +const Metrics = require('./Metrics') + +const ONE_MB = 1024 * 1024 + +function emitPdfStats(stats, timings) { + if (timings['compute-pdf-caching']) { + emitPdfCachingStats(stats, timings) + } else { + // How much bandwidth will the pdf incur when downloaded in full? + Metrics.summary('pdf-bandwidth', stats['pdf-size']) + } +} + +function emitPdfCachingStats(stats, timings) { + if (!stats['pdf-size']) return // double check + + // How large is the overhead of hashing up-front? + const fraction = + timings.compileE2E - timings['compute-pdf-caching'] !== 0 + ? timings.compileE2E / + (timings.compileE2E - timings['compute-pdf-caching']) + : 1 + Metrics.summary('overhead-compute-pdf-ranges', fraction * 100 - 100) + + // How does the hashing scale to pdf size in MB? + Metrics.timing( + 'compute-pdf-caching-relative-to-pdf-size', + timings['compute-pdf-caching'] / (stats['pdf-size'] / ONE_MB) + ) + if (stats['pdf-caching-total-ranges-size']) { + // How does the hashing scale to total ranges size in MB? + Metrics.timing( + 'compute-pdf-caching-relative-to-total-ranges-size', + timings['compute-pdf-caching'] / + (stats['pdf-caching-total-ranges-size'] / ONE_MB) + ) + // How fast is the hashing per range on average? + Metrics.timing( + 'compute-pdf-caching-relative-to-ranges-count', + timings['compute-pdf-caching'] / stats['pdf-caching-n-ranges'] + ) + + // How many ranges are new? + Metrics.summary( + 'new-pdf-ranges-relative-to-total-ranges', + (stats['pdf-caching-n-new-ranges'] / stats['pdf-caching-n-ranges']) * 100 + ) + } + + // How much content is cacheable? + Metrics.summary( + 'cacheable-ranges-to-pdf-size', + (stats['pdf-caching-total-ranges-size'] / stats['pdf-size']) * 100 + ) + + const sizeWhenDownloadedInFull = + // All of the pdf + stats['pdf-size'] - + // These ranges are potentially cached. + stats['pdf-caching-total-ranges-size'] + + // These ranges are not cached. + stats['pdf-caching-new-ranges-size'] + + // How much bandwidth can we save when downloading the pdf in full? + Metrics.summary( + 'pdf-bandwidth-savings', + 100 - (sizeWhenDownloadedInFull / stats['pdf-size']) * 100 + ) + + // How much bandwidth will the pdf incur when downloaded in full? + Metrics.summary('pdf-bandwidth', sizeWhenDownloadedInFull) + + // How much space do the ranges use? + // This will accumulate the ranges size over time, skipping already written ranges. + Metrics.summary('pdf-ranges-disk-size', stats['pdf-caching-new-ranges-size']) +} + +module.exports = { + emitPdfStats +} diff --git a/services/clsi/app/js/ContentController.js b/services/clsi/app/js/ContentController.js new file mode 100644 index 0000000000..76478defe8 --- /dev/null +++ b/services/clsi/app/js/ContentController.js @@ -0,0 +1,24 @@ +const Path = require('path') +const send = require('send') +const Settings = require('settings-sharelatex') +const OutputCacheManager = require('./OutputCacheManager') + +const ONE_DAY_S = 24 * 60 * 60 +const ONE_DAY_MS = ONE_DAY_S * 1000 + +function getPdfRange(req, res, next) { + const { projectId, userId, contentId, hash } = req.params + const perUserDir = userId ? `${projectId}-${userId}` : projectId + const path = Path.join( + Settings.path.outputDir, + perUserDir, + OutputCacheManager.CONTENT_SUBDIR, + contentId, + hash + ) + res.setHeader('cache-control', `public, max-age=${ONE_DAY_S}`) + res.setHeader('expires', new Date(Date.now() + ONE_DAY_MS).toUTCString()) + send(req, path).pipe(res) +} + +module.exports = { getPdfRange } diff --git a/services/clsi/app/js/OutputCacheManager.js b/services/clsi/app/js/OutputCacheManager.js index 8aefb9b77a..cd1bd883a2 100644 --- a/services/clsi/app/js/OutputCacheManager.js +++ b/services/clsi/app/js/OutputCacheManager.js @@ -22,10 +22,13 @@ const logger = require('logger-sharelatex') const _ = require('lodash') const Settings = require('settings-sharelatex') const crypto = require('crypto') +const Metrics = require('./Metrics') const OutputFileOptimiser = require('./OutputFileOptimiser') +const ContentCacheManager = require('./ContentCacheManager') module.exports = OutputCacheManager = { + CONTENT_SUBDIR: 'content', CACHE_SUBDIR: 'generated-files', ARCHIVE_SUBDIR: 'archived-logs', // build id is HEXDATE-HEXRANDOM from Date.now()and RandomBytes @@ -59,7 +62,13 @@ module.exports = OutputCacheManager = { }) }, - saveOutputFiles(outputFiles, compileDir, outputDir, callback) { + saveOutputFiles( + { request, stats, timings }, + outputFiles, + compileDir, + outputDir, + callback + ) { if (callback == null) { callback = function (error) {} } @@ -72,7 +81,31 @@ module.exports = OutputCacheManager = { compileDir, outputDir, buildId, - callback + function (err, result) { + if (err != null) { + return callback(err) + } + OutputCacheManager.collectOutputPdfSize( + result, + outputDir, + stats, + (err, result) => { + if (err) return callback(err, result) + + if (!Settings.enablePdfCaching || !request.enablePdfCaching) { + return callback(null, result) + } + + OutputCacheManager.saveStreamsInContentDir( + { stats, timings }, + result, + compileDir, + outputDir, + callback + ) + } + ) + } ) }) }, @@ -206,6 +239,107 @@ module.exports = OutputCacheManager = { }) }, + collectOutputPdfSize(outputFiles, outputDir, stats, callback) { + const outputFile = outputFiles.find((x) => x.path === 'output.pdf') + if (!outputFile) return callback(null, outputFiles) + const outputFilePath = Path.join( + outputDir, + OutputCacheManager.path(outputFile.build, outputFile.path) + ) + fs.stat(outputFilePath, (err, stat) => { + if (err) return callback(err, outputFiles) + + outputFile.size = stat.size + stats['pdf-size'] = outputFile.size + callback(null, outputFiles) + }) + }, + + saveStreamsInContentDir( + { stats, timings }, + outputFiles, + compileDir, + outputDir, + callback + ) { + const cacheRoot = Path.join(outputDir, OutputCacheManager.CONTENT_SUBDIR) + // check if content dir exists + OutputCacheManager.ensureContentDir(cacheRoot, function (err, contentDir) { + if (err) return callback(err, outputFiles) + + const outputFile = outputFiles.find((x) => x.path === 'output.pdf') + if (outputFile) { + // possibly we should copy the file from the build dir here + const outputFilePath = Path.join( + outputDir, + OutputCacheManager.path(outputFile.build, outputFile.path) + ) + const timer = new Metrics.Timer('compute-pdf-ranges') + ContentCacheManager.update(contentDir, outputFilePath, function ( + err, + ranges + ) { + if (err) return callback(err, outputFiles) + const [contentRanges, newContentRanges] = ranges + + if (Settings.enablePdfCachingDark) { + // In dark mode we are doing the computation only and do not emit + // any ranges to the frontend. + } else { + outputFile.contentId = Path.basename(contentDir) + outputFile.ranges = contentRanges + } + + timings['compute-pdf-caching'] = timer.done() + stats['pdf-caching-n-ranges'] = contentRanges.length + stats['pdf-caching-total-ranges-size'] = contentRanges.reduce( + (sum, next) => sum + (next.end - next.start), + 0 + ) + stats['pdf-caching-n-new-ranges'] = newContentRanges.length + stats['pdf-caching-new-ranges-size'] = newContentRanges.reduce( + (sum, next) => sum + (next.end - next.start), + 0 + ) + callback(null, outputFiles) + }) + } else { + callback(null, outputFiles) + } + }) + }, + + ensureContentDir(contentRoot, callback) { + fse.ensureDir(contentRoot, function (err) { + if (err != null) { + return callback(err) + } + fs.readdir(contentRoot, function (err, results) { + const dirs = results.sort() + const contentId = dirs.find((dir) => + OutputCacheManager.BUILD_REGEX.test(dir) + ) + if (contentId) { + callback(null, Path.join(contentRoot, contentId)) + } else { + // make a content directory + OutputCacheManager.generateBuildId(function (err, contentId) { + if (err) { + return callback(err) + } + const contentDir = Path.join(contentRoot, contentId) + fse.ensureDir(contentDir, function (err) { + if (err) { + return callback(err) + } + return callback(null, contentDir) + }) + }) + } + }) + }) + }, + archiveLogs(outputFiles, compileDir, outputDir, buildId, callback) { if (callback == null) { callback = function (error) {} diff --git a/services/clsi/app/js/RequestParser.js b/services/clsi/app/js/RequestParser.js index 4377204428..66c917aeed 100644 --- a/services/clsi/app/js/RequestParser.js +++ b/services/clsi/app/js/RequestParser.js @@ -50,6 +50,14 @@ module.exports = RequestParser = { type: 'string' } ) + response.enablePdfCaching = this._parseAttribute( + 'enablePdfCaching', + compile.options.enablePdfCaching, + { + default: false, + type: 'boolean' + } + ) response.timeout = this._parseAttribute( 'timeout', compile.options.timeout, diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index a0ad8433aa..b65a019365 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -63,7 +63,12 @@ module.exports = { texliveOpenoutAny: process.env.TEXLIVE_OPENOUT_ANY, sentry: { dsn: process.env.SENTRY_DSN - } + }, + + enablePdfCaching: process.env.ENABLE_PDF_CACHING === 'true', + enablePdfCachingDark: process.env.ENABLE_PDF_CACHING_DARK === 'true', + pdfCachingMinChunkSize: + parseInt(process.env.PDF_CACHING_MIN_CHUNK_SIZE, 10) || 1024 } if (process.env.ALLOWED_COMPILE_GROUPS) { diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 2237933754..245059ad98 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -29,6 +29,7 @@ services: NODE_ENV: test NODE_OPTIONS: "--unhandled-rejections=strict" TEXLIVE_IMAGE: + ENABLE_PDF_CACHING: "true" command: npm run test:acceptance:_run diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 6688000204..ee68a7c0ee 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -38,5 +38,6 @@ services: LOG_LEVEL: ERROR NODE_ENV: test NODE_OPTIONS: "--unhandled-rejections=strict" + ENABLE_PDF_CACHING: "true" command: npm run --silent test:acceptance diff --git a/services/clsi/test/acceptance/js/Stats.js b/services/clsi/test/acceptance/js/Stats.js new file mode 100644 index 0000000000..87b20b1cc9 --- /dev/null +++ b/services/clsi/test/acceptance/js/Stats.js @@ -0,0 +1,16 @@ +const request = require('request') +const Settings = require('settings-sharelatex') +after(function (done) { + request( + { + url: `${Settings.apis.clsi.url}/metrics` + }, + (err, response, body) => { + if (err) return done(err) + console.error('-- metrics --') + console.error(body) + console.error('-- metrics --') + done() + } + ) +}) diff --git a/services/clsi/test/acceptance/js/helpers/Client.js b/services/clsi/test/acceptance/js/helpers/Client.js index 43825ae415..c5814ff3ef 100644 --- a/services/clsi/test/acceptance/js/helpers/Client.js +++ b/services/clsi/test/acceptance/js/helpers/Client.js @@ -30,6 +30,10 @@ module.exports = Client = { if (callback == null) { callback = function (error, res, body) {} } + if (data) { + // Enable pdf caching unless disabled explicitly. + data.options = Object.assign({}, { enablePdfCaching: true }, data.options) + } return request.post( { url: `${this.host}/project/${project_id}/compile`, diff --git a/services/clsi/test/unit/js/CompileControllerTests.js b/services/clsi/test/unit/js/CompileControllerTests.js index 6f256d2955..6d6b34ee58 100644 --- a/services/clsi/test/unit/js/CompileControllerTests.js +++ b/services/clsi/test/unit/js/CompileControllerTests.js @@ -113,6 +113,8 @@ describe('CompileController', function () { this.ProjectPersistenceManager.markProjectAsJustAccessed = sinon .stub() .callsArg(1) + this.stats = { foo: 1 } + this.timings = { bar: 2 } this.res.status = sinon.stub().returnsThis() return (this.res.send = sinon.stub()) }) @@ -121,7 +123,7 @@ describe('CompileController', function () { beforeEach(function () { this.CompileManager.doCompileWithLock = sinon .stub() - .callsArgWith(1, null, this.output_files) + .yields(null, this.output_files, this.stats, this.timings) return this.CompileController.compile(this.req, this.res) }) @@ -150,12 +152,16 @@ describe('CompileController', function () { compile: { status: 'success', error: null, + stats: this.stats, + timings: this.timings, outputFiles: this.output_files.map((file) => { return { url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`, path: file.path, type: file.type, - build: file.build + build: file.build, + // gets dropped by JSON.stringify + contentId: undefined } }) } @@ -180,7 +186,7 @@ describe('CompileController', function () { ] this.CompileManager.doCompileWithLock = sinon .stub() - .callsArgWith(1, null, this.output_files) + .yields(null, this.output_files, this.stats, this.timings) this.CompileController.compile(this.req, this.res) }) @@ -191,12 +197,16 @@ describe('CompileController', function () { compile: { status: 'failure', error: null, + stats: this.stats, + timings: this.timings, outputFiles: this.output_files.map((file) => { return { url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`, path: file.path, type: file.type, - build: file.build + build: file.build, + // gets dropped by JSON.stringify + contentId: undefined } }) } @@ -220,7 +230,10 @@ describe('CompileController', function () { compile: { status: 'error', error: this.message, - outputFiles: [] + outputFiles: [], + // JSON.stringify will omit these + stats: undefined, + timings: undefined } }) .should.equal(true) @@ -244,7 +257,10 @@ describe('CompileController', function () { compile: { status: 'timedout', error: this.message, - outputFiles: [] + outputFiles: [], + // JSON.stringify will omit these + stats: undefined, + timings: undefined } }) .should.equal(true) @@ -266,7 +282,10 @@ describe('CompileController', function () { compile: { error: null, status: 'failure', - outputFiles: [] + outputFiles: [], + // JSON.stringify will omit these + stats: undefined, + timings: undefined } }) .should.equal(true) diff --git a/services/clsi/test/unit/js/CompileManagerTests.js b/services/clsi/test/unit/js/CompileManagerTests.js index 2d40bdd64a..97e318a4a6 100644 --- a/services/clsi/test/unit/js/CompileManagerTests.js +++ b/services/clsi/test/unit/js/CompileManagerTests.js @@ -71,7 +71,7 @@ describe('CompileManager', function () { this.compileDir = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}` this.CompileManager.doCompile = sinon .stub() - .callsArgWith(1, null, this.output_files) + .yields(null, this.output_files) return (this.LockManager.runWithLock = (lockFile, runner, callback) => runner((err, ...result) => callback(err, ...Array.from(result)))) }) @@ -172,10 +172,10 @@ describe('CompileManager', function () { this.LatexRunner.runLatex = sinon.stub().callsArg(2) this.OutputFileFinder.findOutputFiles = sinon .stub() - .callsArgWith(2, null, this.output_files) + .yields(null, this.output_files) this.OutputCacheManager.saveOutputFiles = sinon .stub() - .callsArgWith(3, null, this.build_files) + .yields(null, this.build_files) this.DraftModeManager.injectDraftMode = sinon.stub().callsArg(1) return (this.TikzManager.checkMainFile = sinon.stub().callsArg(3, false)) }) From 013d38552a22a50076ab22c37906b286bdec2477 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Wed, 12 May 2021 09:47:35 +0100 Subject: [PATCH 697/754] log the expiry timeout value when disk is low --- services/clsi/app/js/ProjectPersistenceManager.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/js/ProjectPersistenceManager.js b/services/clsi/app/js/ProjectPersistenceManager.js index 536630a8e0..d0fd4c2439 100644 --- a/services/clsi/app/js/ProjectPersistenceManager.js +++ b/services/clsi/app/js/ProjectPersistenceManager.js @@ -38,7 +38,10 @@ module.exports = ProjectPersistenceManager = { const lowerExpiry = ProjectPersistenceManager.EXPIRY_TIMEOUT * 0.9 if (lowDisk && Settings.project_cache_length_ms / 2 < lowerExpiry) { logger.warn( - { stats: stats }, + { + stats: stats, + newExpiryTimeoutInDays: (lowerExpiry / oneDay).toFixed(2) + }, 'disk running low on space, modifying EXPIRY_TIMEOUT' ) ProjectPersistenceManager.EXPIRY_TIMEOUT = lowerExpiry From ff2175e72796527122e914c6878f79e04fd2aeed Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Thu, 13 May 2021 14:56:15 +0100 Subject: [PATCH 698/754] add validation for express :content_id parameter --- services/clsi/app.js | 21 +++++++++++++++++++++ services/clsi/app/js/ContentCacheManager.js | 5 ++++- services/clsi/app/js/OutputCacheManager.js | 1 + 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/services/clsi/app.js b/services/clsi/app.js index 6266c86b16..77ab922987 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -29,6 +29,7 @@ Metrics.memory.monitor(logger) const ProjectPersistenceManager = require('./app/js/ProjectPersistenceManager') const OutputCacheManager = require('./app/js/OutputCacheManager') +const ContentCacheManager = require('./app/js/ContentCacheManager') require('./app/js/db').sync() @@ -76,6 +77,26 @@ app.param('build_id', function (req, res, next, buildId) { } }) +app.param('contentId', function (req, res, next, contentId) { + if ( + contentId != null + ? contentId.match(OutputCacheManager.CONTENT_REGEX) + : undefined + ) { + return next() + } else { + return next(new Error(`invalid content id ${contentId}`)) + } +}) + +app.param('hash', function (req, res, next, hash) { + if (hash != null ? hash.match(ContentCacheManager.HASH_REGEX) : undefined) { + return next() + } else { + return next(new Error(`invalid hash ${hash}`)) + } +}) + app.post( '/project/:project_id/compile', bodyParser.json({ limit: Settings.compileSizeLimit }), diff --git a/services/clsi/app/js/ContentCacheManager.js b/services/clsi/app/js/ContentCacheManager.js index 94ed9ebc00..099f0ee801 100644 --- a/services/clsi/app/js/ContentCacheManager.js +++ b/services/clsi/app/js/ContentCacheManager.js @@ -115,4 +115,7 @@ async function writePdfStream(dir, hash, buffers) { return true } -module.exports = { update: callbackify(update) } +module.exports = { + HASH_REGEX: /^[0-9a-f]{64}$/, + update: callbackify(update) +} diff --git a/services/clsi/app/js/OutputCacheManager.js b/services/clsi/app/js/OutputCacheManager.js index cd1bd883a2..fe23ae3e3c 100644 --- a/services/clsi/app/js/OutputCacheManager.js +++ b/services/clsi/app/js/OutputCacheManager.js @@ -34,6 +34,7 @@ module.exports = OutputCacheManager = { // build id is HEXDATE-HEXRANDOM from Date.now()and RandomBytes // for backwards compatibility, make the randombytes part optional BUILD_REGEX: /^[0-9a-f]+(-[0-9a-f]+)?$/, + CONTENT_REGEX: /^[0-9a-f]+(-[0-9a-f]+)?$/, CACHE_LIMIT: 2, // maximum number of cache directories CACHE_AGE: 60 * 60 * 1000, // up to one hour old From 44e0742aa33b37bddafb8cd10f25f1ba999de1ac Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 14 May 2021 15:49:20 +0100 Subject: [PATCH 699/754] use fse.copy for performance it uses the native fs.copyFile method --- services/clsi/app/js/UrlCache.js | 26 +++++---------------- services/clsi/test/unit/js/UrlCacheTests.js | 5 ++-- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/services/clsi/app/js/UrlCache.js b/services/clsi/app/js/UrlCache.js index e8ee10dc67..90c6486f98 100644 --- a/services/clsi/app/js/UrlCache.js +++ b/services/clsi/app/js/UrlCache.js @@ -19,6 +19,7 @@ const UrlFetcher = require('./UrlFetcher') const Settings = require('settings-sharelatex') const crypto = require('crypto') const fs = require('fs') +const fse = require('fs-extra') const logger = require('logger-sharelatex') const async = require('async') @@ -35,8 +36,12 @@ module.exports = UrlCache = { if (error != null) { return callback(error) } - return UrlCache._copyFile(pathToCachedUrl, destPath, function (error) { + return fse.copy(pathToCachedUrl, destPath, function (error) { if (error != null) { + logger.error( + { err: error, from: pathToCachedUrl, to: destPath }, + 'error copying file from cache' + ) return UrlCache._clearUrlDetails(project_id, url, () => callback(error) ) @@ -163,25 +168,6 @@ module.exports = UrlCache = { )}` }, - _copyFile(from, to, _callback) { - if (_callback == null) { - _callback = function (error) {} - } - const callbackOnce = function (error) { - if (error != null) { - logger.error({ err: error, from, to }, 'error copying file from cache') - } - _callback(error) - return (_callback = function () {}) - } - const writeStream = fs.createWriteStream(to) - const readStream = fs.createReadStream(from) - writeStream.on('error', callbackOnce) - readStream.on('error', callbackOnce) - writeStream.on('close', callbackOnce) - return writeStream.on('open', () => readStream.pipe(writeStream)) - }, - _clearUrlFromCache(project_id, url, callback) { if (callback == null) { callback = function (error) {} diff --git a/services/clsi/test/unit/js/UrlCacheTests.js b/services/clsi/test/unit/js/UrlCacheTests.js index 2b991245a9..7276b1325d 100644 --- a/services/clsi/test/unit/js/UrlCacheTests.js +++ b/services/clsi/test/unit/js/UrlCacheTests.js @@ -27,7 +27,8 @@ describe('UrlCache', function () { 'settings-sharelatex': (this.Settings = { path: { clsiCacheDir: '/cache/dir' } }), - fs: (this.fs = {}) + fs: (this.fs = {}), + 'fs-extra': (this.fse = { copy: sinon.stub().yields() }) } })) }) @@ -268,7 +269,7 @@ describe('UrlCache', function () { }) it('should copy the file to the new location', function () { - return this.UrlCache._copyFile + return this.fse.copy .calledWith(this.cachePath, this.destPath) .should.equal(true) }) From 24ef5786a0514da22114a486e922c901e5af1942 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Fri, 14 May 2021 15:58:11 +0100 Subject: [PATCH 700/754] upgrade fs-extra --- services/clsi/package-lock.json | 31 ++++++++++++++++--------------- services/clsi/package.json | 2 +- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index a4fb21c0f9..4a662040da 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -3075,13 +3075,13 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", + "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", "requires": { "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" } }, "fs-minipass": { @@ -3419,9 +3419,9 @@ } }, "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" }, "gtoken": { "version": "4.1.4", @@ -4032,11 +4032,12 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" } }, "jsprim": { @@ -7102,9 +7103,9 @@ "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" }, "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" }, "unpipe": { "version": "1.0.0", diff --git a/services/clsi/package.json b/services/clsi/package.json index eb194d9e49..3afa29c459 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -25,7 +25,7 @@ "diskusage": "^1.1.3", "dockerode": "^3.1.0", "express": "^4.17.1", - "fs-extra": "^8.1.0", + "fs-extra": "^10.0.0", "heapdump": "^0.3.15", "lockfile": "^1.0.4", "lodash": "^4.17.20", From 18e943742e90f3ae9b24a274b6396ba4224a4084 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Mon, 17 May 2021 09:25:29 +0100 Subject: [PATCH 701/754] [perf] drop useless synchronous syscall on hot path for writing docs --- services/clsi/app/js/ResourceWriter.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/services/clsi/app/js/ResourceWriter.js b/services/clsi/app/js/ResourceWriter.js index 1625ee15ab..d8cc2a4cab 100644 --- a/services/clsi/app/js/ResourceWriter.js +++ b/services/clsi/app/js/ResourceWriter.js @@ -334,12 +334,7 @@ module.exports = ResourceWriter = { } ) // try and continue compiling even if http resource can not be downloaded at this time } else { - const process = require('process') fs.writeFile(path, resource.content, callback) - try { - let result - return (result = fs.lstatSync(path)) - } catch (e) {} } }) }) From 371de76a4a2c65bdc34b48b009cb2d0fd7e7347e Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Mon, 17 May 2021 10:54:11 +0100 Subject: [PATCH 702/754] use fs.copyFile instead of fse.copy in UrlCache module --- services/clsi/app/js/UrlCache.js | 3 +-- services/clsi/test/unit/js/UrlCacheTests.js | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/services/clsi/app/js/UrlCache.js b/services/clsi/app/js/UrlCache.js index 90c6486f98..23afafaaa8 100644 --- a/services/clsi/app/js/UrlCache.js +++ b/services/clsi/app/js/UrlCache.js @@ -19,7 +19,6 @@ const UrlFetcher = require('./UrlFetcher') const Settings = require('settings-sharelatex') const crypto = require('crypto') const fs = require('fs') -const fse = require('fs-extra') const logger = require('logger-sharelatex') const async = require('async') @@ -36,7 +35,7 @@ module.exports = UrlCache = { if (error != null) { return callback(error) } - return fse.copy(pathToCachedUrl, destPath, function (error) { + return fs.copyFile(pathToCachedUrl, destPath, function (error) { if (error != null) { logger.error( { err: error, from: pathToCachedUrl, to: destPath }, diff --git a/services/clsi/test/unit/js/UrlCacheTests.js b/services/clsi/test/unit/js/UrlCacheTests.js index 7276b1325d..40652c5899 100644 --- a/services/clsi/test/unit/js/UrlCacheTests.js +++ b/services/clsi/test/unit/js/UrlCacheTests.js @@ -27,8 +27,7 @@ describe('UrlCache', function () { 'settings-sharelatex': (this.Settings = { path: { clsiCacheDir: '/cache/dir' } }), - fs: (this.fs = {}), - 'fs-extra': (this.fse = { copy: sinon.stub().yields() }) + fs: (this.fs = { copyFile: sinon.stub().yields() }) } })) }) @@ -249,7 +248,6 @@ describe('UrlCache', function () { beforeEach(function () { this.cachePath = 'path/to/cached/url' this.destPath = 'path/to/destination' - this.UrlCache._copyFile = sinon.stub().callsArg(2) this.UrlCache._ensureUrlIsInCache = sinon .stub() .callsArgWith(3, null, this.cachePath) @@ -269,7 +267,7 @@ describe('UrlCache', function () { }) it('should copy the file to the new location', function () { - return this.fse.copy + return this.fs.copyFile .calledWith(this.cachePath, this.destPath) .should.equal(true) }) From 7b6434ef81d42956f441ac2b32c7eb9e8c3a088d Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Tue, 11 May 2021 15:53:01 +0100 Subject: [PATCH 703/754] add benchmark script for hashing --- services/clsi/test/bench/hashbench.js | 71 +++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 services/clsi/test/bench/hashbench.js diff --git a/services/clsi/test/bench/hashbench.js b/services/clsi/test/bench/hashbench.js new file mode 100644 index 0000000000..9bb79c9089 --- /dev/null +++ b/services/clsi/test/bench/hashbench.js @@ -0,0 +1,71 @@ +const ContentCacheManager = require('../../app/js/ContentCacheManager') +const fs = require('fs') +const crypto = require('crypto') +const path = require('path') +const os = require('os') +const async = require('async') +const _createHash = crypto.createHash + +const files = process.argv.slice(2) + +function test(hashType, filePath, callback) { + // override the default hash in ContentCacheManager + crypto.createHash = function (hash) { + if (hashType === 'hmac-sha1') { + return crypto.createHmac('sha1', 'a secret') + } + hash = hashType + return _createHash(hash) + } + fs.mkdtemp(path.join(os.tmpdir(), 'pdfcache'), (err, dir) => { + if (err) { + return callback(err) + } + const t0 = process.hrtime.bigint() + ContentCacheManager.update(dir, filePath, (x) => { + const t1 = process.hrtime.bigint() + const cold = Number(t1 - t0) / 1e6 + ContentCacheManager.update(dir, filePath, (x) => { + const t2 = process.hrtime.bigint() + const warm = Number(t2 - t1) / 1e6 + fs.rmdir(dir, { recursive: true }, (err) => { + if (err) { + return callback(err) + } + console.log( + filePath, + 'hashType', + hashType, + 'cold-start', + cold.toFixed(2), + 'ms', + 'warm-start', + warm.toFixed(2), + 'ms' + ) + callback(null, [hashType, cold, warm]) + }) + }) + }) + }) +} + +var jobs = [] +files.forEach((file) => { + jobs.push((cb) => { + test('md5', file, cb) + }) + jobs.push((cb) => { + test('sha1', file, cb) + }) + jobs.push((cb) => { + test('hmac-sha1', file, cb) + }) + jobs.push((cb) => { + test('sha256', file, cb) + }) +}) + +async.series(jobs, () => { + console.log('DONE') +}) From cbeba4af001e3aabba29df9f6c26a8d7283babe2 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Wed, 12 May 2021 16:22:59 +0100 Subject: [PATCH 704/754] run the hash benchmark 10 times --- services/clsi/test/bench/hashbench.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/services/clsi/test/bench/hashbench.js b/services/clsi/test/bench/hashbench.js index 9bb79c9089..e87c3dd059 100644 --- a/services/clsi/test/bench/hashbench.js +++ b/services/clsi/test/bench/hashbench.js @@ -66,6 +66,13 @@ files.forEach((file) => { }) }) -async.series(jobs, () => { - console.log('DONE') -}) +async.timesSeries( + 10, + (n, cb) => { + console.log('run', n) + async.series(jobs, cb) + }, + () => { + console.log('DONE') + } +) From 939ad3af31ea6ff2007b630cd8259606f2edbc8a Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Mon, 17 May 2021 12:00:32 +0100 Subject: [PATCH 705/754] include UV_THREADPOOL_SIZE in benchmark logs --- services/clsi/test/bench/hashbench.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/clsi/test/bench/hashbench.js b/services/clsi/test/bench/hashbench.js index e87c3dd059..a8ac898eae 100644 --- a/services/clsi/test/bench/hashbench.js +++ b/services/clsi/test/bench/hashbench.js @@ -33,6 +33,8 @@ function test(hashType, filePath, callback) { return callback(err) } console.log( + 'uvthreads', + process.env.UV_THREADPOOL_SIZE, filePath, 'hashType', hashType, From 8c80ddd27a889fc6a736959676e19508edb5c02c Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Mon, 17 May 2021 12:00:57 +0100 Subject: [PATCH 706/754] remove unnecessary console.log from hash benchmark --- services/clsi/test/bench/hashbench.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/services/clsi/test/bench/hashbench.js b/services/clsi/test/bench/hashbench.js index a8ac898eae..bbd9a02855 100644 --- a/services/clsi/test/bench/hashbench.js +++ b/services/clsi/test/bench/hashbench.js @@ -68,13 +68,6 @@ files.forEach((file) => { }) }) -async.timesSeries( - 10, - (n, cb) => { - console.log('run', n) - async.series(jobs, cb) - }, - () => { - console.log('DONE') - } -) +async.timesSeries(10, (n, cb) => { + async.series(jobs, cb) +}) From 6b9c8bced66853ed36379204a70b30ac5be223d2 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Mon, 17 May 2021 14:18:07 +0100 Subject: [PATCH 707/754] [ContentCacheManager] write streams to disk atomically Use an intermediate file for writing to disk, then rename to the target. --- services/clsi/app/js/ContentCacheManager.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/services/clsi/app/js/ContentCacheManager.js b/services/clsi/app/js/ContentCacheManager.js index 94ed9ebc00..a32a952ca5 100644 --- a/services/clsi/app/js/ContentCacheManager.js +++ b/services/clsi/app/js/ContentCacheManager.js @@ -100,17 +100,27 @@ async function writePdfStream(dir, hash, buffers) { // ETags used for client side caching via browser internals. return false } catch (e) {} - const file = await fs.promises.open(filename, 'w') + const atomicWriteFilename = filename + '~' + const file = await fs.promises.open(atomicWriteFilename, 'w') if (Settings.enablePdfCachingDark) { // Write an empty file in dark mode. buffers = [] } try { - for (const buffer of buffers) { - await file.write(buffer) + try { + for (const buffer of buffers) { + await file.write(buffer) + } + } finally { + await file.close() + } + await fs.promises.rename(atomicWriteFilename, filename) + } catch (err) { + try { + await fs.promises.unlink(atomicWriteFilename) + } catch (_) { + throw err } - } finally { - await file.close() } return true } From 224ae0c25468bf47f77c8fdfb601ffc3d0bde972 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Mon, 17 May 2021 14:07:37 +0100 Subject: [PATCH 708/754] [ContentCacheManager] add support for stream detection across chunks Retain a small part (6 or 9 bytes) of each chunk in memory for providing the next iteration with enough context for finding the start/end marker of a range. --- services/clsi/app/js/ContentCacheManager.js | 31 +++- .../test/unit/js/ContentCacheManagerTests.js | 160 ++++++++++++++++++ 2 files changed, 186 insertions(+), 5 deletions(-) create mode 100644 services/clsi/test/unit/js/ContentCacheManagerTests.js diff --git a/services/clsi/app/js/ContentCacheManager.js b/services/clsi/app/js/ContentCacheManager.js index 94ed9ebc00..45fef1787c 100644 --- a/services/clsi/app/js/ContentCacheManager.js +++ b/services/clsi/app/js/ContentCacheManager.js @@ -10,6 +10,11 @@ const Settings = require('settings-sharelatex') const MIN_CHUNK_SIZE = Settings.pdfCachingMinChunkSize +const START_OF_STREAM_MARKER = 'stream' +const END_OF_STREAM_MARKER = 'endstream' +const START_OF_STREAM_MARKER_LENGTH = START_OF_STREAM_MARKER.length +const END_OF_STREAM_MARKER_LENGTH = END_OF_STREAM_MARKER.length + /** * * @param {String} contentDir path to directory where content hash files are cached @@ -41,15 +46,17 @@ class PdfStreamsExtractor { this.inStream = false this.streamStartIndex = 0 this.buffers = [] + this.lastChunk = Buffer.alloc(0) } consume(chunk) { let chunkIndex = 0 const pdfStreams = [] + chunk = Buffer.concat([this.lastChunk, chunk]) while (true) { if (!this.inStream) { // Not in a stream, look for stream start - const index = chunk.indexOf('stream', chunkIndex) + const index = chunk.indexOf(START_OF_STREAM_MARKER, chunkIndex) if (index === -1) { // Couldn't find stream start break @@ -60,13 +67,12 @@ class PdfStreamsExtractor { chunkIndex = index } else { // In a stream, look for stream end - const index = chunk.indexOf('endstream', chunkIndex) + const index = chunk.indexOf(END_OF_STREAM_MARKER, chunkIndex) if (index === -1) { - this.buffers.push(chunk.slice(chunkIndex)) break } // add "endstream" part - const endIndex = index + 9 + const endIndex = index + END_OF_STREAM_MARKER_LENGTH this.buffers.push(chunk.slice(chunkIndex, endIndex)) pdfStreams.push({ start: this.streamStartIndex, @@ -78,7 +84,22 @@ class PdfStreamsExtractor { chunkIndex = endIndex } } - this.fileIndex += chunk.length + + const remaining = chunk.length - chunkIndex + const nextMarkerLength = this.inStream + ? END_OF_STREAM_MARKER_LENGTH + : START_OF_STREAM_MARKER_LENGTH + if (remaining > nextMarkerLength) { + const retainMarkerSection = chunk.length - nextMarkerLength + if (this.inStream) { + this.buffers.push(chunk.slice(chunkIndex, retainMarkerSection)) + } + this.lastChunk = chunk.slice(retainMarkerSection) + this.fileIndex += retainMarkerSection + } else { + this.lastChunk = chunk.slice(chunkIndex) + this.fileIndex += chunkIndex + } return pdfStreams } } diff --git a/services/clsi/test/unit/js/ContentCacheManagerTests.js b/services/clsi/test/unit/js/ContentCacheManagerTests.js new file mode 100644 index 0000000000..90fe60c52c --- /dev/null +++ b/services/clsi/test/unit/js/ContentCacheManagerTests.js @@ -0,0 +1,160 @@ +const Path = require('path') +const crypto = require('crypto') +const { Readable } = require('stream') +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +const { expect } = require('chai') + +const MODULE_PATH = '../../../app/js/ContentCacheManager' + +class FakeFile { + constructor() { + this.closed = false + this.contents = [] + } + + async write(blob) { + this.contents.push(blob) + return this + } + + async close() { + this.closed = true + return this + } + + toJSON() { + return { + contents: Buffer.concat(this.contents).toString(), + closed: this.closed + } + } +} + +function hash(blob) { + const hash = crypto.createHash('sha256') + hash.update(blob) + return hash.digest('hex') +} + +describe('ContentCacheManager', function () { + let contentDir, pdfPath + let ContentCacheManager, fs, files, Settings + function load() { + ContentCacheManager = SandboxedModule.require(MODULE_PATH, { + requires: { + fs, + 'settings-sharelatex': Settings + } + }) + } + let contentRanges, newContentRanges + function run(filePath, done) { + ContentCacheManager.update(contentDir, filePath, (err, ranges) => { + if (err) return done(err) + ;[contentRanges, newContentRanges] = ranges + done() + }) + } + + beforeEach(function () { + contentDir = + '/app/output/602cee6f6460fca0ba7921e6/content/1797a7f48f9-5abc1998509dea1f' + pdfPath = + '/app/output/602cee6f6460fca0ba7921e6/generated-files/1797a7f48ea-8ac6805139f43351/output.pdf' + Settings = { + pdfCachingMinChunkSize: 1024, + enablePdfCachingDark: false + } + files = {} + fs = { + createReadStream: sinon.stub().returns(Readable.from([])), + promises: { + async open(name) { + files[name] = new FakeFile() + return files[name] + }, + async stat(name) { + if (!files[name]) { + throw new Error() + } + }, + rename: sinon.stub().resolves(), + unlink: sinon.stub().resolves() + } + } + }) + + describe('with a small minChunkSize', function () { + beforeEach(function () { + Settings.pdfCachingMinChunkSize = 1 + load() + }) + + describe('when the ranges are split across chunks', function () { + const RANGE_1 = 'stream123endstream' + const RANGE_2 = 'stream(|)endstream' + const RANGE_3 = 'stream!$%endstream' + beforeEach(function (done) { + fs.createReadStream + .withArgs(pdfPath) + .returns( + Readable.from([ + Buffer.from('abcstr'), + Buffer.from('eam123endstreamABC'), + Buffer.from('str'), + Buffer.from('eam(|'), + Buffer.from(')end'), + Buffer.from('stream-_~stream!$%endstream') + ]) + ) + run(pdfPath, done) + }) + + it('should produce three ranges', function () { + expect(contentRanges).to.have.length(3) + }) + + it('should find the correct offsets', function () { + expect(contentRanges).to.deep.equal([ + { + start: 3, + end: 21, + hash: hash(RANGE_1) + }, + { + start: 24, + end: 42, + hash: hash(RANGE_2) + }, + { + start: 45, + end: 63, + hash: hash(RANGE_3) + } + ]) + }) + + it('should store the contents', function () { + expect(JSON.parse(JSON.stringify(files))).to.deep.equal({ + [Path.join(contentDir, hash(RANGE_1))]: { + contents: RANGE_1, + closed: true + }, + [Path.join(contentDir, hash(RANGE_2))]: { + contents: RANGE_2, + closed: true + }, + [Path.join(contentDir, hash(RANGE_3))]: { + contents: RANGE_3, + closed: true + } + }) + }) + + it('should mark all ranges as new', function () { + expect(contentRanges).to.deep.equal(newContentRanges) + }) + }) + }) +}) From bc1ed82c6c458fd25318b27e6296768cca20a6ee Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 18 May 2021 09:50:13 +0100 Subject: [PATCH 709/754] [ContentCacheManager] skip writing of duplicate streams --- services/clsi/app/js/ContentCacheManager.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/services/clsi/app/js/ContentCacheManager.js b/services/clsi/app/js/ContentCacheManager.js index 94ed9ebc00..f631fa6a5f 100644 --- a/services/clsi/app/js/ContentCacheManager.js +++ b/services/clsi/app/js/ContentCacheManager.js @@ -20,13 +20,20 @@ async function update(contentDir, filePath) { const extractor = new PdfStreamsExtractor() const ranges = [] const newRanges = [] + const seenHashes = new Set() for await (const chunk of stream) { const pdfStreams = extractor.consume(chunk) for (const pdfStream of pdfStreams) { if (pdfStream.end - pdfStream.start < MIN_CHUNK_SIZE) continue const hash = pdfStreamHash(pdfStream.buffers) + const range = { start: pdfStream.start, end: pdfStream.end, hash } ranges.push(range) + + // Optimization: Skip writing of duplicate streams. + if (seenHashes.has(hash)) continue + seenHashes.add(hash) + if (await writePdfStream(contentDir, hash, pdfStream.buffers)) { newRanges.push(range) } From 567d02881dae48ad2f8691d05c24f1bf5b538768 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 18 May 2021 11:09:30 +0100 Subject: [PATCH 710/754] [misc] fix unit tests following the merge of atomic writes --- services/clsi/test/unit/js/ContentCacheManagerTests.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/services/clsi/test/unit/js/ContentCacheManagerTests.js b/services/clsi/test/unit/js/ContentCacheManagerTests.js index 90fe60c52c..a7b25eacbc 100644 --- a/services/clsi/test/unit/js/ContentCacheManagerTests.js +++ b/services/clsi/test/unit/js/ContentCacheManagerTests.js @@ -79,7 +79,13 @@ describe('ContentCacheManager', function () { throw new Error() } }, - rename: sinon.stub().resolves(), + async rename(oldName, newName) { + if (!files[oldName]) { + throw new Error() + } + files[newName] = files[oldName] + delete files[oldName] + }, unlink: sinon.stub().resolves() } } From d493238eaf51882ddf540e75fd41387ebed41563 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Tue, 18 May 2021 16:25:24 +0100 Subject: [PATCH 711/754] wip expire old hash files --- services/clsi/app/js/ContentCacheManager.js | 40 +++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/services/clsi/app/js/ContentCacheManager.js b/services/clsi/app/js/ContentCacheManager.js index 2096e1afc7..f5ccc503b5 100644 --- a/services/clsi/app/js/ContentCacheManager.js +++ b/services/clsi/app/js/ContentCacheManager.js @@ -26,6 +26,10 @@ async function update(contentDir, filePath) { const ranges = [] const newRanges = [] const seenHashes = new Set() + // keep track of hashes expire old ones when they reach a generation > N. + const tracker = new HashFileTracker() + await loadState(contentDir, tracker) + for await (const chunk of stream) { const pdfStreams = extractor.consume(chunk) for (const pdfStream of pdfStreams) { @@ -44,9 +48,45 @@ async function update(contentDir, filePath) { } } } + const expiredHashes = tracker.update(ranges).findStale(5) + await deleteHashFiles(expiredHashes) return [ranges, newRanges] } +class HashFileTracker { + constructor(contentDir) { + this.hashAge = new Map() + } + + update(ranges) { + for (const [hash, age] of this.hashAge) { + this.hashAge.set(hash, age + 1) + } + for (const range in ranges) { + this.hashAge.set(range.hash, 0) + } + } + + findStale(maxAge) { + var stale = [] + for (const [hash, age] of this.hashAge) { + if (age > maxAge) { + stale.push(hash) + this.hashAge.delete(hash) + } + } + return stale + } +} + +async function loadState(contentDir, tracker) { + +} + +async function deleteHashFiles(n) { + // delete any hash file older than N generations +} + class PdfStreamsExtractor { constructor() { this.fileIndex = 0 From 011a22872737de9c9616e6e15aa8aabe3fad74a6 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 18 May 2021 17:32:21 +0100 Subject: [PATCH 712/754] [misc] install p-limit and nodemon --- services/clsi/package-lock.json | 812 +++++++++++++++++++++++++++++++- services/clsi/package.json | 2 + 2 files changed, 809 insertions(+), 5 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index 4a662040da..773d9f4319 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -1066,6 +1066,12 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true + }, "@sinonjs/commons": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.1.tgz", @@ -1127,6 +1133,15 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "requires": { + "defer-to-connect": "^1.0.1" + } + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -1276,6 +1291,55 @@ "uri-js": "^4.2.2" } }, + "ansi-align": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", + "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", + "dev": true, + "requires": { + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "ansi-colors": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", @@ -1587,6 +1651,105 @@ "integrity": "sha1-tcCeF8rNET0Rt7s+04TMASmU2Gs=", "dev": true }, + "boxen": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", + "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", + "dev": true, + "requires": { + "ansi-align": "^3.0.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "cli-boxes": "^2.2.0", + "string-width": "^4.1.0", + "term-size": "^2.1.0", + "type-fest": "^0.8.1", + "widest-line": "^3.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1652,6 +1815,38 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true + } + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1760,6 +1955,18 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", "integrity": "sha1-VHJri4//TfBTxCGH6AH7RBLfFJQ=" }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true + }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -1826,6 +2033,15 @@ } } }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, "cls-bluebird": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz", @@ -1931,6 +2147,20 @@ } } }, + "configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dev": true, + "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" + } + }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -2014,6 +2244,12 @@ "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" }, + "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==", + "dev": true + }, "d64": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/d64/-/d64-1.0.0.tgz", @@ -2047,6 +2283,15 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", @@ -2067,6 +2312,12 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "dev": true + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -2206,6 +2457,12 @@ } } }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -2315,6 +2572,12 @@ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" }, + "escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "dev": true + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -3170,6 +3433,15 @@ "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", "dev": true }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -3200,6 +3472,23 @@ "is-glob": "^4.0.1" } }, + "global-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", + "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", + "dev": true, + "requires": { + "ini": "1.3.7" + }, + "dependencies": { + "ini": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", + "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", + "dev": true + } + } + }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -3418,6 +3707,25 @@ "node-forge": "^0.9.0" } }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + } + }, "graceful-fs": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", @@ -3509,6 +3817,12 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, + "has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "dev": true + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -3541,6 +3855,12 @@ "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", "dev": true }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, "http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", @@ -3647,6 +3967,12 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, "ignore-walk": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", @@ -3665,6 +3991,12 @@ "resolve-from": "^4.0.0" } }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -3874,6 +4206,15 @@ "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", "dev": true }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, "is-date-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", @@ -3903,6 +4244,22 @@ "is-extglob": "^2.1.1" } }, + "is-installed-globally": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", + "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "dev": true, + "requires": { + "global-dirs": "^2.0.1", + "is-path-inside": "^3.0.1" + } + }, + "is-npm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", + "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", + "dev": true + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3914,6 +4271,12 @@ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", @@ -3959,6 +4322,12 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", + "dev": true + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -4010,6 +4379,12 @@ "bignumber.js": "^7.0.0" } }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -4086,6 +4461,24 @@ "safe-buffer": "^5.0.1" } }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "requires": { + "json-buffer": "3.0.0" + } + }, + "latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "dev": true, + "requires": { + "package-json": "^6.3.0" + } + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -4286,6 +4679,12 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -4294,6 +4693,23 @@ "yallist": "^3.0.2" } }, + "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==", + "dev": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "make-plural": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-4.3.0.tgz", @@ -4396,6 +4812,12 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -4528,6 +4950,17 @@ "dev": true, "requires": { "p-limit": "^2.0.0" + }, + "dependencies": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + } } } } @@ -4750,6 +5183,56 @@ "tar": "^4" } }, + "nodemon": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.7.tgz", + "integrity": "sha512-XHzK69Awgnec9UzHr1kc8EomQh4sjTQ8oRf8TsGrSmHDx9/UmiGG9E/mM3BuTfNeFwdNBvrqQq/RHL0xIeyFOA==", + "dev": true, + "requires": { + "chokidar": "^3.2.2", + "debug": "^3.2.6", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.7", + "semver": "^5.7.1", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.3", + "update-notifier": "^4.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "nopt": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", @@ -4777,6 +5260,12 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "dev": true + }, "npm-bundled": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz", @@ -4950,13 +5439,18 @@ "os-tmpdir": "^1.0.0" } }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true + }, "p-limit": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", - "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", - "dev": true, + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "requires": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" } }, "p-locate": { @@ -4991,6 +5485,26 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "dev": true, + "requires": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -5226,6 +5740,12 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true + }, "prettier": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", @@ -5791,6 +6311,17 @@ "dev": true, "requires": { "p-limit": "^2.2.0" + }, + "dependencies": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + } } }, "path-exists": { @@ -5929,6 +6460,12 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -5976,6 +6513,15 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew=" }, + "pupa": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", + "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "dev": true, + "requires": { + "escape-goat": "^2.0.0" + } + }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -6133,6 +6679,24 @@ "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", "dev": true }, + "registry-auth-token": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", + "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", + "dev": true, + "requires": { + "rc": "^1.2.8" + } + }, + "registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "dev": true, + "requires": { + "rc": "^1.2.8" + } + }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -6239,6 +6803,15 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, + "requires": { + "lowercase-keys": "^1.0.0" + } + }, "restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -6343,6 +6916,23 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", "integrity": "sha1-fnQlb7qknHWqfHogXMInmcrIAAQ=" }, + "semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dev": true, + "requires": { + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -6952,6 +7542,12 @@ } } }, + "term-size": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", + "dev": true + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -7002,6 +7598,12 @@ "resolved": "https://registry.npmjs.org/to-no-case/-/to-no-case-1.0.2.tgz", "integrity": "sha1-xyKQcWTvaxeBMsjmmTAhLRtKoWo=" }, + "to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true + }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -7037,6 +7639,26 @@ "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "requires": { + "nopt": "~1.0.10" + }, + "dependencies": { + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1" + } + } + } + }, "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", @@ -7091,17 +7713,44 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "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==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, "typescript": { "version": "3.7.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", "dev": true }, + "undefsafe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", + "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", + "dev": true, + "requires": { + "debug": "^2.2.0" + } + }, "underscore": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "requires": { + "crypto-random-string": "^2.0.0" + } + }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -7112,6 +7761,78 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "update-notifier": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", + "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", + "dev": true, + "requires": { + "boxen": "^4.2.0", + "chalk": "^3.0.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.3.1", + "is-npm": "^4.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.0.0", + "pupa": "^2.0.1", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -7120,6 +7841,15 @@ "punycode": "^2.1.0" } }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dev": true, + "requires": { + "prepend-http": "^2.0.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -7314,6 +8044,49 @@ "string-width": "^1.0.2 || 2" } }, + "widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "requires": { + "string-width": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, "wkx": { "version": "0.4.8", "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.8.tgz", @@ -7409,6 +8182,24 @@ } } }, + "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==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true + }, "xregexp": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz", @@ -7491,6 +8282,17 @@ "dev": true, "requires": { "p-limit": "^2.0.0" + }, + "dependencies": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + } } }, "string-width": { diff --git a/services/clsi/package.json b/services/clsi/package.json index 3afa29c459..dac3ba9dbb 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -31,6 +31,7 @@ "lodash": "^4.17.20", "logger-sharelatex": "^2.2.0", "mysql": "^2.18.1", + "p-limit": "^3.1.0", "request": "^2.88.2", "sequelize": "^5.21.5", "settings-sharelatex": "^1.1.0", @@ -58,6 +59,7 @@ "eslint-plugin-react": "^7.19.0", "eslint-plugin-standard": "^4.0.1", "mocha": "^7.1.0", + "nodemon": "^2.0.7", "prettier": "^2.0.0", "prettier-eslint-cli": "^5.0.0", "sandboxed-module": "^2.0.3", From 7aeeb5a5a9be028d78bea0b32706d7ca3c8de922 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 18 May 2021 18:06:15 +0100 Subject: [PATCH 713/754] [ContentCacheManager] finish tracking of ranges across builds --- services/clsi/app/js/ContentCacheManager.js | 93 ++++++-- services/clsi/app/js/ContentCacheMetrics.js | 5 +- services/clsi/app/js/OutputCacheManager.js | 5 +- .../test/unit/js/ContentCacheManagerTests.js | 203 ++++++++++++++++-- 4 files changed, 272 insertions(+), 34 deletions(-) diff --git a/services/clsi/app/js/ContentCacheManager.js b/services/clsi/app/js/ContentCacheManager.js index f5ccc503b5..60c68b9ba6 100644 --- a/services/clsi/app/js/ContentCacheManager.js +++ b/services/clsi/app/js/ContentCacheManager.js @@ -7,6 +7,7 @@ const fs = require('fs') const crypto = require('crypto') const Path = require('path') const Settings = require('settings-sharelatex') +const pLimit = require('p-limit') const MIN_CHUNK_SIZE = Settings.pdfCachingMinChunkSize @@ -27,9 +28,7 @@ async function update(contentDir, filePath) { const newRanges = [] const seenHashes = new Set() // keep track of hashes expire old ones when they reach a generation > N. - const tracker = new HashFileTracker() - await loadState(contentDir, tracker) - + const tracker = await HashFileTracker.from(contentDir) for await (const chunk of stream) { const pdfStreams = extractor.consume(chunk) for (const pdfStream of pdfStreams) { @@ -48,43 +47,98 @@ async function update(contentDir, filePath) { } } } - const expiredHashes = tracker.update(ranges).findStale(5) - await deleteHashFiles(expiredHashes) - return [ranges, newRanges] + tracker.update(ranges, newRanges) + const reclaimedSpace = await tracker.deleteStaleHashes(5) + await tracker.flush() + return [ranges, newRanges, reclaimedSpace] +} + +function getStatePath(contentDir) { + return Path.join(contentDir, '.state.v0.json') } class HashFileTracker { - constructor(contentDir) { - this.hashAge = new Map() + constructor(contentDir, { hashAge = [], hashSize = [] }) { + this.contentDir = contentDir + this.hashAge = new Map(hashAge) + this.hashSize = new Map(hashSize) } - update(ranges) { + static async from(contentDir) { + const statePath = getStatePath(contentDir) + let state = {} + try { + const blob = await fs.promises.readFile(statePath) + state = JSON.parse(blob) + } catch (e) {} + return new HashFileTracker(contentDir, state) + } + + update(ranges, newRanges) { for (const [hash, age] of this.hashAge) { this.hashAge.set(hash, age + 1) } - for (const range in ranges) { + for (const range of ranges) { this.hashAge.set(range.hash, 0) } + for (const range of newRanges) { + this.hashSize.set(range.hash, range.end - range.start) + } + return this } findStale(maxAge) { - var stale = [] + const stale = [] for (const [hash, age] of this.hashAge) { if (age > maxAge) { stale.push(hash) - this.hashAge.delete(hash) } } return stale } -} -async function loadState(contentDir, tracker) { + async flush() { + const statePath = getStatePath(this.contentDir) + const blob = JSON.stringify({ + hashAge: Array.from(this.hashAge.entries()), + hashSize: Array.from(this.hashSize.entries()) + }) + const atomicWrite = statePath + '~' + try { + await fs.promises.writeFile(atomicWrite, blob) + } catch (err) { + try { + await fs.promises.unlink(atomicWrite) + } catch (e) {} + throw err + } + try { + await fs.promises.rename(atomicWrite, statePath) + } catch (err) { + try { + await fs.promises.unlink(atomicWrite) + } catch (e) {} + throw err + } + } -} + async deleteStaleHashes(n) { + // delete any hash file older than N generations + const hashes = this.findStale(n) -async function deleteHashFiles(n) { - // delete any hash file older than N generations + let reclaimedSpace = 0 + if (hashes.length === 0) { + return reclaimedSpace + } + + await promiseMapWithLimit(10, hashes, async (hash) => { + await fs.promises.unlink(Path.join(this.contentDir, hash)) + this.hashAge.delete(hash) + reclaimedSpace += this.hashSize.get(hash) + this.hashSize.delete(hash) + }) + return reclaimedSpace + } } class PdfStreamsExtractor { @@ -193,6 +247,11 @@ async function writePdfStream(dir, hash, buffers) { return true } +function promiseMapWithLimit(concurrency, array, fn) { + const limit = pLimit(concurrency) + return Promise.all(array.map((x) => limit(() => fn(x)))) +} + module.exports = { HASH_REGEX: /^[0-9a-f]{64}$/, update: callbackify(update) diff --git a/services/clsi/app/js/ContentCacheMetrics.js b/services/clsi/app/js/ContentCacheMetrics.js index 04b475f827..f5f9188ddc 100644 --- a/services/clsi/app/js/ContentCacheMetrics.js +++ b/services/clsi/app/js/ContentCacheMetrics.js @@ -72,7 +72,10 @@ function emitPdfCachingStats(stats, timings) { // How much space do the ranges use? // This will accumulate the ranges size over time, skipping already written ranges. - Metrics.summary('pdf-ranges-disk-size', stats['pdf-caching-new-ranges-size']) + Metrics.summary( + 'pdf-ranges-disk-size', + stats['pdf-caching-new-ranges-size'] - stats['pdf-caching-reclaimed-space'] + ) } module.exports = { diff --git a/services/clsi/app/js/OutputCacheManager.js b/services/clsi/app/js/OutputCacheManager.js index fe23ae3e3c..e6167570f8 100644 --- a/services/clsi/app/js/OutputCacheManager.js +++ b/services/clsi/app/js/OutputCacheManager.js @@ -278,10 +278,10 @@ module.exports = OutputCacheManager = { const timer = new Metrics.Timer('compute-pdf-ranges') ContentCacheManager.update(contentDir, outputFilePath, function ( err, - ranges + result ) { if (err) return callback(err, outputFiles) - const [contentRanges, newContentRanges] = ranges + const [contentRanges, newContentRanges, reclaimedSpace] = result if (Settings.enablePdfCachingDark) { // In dark mode we are doing the computation only and do not emit @@ -302,6 +302,7 @@ module.exports = OutputCacheManager = { (sum, next) => sum + (next.end - next.start), 0 ) + stats['pdf-caching-reclaimed-space'] = reclaimedSpace callback(null, outputFiles) }) } else { diff --git a/services/clsi/test/unit/js/ContentCacheManagerTests.js b/services/clsi/test/unit/js/ContentCacheManagerTests.js index a7b25eacbc..35f22fffae 100644 --- a/services/clsi/test/unit/js/ContentCacheManagerTests.js +++ b/services/clsi/test/unit/js/ContentCacheManagerTests.js @@ -48,16 +48,19 @@ describe('ContentCacheManager', function () { } }) } - let contentRanges, newContentRanges + let contentRanges, newContentRanges, reclaimed function run(filePath, done) { ContentCacheManager.update(contentDir, filePath, (err, ranges) => { if (err) return done(err) - ;[contentRanges, newContentRanges] = ranges + let newlyReclaimed + ;[contentRanges, newContentRanges, newlyReclaimed] = ranges + reclaimed += newlyReclaimed done() }) } beforeEach(function () { + reclaimed = 0 contentDir = '/app/output/602cee6f6460fca0ba7921e6/content/1797a7f48f9-5abc1998509dea1f' pdfPath = @@ -70,6 +73,18 @@ describe('ContentCacheManager', function () { fs = { createReadStream: sinon.stub().returns(Readable.from([])), promises: { + async writeFile(name, blob) { + const file = new FakeFile() + await file.write(Buffer.from(blob)) + await file.close() + files[name] = file + }, + async readFile(name) { + if (!files[name]) { + throw new Error() + } + return files[name].toJSON().contents + }, async open(name) { files[name] = new FakeFile() return files[name] @@ -86,7 +101,12 @@ describe('ContentCacheManager', function () { files[newName] = files[oldName] delete files[oldName] }, - unlink: sinon.stub().resolves() + async unlink(name) { + if (!files[name]) { + throw new Error() + } + delete files[name] + } } } }) @@ -99,9 +119,12 @@ describe('ContentCacheManager', function () { describe('when the ranges are split across chunks', function () { const RANGE_1 = 'stream123endstream' - const RANGE_2 = 'stream(|)endstream' - const RANGE_3 = 'stream!$%endstream' - beforeEach(function (done) { + const RANGE_2 = 'stream(||)endstream' + const RANGE_3 = 'stream!$%/=endstream' + const h1 = hash(RANGE_1) + const h2 = hash(RANGE_2) + const h3 = hash(RANGE_3) + function runWithSplitStream(done) { fs.createReadStream .withArgs(pdfPath) .returns( @@ -109,12 +132,15 @@ describe('ContentCacheManager', function () { Buffer.from('abcstr'), Buffer.from('eam123endstreamABC'), Buffer.from('str'), - Buffer.from('eam(|'), + Buffer.from('eam(||'), Buffer.from(')end'), - Buffer.from('stream-_~stream!$%endstream') + Buffer.from('stream-_~stream!$%/=endstream') ]) ) run(pdfPath, done) + } + beforeEach(function (done) { + runWithSplitStream(done) }) it('should produce three ranges', function () { @@ -130,12 +156,12 @@ describe('ContentCacheManager', function () { }, { start: 24, - end: 42, + end: 43, hash: hash(RANGE_2) }, { - start: 45, - end: 63, + start: 46, + end: 66, hash: hash(RANGE_3) } ]) @@ -143,17 +169,32 @@ describe('ContentCacheManager', function () { it('should store the contents', function () { expect(JSON.parse(JSON.stringify(files))).to.deep.equal({ - [Path.join(contentDir, hash(RANGE_1))]: { + [Path.join(contentDir, h1)]: { contents: RANGE_1, closed: true }, - [Path.join(contentDir, hash(RANGE_2))]: { + [Path.join(contentDir, h2)]: { contents: RANGE_2, closed: true }, - [Path.join(contentDir, hash(RANGE_3))]: { + [Path.join(contentDir, h3)]: { contents: RANGE_3, closed: true + }, + [Path.join(contentDir, '.state.v0.json')]: { + contents: JSON.stringify({ + hashAge: [ + [h1, 0], + [h2, 0], + [h3, 0] + ], + hashSize: [ + [h1, 18], + [h2, 19], + [h3, 20] + ] + }), + closed: true } }) }) @@ -161,6 +202,140 @@ describe('ContentCacheManager', function () { it('should mark all ranges as new', function () { expect(contentRanges).to.deep.equal(newContentRanges) }) + + describe('when re-running with one stream removed', function () { + function runWithOneSplitStreamRemoved(done) { + fs.createReadStream + .withArgs(pdfPath) + .returns( + Readable.from([ + Buffer.from('abcstr'), + Buffer.from('eam123endstreamABC'), + Buffer.from('stream!$%/=endstream') + ]) + ) + run(pdfPath, done) + } + beforeEach(function (done) { + runWithOneSplitStreamRemoved(done) + }) + + it('should produce two ranges', function () { + expect(contentRanges).to.have.length(2) + }) + + it('should find the correct offsets', function () { + expect(contentRanges).to.deep.equal([ + { + start: 3, + end: 21, + hash: hash(RANGE_1) + }, + { + start: 24, + end: 44, + hash: hash(RANGE_3) + } + ]) + }) + + it('should update the age of the 2nd range', function () { + expect(JSON.parse(JSON.stringify(files))).to.deep.equal({ + [Path.join(contentDir, h1)]: { + contents: RANGE_1, + closed: true + }, + [Path.join(contentDir, h2)]: { + contents: RANGE_2, + closed: true + }, + [Path.join(contentDir, h3)]: { + contents: RANGE_3, + closed: true + }, + [Path.join(contentDir, '.state.v0.json')]: { + contents: JSON.stringify({ + hashAge: [ + [h1, 0], + [h2, 1], + [h3, 0] + ], + hashSize: [ + [h1, 18], + [h2, 19], + [h3, 20] + ] + }), + closed: true + } + }) + }) + + it('should find no new ranges', function () { + expect(newContentRanges).to.deep.equal([]) + }) + + describe('when re-running 5 more times', function () { + for (let i = 0; i < 5; i++) { + beforeEach(function (done) { + runWithOneSplitStreamRemoved(done) + }) + } + + it('should still produce two ranges', function () { + expect(contentRanges).to.have.length(2) + }) + + it('should still find the correct offsets', function () { + expect(contentRanges).to.deep.equal([ + { + start: 3, + end: 21, + hash: hash(RANGE_1) + }, + { + start: 24, + end: 44, + hash: hash(RANGE_3) + } + ]) + }) + + it('should delete the 2nd range', function () { + expect(JSON.parse(JSON.stringify(files))).to.deep.equal({ + [Path.join(contentDir, h1)]: { + contents: RANGE_1, + closed: true + }, + [Path.join(contentDir, h3)]: { + contents: RANGE_3, + closed: true + }, + [Path.join(contentDir, '.state.v0.json')]: { + contents: JSON.stringify({ + hashAge: [ + [h1, 0], + [h3, 0] + ], + hashSize: [ + [h1, 18], + [h3, 20] + ] + }), + closed: true + } + }) + }) + + it('should find no new ranges', function () { + expect(newContentRanges).to.deep.equal([]) + }) + + it('should yield the reclaimed space', function () { + expect(reclaimed).to.equal(RANGE_2.length) + }) + }) + }) }) }) }) From f361cdfaca10b5a4adc53e0a3bbd5ab3edf3211d Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 18 May 2021 18:15:58 +0100 Subject: [PATCH 714/754] [ContentCacheManager] deeply integrate the HashFileTracker - Update the tracker contents as we hash ranges - Let the tracker be authoritative for already written ranges This is saving a stat call per newly written range. --- services/clsi/app/js/ContentCacheManager.js | 37 +++++++++------------ 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/services/clsi/app/js/ContentCacheManager.js b/services/clsi/app/js/ContentCacheManager.js index 60c68b9ba6..d424681615 100644 --- a/services/clsi/app/js/ContentCacheManager.js +++ b/services/clsi/app/js/ContentCacheManager.js @@ -26,9 +26,10 @@ async function update(contentDir, filePath) { const extractor = new PdfStreamsExtractor() const ranges = [] const newRanges = [] - const seenHashes = new Set() // keep track of hashes expire old ones when they reach a generation > N. const tracker = await HashFileTracker.from(contentDir) + tracker.updateAge() + for await (const chunk of stream) { const pdfStreams = extractor.consume(chunk) for (const pdfStream of pdfStreams) { @@ -39,15 +40,12 @@ async function update(contentDir, filePath) { ranges.push(range) // Optimization: Skip writing of duplicate streams. - if (seenHashes.has(hash)) continue - seenHashes.add(hash) + if (tracker.track(range)) continue - if (await writePdfStream(contentDir, hash, pdfStream.buffers)) { - newRanges.push(range) - } + await writePdfStream(contentDir, hash, pdfStream.buffers) + newRanges.push(range) } } - tracker.update(ranges, newRanges) const reclaimedSpace = await tracker.deleteStaleHashes(5) await tracker.flush() return [ranges, newRanges, reclaimedSpace] @@ -74,16 +72,19 @@ class HashFileTracker { return new HashFileTracker(contentDir, state) } - update(ranges, newRanges) { + track(range) { + const exists = this.hashAge.has(range.hash) + if (!exists) { + this.hashSize.set(range.hash, range.end - range.start) + } + this.hashAge.set(range.hash, 0) + return exists + } + + updateAge() { for (const [hash, age] of this.hashAge) { this.hashAge.set(hash, age + 1) } - for (const range of ranges) { - this.hashAge.set(range.hash, 0) - } - for (const range of newRanges) { - this.hashSize.set(range.hash, range.end - range.start) - } return this } @@ -215,13 +216,6 @@ function pdfStreamHash(buffers) { async function writePdfStream(dir, hash, buffers) { const filename = Path.join(dir, hash) - try { - await fs.promises.stat(filename) - // The file exists. Do not rewrite the content. - // It would change the modified-time of the file and hence invalidate the - // ETags used for client side caching via browser internals. - return false - } catch (e) {} const atomicWriteFilename = filename + '~' const file = await fs.promises.open(atomicWriteFilename, 'w') if (Settings.enablePdfCachingDark) { @@ -244,7 +238,6 @@ async function writePdfStream(dir, hash, buffers) { throw err } } - return true } function promiseMapWithLimit(concurrency, array, fn) { From b53a105681496119077305ee582ef404914f04cf Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Wed, 19 May 2021 11:17:08 +0100 Subject: [PATCH 715/754] [LatexRunner] do not emit empty cpu/sys timings --- services/clsi/app/js/LatexRunner.js | 38 ++++++-------- .../clsi/test/unit/js/LatexRunnerTests.js | 49 ++++++++++++++++++- 2 files changed, 63 insertions(+), 24 deletions(-) diff --git a/services/clsi/app/js/LatexRunner.js b/services/clsi/app/js/LatexRunner.js index f8799d216c..4800021932 100644 --- a/services/clsi/app/js/LatexRunner.js +++ b/services/clsi/app/js/LatexRunner.js @@ -23,6 +23,12 @@ const fs = require('fs') const ProcessTable = {} // table of currently running jobs (pids or docker container names) +const TIME_V_METRICS = Object.entries({ + 'cpu-percent': /Percent of CPU this job got: (\d+)/m, + 'cpu-time': /User time.*: (\d+.\d+)/m, + 'sys-time': /System time.*: (\d+.\d+)/m +}) + module.exports = LatexRunner = { runLatex(project_id, options, callback) { let command @@ -116,28 +122,16 @@ module.exports = LatexRunner = { stats[`latex-runs-with-errors-${runs}`] = failed ? 1 : 0 // timing information from /usr/bin/time const timings = {} - const stderr = output != null ? output.stderr : undefined - timings['cpu-percent'] = - __guard__( - stderr != null - ? stderr.match(/Percent of CPU this job got: (\d+)/m) - : undefined, - (x3) => x3[1] - ) || 0 - timings['cpu-time'] = - __guard__( - stderr != null - ? stderr.match(/User time.*: (\d+.\d+)/m) - : undefined, - (x4) => x4[1] - ) || 0 - timings['sys-time'] = - __guard__( - stderr != null - ? stderr.match(/System time.*: (\d+.\d+)/m) - : undefined, - (x5) => x5[1] - ) || 0 + const stderr = (output && output.stderr) || '' + if (stderr.includes('Command being timed:')) { + // Add metrics for runs with `$ time -v ...` + for (const [timing, matcher] of TIME_V_METRICS) { + const match = stderr.match(matcher) + if (match) { + timings[timing] = parseFloat(match[1]) + } + } + } // record output files LatexRunner.writeLogOutput(project_id, directory, output, () => { return callback(error, output, stats, timings) diff --git a/services/clsi/test/unit/js/LatexRunnerTests.js b/services/clsi/test/unit/js/LatexRunnerTests.js index a9116d0fd7..f763f39cb0 100644 --- a/services/clsi/test/unit/js/LatexRunnerTests.js +++ b/services/clsi/test/unit/js/LatexRunnerTests.js @@ -11,6 +11,7 @@ */ const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') +const { expect } = require('chai') const modulePath = require('path').join( __dirname, '../../../app/js/LatexRunner' @@ -58,7 +59,7 @@ describe('LatexRunner', function () { }) describe('normally', function () { - beforeEach(function () { + beforeEach(function (done) { return this.LatexRunner.runLatex( this.project_id, { @@ -70,7 +71,10 @@ describe('LatexRunner', function () { environment: this.env, compileGroup: this.compileGroup }, - this.callback + (error, output, stats, timings) => { + this.timings = timings + done(error) + } ) }) @@ -96,6 +100,47 @@ describe('LatexRunner', function () { .calledWith(this.directory + '/' + 'output.stderr', 'this is stderr') .should.equal(true) }) + + it('should not record cpu metrics', function () { + expect(this.timings['cpu-percent']).to.not.exist + expect(this.timings['cpu-time']).to.not.exist + expect(this.timings['sys-time']).to.not.exist + }) + }) + + describe('with time -v', function () { + beforeEach(function (done) { + this.CommandRunner.run = sinon.stub().callsArgWith(7, null, { + stdout: 'this is stdout', + stderr: + '\tCommand being timed: "sh -c timeout 1 yes > /dev/null"\n' + + '\tUser time (seconds): 0.28\n' + + '\tSystem time (seconds): 0.70\n' + + '\tPercent of CPU this job got: 98%\n' + }) + this.LatexRunner.runLatex( + this.project_id, + { + directory: this.directory, + mainFile: this.mainFile, + compiler: this.compiler, + timeout: (this.timeout = 42000), + image: this.image, + environment: this.env, + compileGroup: this.compileGroup + }, + (error, output, stats, timings) => { + this.timings = timings + done(error) + } + ) + }) + + it('should record cpu metrics', function () { + expect(this.timings['cpu-percent']).to.equal(98) + expect(this.timings['cpu-time']).to.equal(0.28) + expect(this.timings['sys-time']).to.equal(0.7) + }) }) describe('with an .Rtex main file', function () { From 5af8f57dd0ed22c372004e7c5752c50626db962d Mon Sep 17 00:00:00 2001 From: decaffeinate <alf.eaton@overleaf.com> Date: Wed, 19 May 2021 16:50:45 +0100 Subject: [PATCH 716/754] decaffeinate: Rename settings.test.coffee from .coffee to .js --- .../acceptance/scripts/{settings.test.coffee => settings.test.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename services/clsi/test/acceptance/scripts/{settings.test.coffee => settings.test.js} (100%) diff --git a/services/clsi/test/acceptance/scripts/settings.test.coffee b/services/clsi/test/acceptance/scripts/settings.test.js similarity index 100% rename from services/clsi/test/acceptance/scripts/settings.test.coffee rename to services/clsi/test/acceptance/scripts/settings.test.js From 8c984a77c484eb35a88b291adbaa38beeb139ca2 Mon Sep 17 00:00:00 2001 From: decaffeinate <alf.eaton@overleaf.com> Date: Wed, 19 May 2021 16:50:45 +0100 Subject: [PATCH 717/754] decaffeinate: Convert settings.test.coffee to JS --- .../test/acceptance/scripts/settings.test.js | 78 +++++++++++-------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/services/clsi/test/acceptance/scripts/settings.test.js b/services/clsi/test/acceptance/scripts/settings.test.js index e3d3b70bb3..38a6e034fd 100644 --- a/services/clsi/test/acceptance/scripts/settings.test.js +++ b/services/clsi/test/acceptance/scripts/settings.test.js @@ -1,47 +1,59 @@ -Path = require "path" +const Path = require("path"); -module.exports = - # Options are passed to Sequelize. - # See http://sequelizejs.com/documentation#usage-options for details - mysql: - clsi: - database: "clsi" - username: "clsi" - password: null - dialect: "sqlite" +module.exports = { + // Options are passed to Sequelize. + // See http://sequelizejs.com/documentation#usage-options for details + mysql: { + clsi: { + database: "clsi", + username: "clsi", + password: null, + dialect: "sqlite", storage: Path.resolve("db.sqlite") + } + }, - path: - compilesDir: Path.resolve(__dirname + "/../../../compiles") - clsiCacheDir: Path.resolve(__dirname + "/../../../cache") - #synctexBaseDir: (project_id) -> Path.join(@compilesDir, project_id) - synctexBaseDir: () -> "/compile" + path: { + compilesDir: Path.resolve(__dirname + "/../../../compiles"), + clsiCacheDir: Path.resolve(__dirname + "/../../../cache"), + //synctexBaseDir: (project_id) -> Path.join(@compilesDir, project_id) + synctexBaseDir() { return "/compile"; }, sandboxedCompilesHostDir: process.env['SANDBOXED_COMPILES_HOST_DIR'] + }, - clsi: - #strace: true - #archive_logs: true - commandRunner: "docker-runner-sharelatex" - latexmkCommandPrefix: ["/usr/bin/time", "-v"] # on Linux - docker: - image: process.env.TEXLIVE_IMAGE || "texlive-full:2017.1-opt" - env: - PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2017/bin/x86_64-linux/" + clsi: { + //strace: true + //archive_logs: true + commandRunner: "docker-runner-sharelatex", + latexmkCommandPrefix: ["/usr/bin/time", "-v"], // on Linux + docker: { + image: process.env.TEXLIVE_IMAGE || "texlive-full:2017.1-opt", + env: { + PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2017/bin/x86_64-linux/", HOME: "/tmp" - modem: + }, + modem: { socketPath: false + }, user: process.env.SIBLING_CONTAINER_USER ||"111" + } + }, - internal: - clsi: - port: 3013 - load_port: 3044 + internal: { + clsi: { + port: 3013, + load_port: 3044, host: "localhost" + } + }, - apis: - clsi: + apis: { + clsi: { url: "http://localhost:3013" + } + }, - smokeTest: false - project_cache_length_ms: 1000 * 60 * 60 * 24 + smokeTest: false, + project_cache_length_ms: 1000 * 60 * 60 * 24, parallelFileDownloads:1 +}; From 85920b31207373f9246de6f8b00b38d2bed904b8 Mon Sep 17 00:00:00 2001 From: decaffeinate <alf.eaton@overleaf.com> Date: Wed, 19 May 2021 16:50:46 +0100 Subject: [PATCH 718/754] decaffeinate: Run post-processing cleanups on settings.test.coffee --- .../clsi/test/acceptance/scripts/settings.test.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/services/clsi/test/acceptance/scripts/settings.test.js b/services/clsi/test/acceptance/scripts/settings.test.js index 38a6e034fd..2f0cc4a10b 100644 --- a/services/clsi/test/acceptance/scripts/settings.test.js +++ b/services/clsi/test/acceptance/scripts/settings.test.js @@ -1,3 +1,8 @@ +/* eslint-disable + no-path-concat, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. const Path = require("path"); module.exports = { @@ -16,14 +21,14 @@ module.exports = { path: { compilesDir: Path.resolve(__dirname + "/../../../compiles"), clsiCacheDir: Path.resolve(__dirname + "/../../../cache"), - //synctexBaseDir: (project_id) -> Path.join(@compilesDir, project_id) + // synctexBaseDir: (project_id) -> Path.join(@compilesDir, project_id) synctexBaseDir() { return "/compile"; }, - sandboxedCompilesHostDir: process.env['SANDBOXED_COMPILES_HOST_DIR'] + sandboxedCompilesHostDir: process.env.SANDBOXED_COMPILES_HOST_DIR }, clsi: { - //strace: true - //archive_logs: true + // strace: true + // archive_logs: true commandRunner: "docker-runner-sharelatex", latexmkCommandPrefix: ["/usr/bin/time", "-v"], // on Linux docker: { From f31abe7cb3582e61fa36808ef53ccd207535d53d Mon Sep 17 00:00:00 2001 From: Alf Eaton <alf.eaton@overleaf.com> Date: Wed, 19 May 2021 18:36:27 +0100 Subject: [PATCH 719/754] Tidy and format --- .../test/acceptance/scripts/settings.test.js | 114 +++++++++--------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/services/clsi/test/acceptance/scripts/settings.test.js b/services/clsi/test/acceptance/scripts/settings.test.js index 2f0cc4a10b..e960ce8b71 100644 --- a/services/clsi/test/acceptance/scripts/settings.test.js +++ b/services/clsi/test/acceptance/scripts/settings.test.js @@ -1,64 +1,64 @@ -/* eslint-disable - no-path-concat, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -const Path = require("path"); +const Path = require('path') module.exports = { - // Options are passed to Sequelize. - // See http://sequelizejs.com/documentation#usage-options for details - mysql: { - clsi: { - database: "clsi", - username: "clsi", - password: null, - dialect: "sqlite", - storage: Path.resolve("db.sqlite") - } - }, + // Options are passed to Sequelize. + // See http://sequelizejs.com/documentation#usage-options for details + mysql: { + clsi: { + database: 'clsi', + username: 'clsi', + password: null, + dialect: 'sqlite', + storage: Path.resolve('db.sqlite'), + }, + }, - path: { - compilesDir: Path.resolve(__dirname + "/../../../compiles"), - clsiCacheDir: Path.resolve(__dirname + "/../../../cache"), - // synctexBaseDir: (project_id) -> Path.join(@compilesDir, project_id) - synctexBaseDir() { return "/compile"; }, - sandboxedCompilesHostDir: process.env.SANDBOXED_COMPILES_HOST_DIR - }, + path: { + // eslint-disable-next-line no-path-concat + compilesDir: Path.resolve(__dirname + '/../../../compiles'), + // eslint-disable-next-line no-path-concat + clsiCacheDir: Path.resolve(__dirname + '/../../../cache'), + // synctexBaseDir: (project_id) -> Path.join(@compilesDir, project_id) + synctexBaseDir() { + return '/compile' + }, + sandboxedCompilesHostDir: process.env.SANDBOXED_COMPILES_HOST_DIR, + }, - clsi: { - // strace: true - // archive_logs: true - commandRunner: "docker-runner-sharelatex", - latexmkCommandPrefix: ["/usr/bin/time", "-v"], // on Linux - docker: { - image: process.env.TEXLIVE_IMAGE || "texlive-full:2017.1-opt", - env: { - PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2017/bin/x86_64-linux/", - HOME: "/tmp" - }, - modem: { - socketPath: false - }, - user: process.env.SIBLING_CONTAINER_USER ||"111" - } - }, + clsi: { + // strace: true + // archive_logs: true + commandRunner: 'docker-runner-sharelatex', + latexmkCommandPrefix: ['/usr/bin/time', '-v'], // on Linux + docker: { + image: process.env.TEXLIVE_IMAGE || 'texlive-full:2017.1-opt', + env: { + PATH: + '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2017/bin/x86_64-linux/', + HOME: '/tmp', + }, + modem: { + socketPath: false, + }, + user: process.env.SIBLING_CONTAINER_USER || '111', + }, + }, - internal: { - clsi: { - port: 3013, - load_port: 3044, - host: "localhost" - } - }, + internal: { + clsi: { + port: 3013, + load_port: 3044, + host: 'localhost', + }, + }, - apis: { - clsi: { - url: "http://localhost:3013" - } - }, + apis: { + clsi: { + url: 'http://localhost:3013', + }, + }, - smokeTest: false, - project_cache_length_ms: 1000 * 60 * 60 * 24, - parallelFileDownloads:1 -}; + smokeTest: false, + project_cache_length_ms: 1000 * 60 * 60 * 24, + parallelFileDownloads: 1, +} From 1b1c4aa4a00b96684ce22aceb14101e3d8f4a433 Mon Sep 17 00:00:00 2001 From: Alf Eaton <alf.eaton@overleaf.com> Date: Wed, 19 May 2021 18:47:51 +0100 Subject: [PATCH 720/754] Fix formatting --- .../test/acceptance/scripts/settings.test.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/services/clsi/test/acceptance/scripts/settings.test.js b/services/clsi/test/acceptance/scripts/settings.test.js index e960ce8b71..f9446c314f 100644 --- a/services/clsi/test/acceptance/scripts/settings.test.js +++ b/services/clsi/test/acceptance/scripts/settings.test.js @@ -9,8 +9,8 @@ module.exports = { username: 'clsi', password: null, dialect: 'sqlite', - storage: Path.resolve('db.sqlite'), - }, + storage: Path.resolve('db.sqlite') + } }, path: { @@ -22,7 +22,7 @@ module.exports = { synctexBaseDir() { return '/compile' }, - sandboxedCompilesHostDir: process.env.SANDBOXED_COMPILES_HOST_DIR, + sandboxedCompilesHostDir: process.env.SANDBOXED_COMPILES_HOST_DIR }, clsi: { @@ -35,30 +35,30 @@ module.exports = { env: { PATH: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2017/bin/x86_64-linux/', - HOME: '/tmp', + HOME: '/tmp' }, modem: { - socketPath: false, + socketPath: false }, - user: process.env.SIBLING_CONTAINER_USER || '111', - }, + user: process.env.SIBLING_CONTAINER_USER || '111' + } }, internal: { clsi: { port: 3013, load_port: 3044, - host: 'localhost', - }, + host: 'localhost' + } }, apis: { clsi: { - url: 'http://localhost:3013', - }, + url: 'http://localhost:3013' + } }, smokeTest: false, project_cache_length_ms: 1000 * 60 * 60 * 24, - parallelFileDownloads: 1, + parallelFileDownloads: 1 } From 6507d668ae5897de058c2b6fd75e9ef693b58680 Mon Sep 17 00:00:00 2001 From: Alf Eaton <alf.eaton@overleaf.com> Date: Wed, 19 May 2021 18:57:35 +0100 Subject: [PATCH 721/754] Update references to .coffee files --- services/clsi/docker-compose-config.yml | 6 +++--- services/clsi/test/acceptance/scripts/full-test.sh | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/clsi/docker-compose-config.yml b/services/clsi/docker-compose-config.yml index afe56bbec5..0c141eaa18 100644 --- a/services/clsi/docker-compose-config.yml +++ b/services/clsi/docker-compose-config.yml @@ -6,7 +6,7 @@ services: ALLOWED_IMAGES: "quay.io/sharelatex/texlive-full:2017.1" TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2017.1 TEXLIVE_IMAGE_USER: "tex" - SHARELATEX_CONFIG: /app/config/settings.defaults.coffee + SHARELATEX_CONFIG: /app/config/settings.defaults.js DOCKER_RUNNER: "true" COMPILES_HOST_DIR: $PWD/compiles SYNCTEX_BIN_HOST_PATH: $PWD/bin/synctex @@ -16,13 +16,13 @@ services: - ./cache:/app/cache - ./bin/synctex:/app/bin/synctex - + ci: environment: ALLOWED_IMAGES: ${TEXLIVE_IMAGE} TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2017.1 TEXLIVE_IMAGE_USER: "tex" - SHARELATEX_CONFIG: /app/config/settings.defaults.coffee + SHARELATEX_CONFIG: /app/config/settings.defaults.js DOCKER_RUNNER: "true" COMPILES_HOST_DIR: $PWD/compiles SYNCTEX_BIN_HOST_PATH: $PWD/bin/synctex diff --git a/services/clsi/test/acceptance/scripts/full-test.sh b/services/clsi/test/acceptance/scripts/full-test.sh index a465bdde16..953b49452a 100755 --- a/services/clsi/test/acceptance/scripts/full-test.sh +++ b/services/clsi/test/acceptance/scripts/full-test.sh @@ -1,6 +1,6 @@ #!/bin/bash -x -export SHARELATEX_CONFIG=`pwd`/test/acceptance/scripts/settings.test.coffee +export SHARELATEX_CONFIG=`pwd`/test/acceptance/scripts/settings.test.js echo ">> Starting server..." From 4d578bf52d3b10c260d431fbbc799180066f5aa4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 May 2021 09:35:58 +0000 Subject: [PATCH 722/754] Bump lodash from 4.17.20 to 4.17.21 Bumps [lodash](https://github.com/lodash/lodash) from 4.17.20 to 4.17.21. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.20...4.17.21) Signed-off-by: dependabot[bot] <support@github.com> --- services/clsi/package-lock.json | 6 +++--- services/clsi/package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index 773d9f4319..7d1557c12c 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -4534,9 +4534,9 @@ } }, "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.at": { "version": "4.6.0", diff --git a/services/clsi/package.json b/services/clsi/package.json index dac3ba9dbb..b28645e8ef 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -28,7 +28,7 @@ "fs-extra": "^10.0.0", "heapdump": "^0.3.15", "lockfile": "^1.0.4", - "lodash": "^4.17.20", + "lodash": "^4.17.21", "logger-sharelatex": "^2.2.0", "mysql": "^2.18.1", "p-limit": "^3.1.0", From d56a49012188e372edc81adb7ac5390c4c54ca37 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 25 May 2021 10:24:49 +0100 Subject: [PATCH 723/754] [misc] AllowedImageNamesTests: add missing done callbacks --- ...ImageNames.js => AllowedImageNamesTests.js} | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) rename services/clsi/test/acceptance/js/{AllowedImageNames.js => AllowedImageNamesTests.js} (88%) diff --git a/services/clsi/test/acceptance/js/AllowedImageNames.js b/services/clsi/test/acceptance/js/AllowedImageNamesTests.js similarity index 88% rename from services/clsi/test/acceptance/js/AllowedImageNames.js rename to services/clsi/test/acceptance/js/AllowedImageNamesTests.js index 8107273fe0..06304a808d 100644 --- a/services/clsi/test/acceptance/js/AllowedImageNames.js +++ b/services/clsi/test/acceptance/js/AllowedImageNamesTests.js @@ -75,7 +75,7 @@ Hello world beforeEach(function (done) { Client.compile(this.project_id, this.request, done) }) - it('should error out with an invalid imageName', function () { + it('should error out with an invalid imageName', function (done) { Client.syncFromCodeWithImage( this.project_id, 'main.tex', @@ -85,11 +85,12 @@ Hello world (error, body) => { expect(String(error)).to.include('statusCode=400') expect(body).to.equal('invalid image') + done() } ) }) - it('should produce a mapping a valid imageName', function () { + it('should produce a mapping a valid imageName', function (done) { Client.syncFromCodeWithImage( this.project_id, 'main.tex', @@ -103,6 +104,7 @@ Hello world { page: 1, h: 133.77, v: 134.76, height: 6.92, width: 343.71 } ] }) + done() } ) }) @@ -112,7 +114,7 @@ Hello world beforeEach(function (done) { Client.compile(this.project_id, this.request, done) }) - it('should error out with an invalid imageName', function () { + it('should error out with an invalid imageName', function (done) { Client.syncFromPdfWithImage( this.project_id, 'main.tex', @@ -122,11 +124,12 @@ Hello world (error, body) => { expect(String(error)).to.include('statusCode=400') expect(body).to.equal('invalid image') + done() } ) }) - it('should produce a mapping a valid imageName', function () { + it('should produce a mapping a valid imageName', function (done) { Client.syncFromPdfWithImage( this.project_id, 1, @@ -138,6 +141,7 @@ Hello world expect(result).to.deep.equal({ code: [{ file: 'main.tex', line: 3, column: -1 }] }) + done() } ) }) @@ -147,7 +151,7 @@ Hello world beforeEach(function (done) { Client.compile(this.project_id, this.request, done) }) - it('should error out with an invalid imageName', function () { + it('should error out with an invalid imageName', function (done) { Client.wordcountWithImage( this.project_id, 'main.tex', @@ -155,11 +159,12 @@ Hello world (error, body) => { expect(String(error)).to.include('statusCode=400') expect(body).to.equal('invalid image') + done() } ) }) - it('should produce a texcout a valid imageName', function () { + it('should produce a texcout a valid imageName', function (done) { Client.wordcountWithImage( this.project_id, 'main.tex', @@ -168,6 +173,7 @@ Hello world expect(error).to.not.exist expect(result).to.exist expect(result.texcount).to.exist + done() } ) }) From 294088fb273fc531b972d29ee39a52216a4d668b Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Mon, 31 May 2021 10:20:25 +0200 Subject: [PATCH 724/754] [ContentCacheManager] use PDF.js Xref table instead of stream detection (#242) * make the content cache manager tests configurable * extend stream content in unit tests * [ContentCacheManagerTests] prepare for full object caching * filesystem stream for pdfjs * working?? * cleaning up * handle overflow * [misc] install pdfjs-dist * [misc] move pdfjs code into app/lib/ and scripts/, also use CamelCase * [misc] abstract the file loading and parsing of xRef tables into helper * [misc] pdfjsTests: add snapshot based tests for the Xref table parser * [misc] FSStream: throw proper error and drop commented code * [misc] FSStream: integrate throwing of MissingDataException into getter * [misc] pdfjs: fix eslint errors * [misc] pdfjs: run format_fix * [misc] pdfjs: allocate very small non empty dummy buffers explicitly * [misc] install @overleaf/o-error * [ContentCacheManager] use PDF.js Xref table instead of stream detection Co-Authored-By: Brian Gough <brian.gough@overleaf.com> * [pdfjs] parseXrefTable: handle empty PDF files gracefully Co-authored-by: Brian Gough <brian.gough@overleaf.com> --- services/clsi/app/js/ContentCacheManager.js | 174 ++++---- services/clsi/app/js/OutputCacheManager.js | 57 +-- services/clsi/app/lib/pdfjs/FSPdfManager.js | 43 ++ services/clsi/app/lib/pdfjs/FSStream.js | 138 +++++++ services/clsi/app/lib/pdfjs/parseXrefTable.js | 24 ++ services/clsi/package-lock.json | 21 +- services/clsi/package.json | 4 +- services/clsi/scripts/demo-pdfjs-Xref.js | 12 + .../clsi/test/acceptance/fixtures/minimal.pdf | Bin 0 -> 12313 bytes .../test/unit/js/ContentCacheManagerTests.js | 371 ++++++------------ ...71616916a7ba0434fcfb56d1fc7e27cd139eaa2f71 | 7 + ...17923e3714927bbf35e63ea88bd93c7a8076cf1fcd | Bin 0 -> 10161 bytes services/clsi/test/unit/lib/pdfjsTests.js | 78 ++++ .../lib/snapshots/asymptote/XrefTable.json | 356 +++++++++++++++++ .../biber_bibliography/XrefTable.json | 128 ++++++ .../lib/snapshots/epstopdf/XrefTable.json | 125 ++++++ .../unit/lib/snapshots/feynmf/XrefTable.json | 115 ++++++ .../unit/lib/snapshots/feynmp/XrefTable.json | 102 +++++ .../lib/snapshots/fontawesome/XrefTable.json | 93 +++++ .../fontawesome_xelatex/XrefTable.json | 126 ++++++ .../lib/snapshots/glossaries/XrefTable.json | 94 +++++ .../unit/lib/snapshots/gnuplot/XrefTable.json | 89 +++++ .../unit/lib/snapshots/hebrew/XrefTable.json | 81 ++++ .../unit/lib/snapshots/knitr/XrefTable.json | 145 +++++++ .../lib/snapshots/knitr_utf8/XrefTable.json | 179 +++++++++ .../snapshots/latex_compiler/XrefTable.json | 132 +++++++ .../lualatex_compiler/XrefTable.json | 74 ++++ .../makeindex-custom-style/XrefTable.json | 107 +++++ .../lib/snapshots/makeindex/XrefTable.json | 90 +++++ .../unit/lib/snapshots/minted/XrefTable.json | 77 ++++ .../multibib_bibliography/XrefTable.json | 133 +++++++ .../lib/snapshots/nomenclature/XrefTable.json | 94 +++++ .../references_in_include/XrefTable.json | 90 +++++ .../simple_bibliography/XrefTable.json | 94 +++++ .../snapshots/subdirectories/XrefTable.json | 108 +++++ .../lib/snapshots/tikz_feynman/XrefTable.json | 145 +++++++ .../snapshots/xelatex_compiler/XrefTable.json | 73 ++++ 37 files changed, 3406 insertions(+), 373 deletions(-) create mode 100644 services/clsi/app/lib/pdfjs/FSPdfManager.js create mode 100644 services/clsi/app/lib/pdfjs/FSStream.js create mode 100644 services/clsi/app/lib/pdfjs/parseXrefTable.js create mode 100644 services/clsi/scripts/demo-pdfjs-Xref.js create mode 100644 services/clsi/test/acceptance/fixtures/minimal.pdf create mode 100644 services/clsi/test/unit/js/snapshots/minimalCompile/chunks/896749b8343851b0dc385f71616916a7ba0434fcfb56d1fc7e27cd139eaa2f71 create mode 100644 services/clsi/test/unit/js/snapshots/minimalCompile/chunks/d7cfc73ad2fba4578a437517923e3714927bbf35e63ea88bd93c7a8076cf1fcd create mode 100644 services/clsi/test/unit/lib/pdfjsTests.js create mode 100644 services/clsi/test/unit/lib/snapshots/asymptote/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/biber_bibliography/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/epstopdf/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/feynmf/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/feynmp/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/fontawesome/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/fontawesome_xelatex/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/glossaries/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/gnuplot/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/hebrew/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/knitr/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/knitr_utf8/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/latex_compiler/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/lualatex_compiler/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/makeindex-custom-style/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/makeindex/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/minted/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/multibib_bibliography/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/nomenclature/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/references_in_include/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/simple_bibliography/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/subdirectories/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/tikz_feynman/XrefTable.json create mode 100644 services/clsi/test/unit/lib/snapshots/xelatex_compiler/XrefTable.json diff --git a/services/clsi/app/js/ContentCacheManager.js b/services/clsi/app/js/ContentCacheManager.js index d424681615..ab73c61d93 100644 --- a/services/clsi/app/js/ContentCacheManager.js +++ b/services/clsi/app/js/ContentCacheManager.js @@ -7,45 +7,97 @@ const fs = require('fs') const crypto = require('crypto') const Path = require('path') const Settings = require('settings-sharelatex') +const OError = require('@overleaf/o-error') const pLimit = require('p-limit') - -const MIN_CHUNK_SIZE = Settings.pdfCachingMinChunkSize - -const START_OF_STREAM_MARKER = 'stream' -const END_OF_STREAM_MARKER = 'endstream' -const START_OF_STREAM_MARKER_LENGTH = START_OF_STREAM_MARKER.length -const END_OF_STREAM_MARKER_LENGTH = END_OF_STREAM_MARKER.length +const { parseXrefTable } = require('../lib/pdfjs/parseXrefTable') /** * * @param {String} contentDir path to directory where content hash files are cached * @param {String} filePath the pdf file to scan for streams + * @param {number} size the pdf size */ -async function update(contentDir, filePath) { - const stream = fs.createReadStream(filePath) - const extractor = new PdfStreamsExtractor() +async function update(contentDir, filePath, size) { const ranges = [] const newRanges = [] // keep track of hashes expire old ones when they reach a generation > N. const tracker = await HashFileTracker.from(contentDir) tracker.updateAge() - for await (const chunk of stream) { - const pdfStreams = extractor.consume(chunk) - for (const pdfStream of pdfStreams) { - if (pdfStream.end - pdfStream.start < MIN_CHUNK_SIZE) continue - const hash = pdfStreamHash(pdfStream.buffers) + const rawTable = await parseXrefTable(filePath, size) + rawTable.sort((a, b) => { + return a.offset - b.offset + }) + rawTable.forEach((obj, idx) => { + obj.idx = idx + }) - const range = { start: pdfStream.start, end: pdfStream.end, hash } + const uncompressedObjects = [] + for (const object of rawTable) { + if (!object.uncompressed) { + continue + } + const nextObject = rawTable[object.idx + 1] + if (!nextObject) { + // Ignore this possible edge case. + // The last object should be part of the xRef table. + continue + } else { + object.endOffset = nextObject.offset + } + const size = object.endOffset - object.offset + object.size = size + if (size < Settings.pdfCachingMinChunkSize) { + continue + } + uncompressedObjects.push(object) + } + + const handle = await fs.promises.open(filePath) + try { + for (const object of uncompressedObjects) { + let buffer = Buffer.alloc(object.size, 0) + const { bytesRead } = await handle.read( + buffer, + 0, + object.size, + object.offset + ) + if (bytesRead !== object.size) { + throw new OError('could not read full chunk', { + object, + bytesRead + }) + } + const idxObj = buffer.indexOf('obj') + if (idxObj > 100) { + throw new OError('objectId is too large', { + object, + idxObj + }) + } + const objectIdRaw = buffer.subarray(0, idxObj) + buffer = buffer.subarray(objectIdRaw.byteLength) + + const hash = pdfStreamHash(buffer) + const range = { + objectId: objectIdRaw.toString(), + start: object.offset + objectIdRaw.byteLength, + end: object.endOffset, + hash + } ranges.push(range) // Optimization: Skip writing of duplicate streams. if (tracker.track(range)) continue - await writePdfStream(contentDir, hash, pdfStream.buffers) + await writePdfStream(contentDir, hash, buffer) newRanges.push(range) } + } finally { + await handle.close() } + const reclaimedSpace = await tracker.deleteStaleHashes(5) await tracker.flush() return [ranges, newRanges, reclaimedSpace] @@ -142,94 +194,21 @@ class HashFileTracker { } } -class PdfStreamsExtractor { - constructor() { - this.fileIndex = 0 - this.inStream = false - this.streamStartIndex = 0 - this.buffers = [] - this.lastChunk = Buffer.alloc(0) - } - - consume(chunk) { - let chunkIndex = 0 - const pdfStreams = [] - chunk = Buffer.concat([this.lastChunk, chunk]) - while (true) { - if (!this.inStream) { - // Not in a stream, look for stream start - const index = chunk.indexOf(START_OF_STREAM_MARKER, chunkIndex) - if (index === -1) { - // Couldn't find stream start - break - } - // Found stream start, start a stream - this.inStream = true - this.streamStartIndex = this.fileIndex + index - chunkIndex = index - } else { - // In a stream, look for stream end - const index = chunk.indexOf(END_OF_STREAM_MARKER, chunkIndex) - if (index === -1) { - break - } - // add "endstream" part - const endIndex = index + END_OF_STREAM_MARKER_LENGTH - this.buffers.push(chunk.slice(chunkIndex, endIndex)) - pdfStreams.push({ - start: this.streamStartIndex, - end: this.fileIndex + endIndex, - buffers: this.buffers - }) - this.inStream = false - this.buffers = [] - chunkIndex = endIndex - } - } - - const remaining = chunk.length - chunkIndex - const nextMarkerLength = this.inStream - ? END_OF_STREAM_MARKER_LENGTH - : START_OF_STREAM_MARKER_LENGTH - if (remaining > nextMarkerLength) { - const retainMarkerSection = chunk.length - nextMarkerLength - if (this.inStream) { - this.buffers.push(chunk.slice(chunkIndex, retainMarkerSection)) - } - this.lastChunk = chunk.slice(retainMarkerSection) - this.fileIndex += retainMarkerSection - } else { - this.lastChunk = chunk.slice(chunkIndex) - this.fileIndex += chunkIndex - } - return pdfStreams - } -} - -function pdfStreamHash(buffers) { +function pdfStreamHash(buffer) { const hash = crypto.createHash('sha256') - for (const buffer of buffers) { - hash.update(buffer) - } + hash.update(buffer) return hash.digest('hex') } -async function writePdfStream(dir, hash, buffers) { +async function writePdfStream(dir, hash, buffer) { const filename = Path.join(dir, hash) const atomicWriteFilename = filename + '~' - const file = await fs.promises.open(atomicWriteFilename, 'w') if (Settings.enablePdfCachingDark) { // Write an empty file in dark mode. - buffers = [] + buffer = Buffer.alloc(0) } try { - try { - for (const buffer of buffers) { - await file.write(buffer) - } - } finally { - await file.close() - } + await fs.promises.writeFile(atomicWriteFilename, buffer) await fs.promises.rename(atomicWriteFilename, filename) } catch (err) { try { @@ -247,5 +226,8 @@ function promiseMapWithLimit(concurrency, array, fn) { module.exports = { HASH_REGEX: /^[0-9a-f]{64}$/, - update: callbackify(update) + update: callbackify(update), + promises: { + update + } } diff --git a/services/clsi/app/js/OutputCacheManager.js b/services/clsi/app/js/OutputCacheManager.js index e6167570f8..4d2e64132b 100644 --- a/services/clsi/app/js/OutputCacheManager.js +++ b/services/clsi/app/js/OutputCacheManager.js @@ -275,36 +275,39 @@ module.exports = OutputCacheManager = { outputDir, OutputCacheManager.path(outputFile.build, outputFile.path) ) + const pdfSize = outputFile.size const timer = new Metrics.Timer('compute-pdf-ranges') - ContentCacheManager.update(contentDir, outputFilePath, function ( - err, - result - ) { - if (err) return callback(err, outputFiles) - const [contentRanges, newContentRanges, reclaimedSpace] = result + ContentCacheManager.update( + contentDir, + outputFilePath, + pdfSize, + function (err, result) { + if (err) return callback(err, outputFiles) + const [contentRanges, newContentRanges, reclaimedSpace] = result - if (Settings.enablePdfCachingDark) { - // In dark mode we are doing the computation only and do not emit - // any ranges to the frontend. - } else { - outputFile.contentId = Path.basename(contentDir) - outputFile.ranges = contentRanges + if (Settings.enablePdfCachingDark) { + // In dark mode we are doing the computation only and do not emit + // any ranges to the frontend. + } else { + outputFile.contentId = Path.basename(contentDir) + outputFile.ranges = contentRanges + } + + timings['compute-pdf-caching'] = timer.done() + stats['pdf-caching-n-ranges'] = contentRanges.length + stats['pdf-caching-total-ranges-size'] = contentRanges.reduce( + (sum, next) => sum + (next.end - next.start), + 0 + ) + stats['pdf-caching-n-new-ranges'] = newContentRanges.length + stats['pdf-caching-new-ranges-size'] = newContentRanges.reduce( + (sum, next) => sum + (next.end - next.start), + 0 + ) + stats['pdf-caching-reclaimed-space'] = reclaimedSpace + callback(null, outputFiles) } - - timings['compute-pdf-caching'] = timer.done() - stats['pdf-caching-n-ranges'] = contentRanges.length - stats['pdf-caching-total-ranges-size'] = contentRanges.reduce( - (sum, next) => sum + (next.end - next.start), - 0 - ) - stats['pdf-caching-n-new-ranges'] = newContentRanges.length - stats['pdf-caching-new-ranges-size'] = newContentRanges.reduce( - (sum, next) => sum + (next.end - next.start), - 0 - ) - stats['pdf-caching-reclaimed-space'] = reclaimedSpace - callback(null, outputFiles) - }) + ) } else { callback(null, outputFiles) } diff --git a/services/clsi/app/lib/pdfjs/FSPdfManager.js b/services/clsi/app/lib/pdfjs/FSPdfManager.js new file mode 100644 index 0000000000..a0450c1e85 --- /dev/null +++ b/services/clsi/app/lib/pdfjs/FSPdfManager.js @@ -0,0 +1,43 @@ +const { PDFDocument } = require('pdfjs-dist/lib/core/document') +const { LocalPdfManager } = require('pdfjs-dist/lib/core/pdf_manager') +const { MissingDataException } = require('pdfjs-dist/lib/core/core_utils') +const { FSStream } = require('./FSStream') + +class FSPdfManager extends LocalPdfManager { + constructor(docId, options) { + const nonEmptyDummyBuffer = Buffer.alloc(1, 0) + super(docId, nonEmptyDummyBuffer) + this.stream = new FSStream(options.fh, 0, options.size) + this.pdfDocument = new PDFDocument(this, this.stream) + } + + async ensure(obj, prop, args) { + try { + const value = obj[prop] + if (typeof value === 'function') { + return value.apply(obj, args) + } + return value + } catch (ex) { + if (!(ex instanceof MissingDataException)) { + throw ex + } + await this.requestRange(ex.begin, ex.end) + return this.ensure(obj, prop, args) + } + } + + requestRange(begin, end) { + return this.stream.requestRange(begin, end) + } + + requestLoadedStream() {} + + onLoadedStream() {} + + terminate(reason) {} +} + +module.exports = { + FSPdfManager +} diff --git a/services/clsi/app/lib/pdfjs/FSStream.js b/services/clsi/app/lib/pdfjs/FSStream.js new file mode 100644 index 0000000000..9179e83f0e --- /dev/null +++ b/services/clsi/app/lib/pdfjs/FSStream.js @@ -0,0 +1,138 @@ +const { Stream } = require('pdfjs-dist/lib/core/stream') +const { MissingDataException } = require('pdfjs-dist/lib/core/core_utils') + +const BUF_SIZE = 1024 // read from the file in 1024 byte pages + +class FSStream extends Stream { + constructor(fh, start, length, dict, cachedBytes) { + const nonEmptyDummyBuffer = Buffer.alloc(1, 0) + super(nonEmptyDummyBuffer, start, length, dict) + delete this.bytes + this.fh = fh + this.cachedBytes = cachedBytes || [] + } + + get length() { + return this.end - this.start + } + + get isEmpty() { + return this.length === 0 + } + + // Manage cached reads from the file + + requestRange(begin, end) { + // expand small ranges to read a larger amount + if (end - begin < BUF_SIZE) { + end = begin + BUF_SIZE + } + end = Math.min(end, this.length) + // keep a cache of previous reads with {begin,end,buffer} values + const result = { + begin: begin, + end: end, + buffer: Buffer.alloc(end - begin, 0) + } + this.cachedBytes.push(result) + return this.fh.read(result.buffer, 0, end - begin, begin) + } + + _ensureGetPos(pos) { + const found = this.cachedBytes.find((x) => { + return x.begin <= pos && pos < x.end + }) + if (!found) { + throw new MissingDataException(pos, pos + 1) + } + return found + } + + _ensureGetRange(begin, end) { + end = Math.min(end, this.length) // BG: handle overflow case + const found = this.cachedBytes.find((x) => { + return x.begin <= begin && end <= x.end + }) + if (!found) { + throw new MissingDataException(begin, end) + } + return found + } + + _readByte(found, pos) { + return found.buffer[pos - found.begin] + } + + _readBytes(found, pos, end) { + return found.buffer.subarray(pos - found.begin, end - found.begin) + } + + // handle accesses to the bytes + + ensureByte(pos) { + this._ensureGetPos(pos) // may throw a MissingDataException + } + + getByte() { + const pos = this.pos + if (this.pos >= this.end) { + return -1 + } + const found = this._ensureGetPos(pos) + return this._readByte(found, this.pos++) + } + + // BG: for a range, end is not included (see Buffer.subarray for example) + + ensureBytes(length, forceClamped = false) { + const pos = this.pos + this._ensureGetRange(pos, pos + length) + } + + getBytes(length, forceClamped = false) { + const pos = this.pos + const strEnd = this.end + + const found = this._ensureGetRange(pos, pos + length) + if (!length) { + const subarray = this._readBytes(found, pos, strEnd) + // `this.bytes` is always a `Uint8Array` here. + return forceClamped ? new Uint8ClampedArray(subarray) : subarray + } + let end = pos + length + if (end > strEnd) { + end = strEnd + } + this.pos = end + const subarray = this._readBytes(found, pos, end) + // `this.bytes` is always a `Uint8Array` here. + return forceClamped ? new Uint8ClampedArray(subarray) : subarray + } + + getByteRange() { + // BG: this isn't needed as far as I can tell + throw new Error('not implemented') + } + + reset() { + this.pos = this.start + } + + moveStart() { + this.start = this.pos + } + + makeSubStream(start, length, dict = null) { + // BG: had to add this check for null length, it is being called with only + // the start value at one point in the xref decoding. The intent is clear + // enough + // - a null length means "to the end of the file" -- not sure how it is + // working in the existing pdfjs code without this. + if (!length) { + length = this.end - start + } + return new FSStream(this.fh, start, length, dict, this.cachedBytes) + } +} + +module.exports = { FSStream } diff --git a/services/clsi/app/lib/pdfjs/parseXrefTable.js b/services/clsi/app/lib/pdfjs/parseXrefTable.js new file mode 100644 index 0000000000..4db1e0cfc7 --- /dev/null +++ b/services/clsi/app/lib/pdfjs/parseXrefTable.js @@ -0,0 +1,24 @@ +const fs = require('fs') +const { FSPdfManager } = require('./FSPdfManager') + +async function parseXrefTable(path, size) { + if (size === 0) { + return [] + } + + const file = await fs.promises.open(path) + try { + const manager = new FSPdfManager(0, { fh: file, size }) + + await manager.ensureDoc('checkHeader') + await manager.ensureDoc('parseStartXRef') + await manager.ensureDoc('parse') + return manager.pdfDocument.catalog.xref.entries + } finally { + file.close() + } +} + +module.exports = { + parseXrefTable +} diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index 773d9f4319..52571c6cc7 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -1008,9 +1008,19 @@ } }, "@overleaf/o-error": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@overleaf/o-error/-/o-error-3.0.0.tgz", - "integrity": "sha512-LsM2s6Iy9G97ktPo0ys4VxtI/m3ahc1ZHwjo5XnhXtjeIkkkVAehsrcRRoV/yWepPjymB0oZonhcfojpjYR/tg==" + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@overleaf/o-error/-/o-error-3.3.1.tgz", + "integrity": "sha512-1FRBYZO0lbJ0U+FRGZVS8ou6RhEw3e2B86WW/NbtBw554g0h5iC8ESf+juIfPMU/WDf/JDIFbg3eB/LnP2RSow==", + "requires": { + "core-js": "^3.8.3" + }, + "dependencies": { + "core-js": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.13.0.tgz", + "integrity": "sha512-iWDbiyha1M5vFwPFmQnvRv+tJzGbFAm6XimJUT0NgHYW3xZEs1SkCAcasWSVFxpI2Xb/V1DDJckq3v90+bQnog==" + } + } }, "@protobufjs/aspromise": { "version": "1.1.2", @@ -5594,6 +5604,11 @@ "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", "dev": true }, + "pdfjs-dist": { + "version": "2.7.570", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.7.570.tgz", + "integrity": "sha512-/ZkA1FwkEOyDaq11JhMLazdwQAA0F9uwrP7h/1L9Akt9KWh1G5/tkzS+bPuUELq2s2GDFnaT+kooN/aSjT7DXQ==" + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", diff --git a/services/clsi/package.json b/services/clsi/package.json index dac3ba9dbb..51870b025a 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -10,7 +10,7 @@ "start": "node $NODE_APP_OPTIONS app.js", "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", "test:acceptance": "npm run test:acceptance:_run -- --grep=$MOCHA_GREP", - "test:unit:_run": "mocha --recursive --reporter spec $@ test/unit/js", + "test:unit:_run": "mocha --recursive --reporter spec $@ test/unit/js test/unit/lib", "test:unit": "npm run test:unit:_run -- --grep=$MOCHA_GREP", "nodemon": "nodemon --config nodemon.json", "lint": "node_modules/.bin/eslint --max-warnings 0 .", @@ -20,6 +20,7 @@ "author": "James Allen <james@sharelatex.com>", "dependencies": { "@overleaf/metrics": "^3.5.1", + "@overleaf/o-error": "^3.3.1", "async": "3.2.0", "body-parser": "^1.19.0", "diskusage": "^1.1.3", @@ -32,6 +33,7 @@ "logger-sharelatex": "^2.2.0", "mysql": "^2.18.1", "p-limit": "^3.1.0", + "pdfjs-dist": "^2.7.570", "request": "^2.88.2", "sequelize": "^5.21.5", "settings-sharelatex": "^1.1.0", diff --git a/services/clsi/scripts/demo-pdfjs-Xref.js b/services/clsi/scripts/demo-pdfjs-Xref.js new file mode 100644 index 0000000000..86be134ef5 --- /dev/null +++ b/services/clsi/scripts/demo-pdfjs-Xref.js @@ -0,0 +1,12 @@ +const fs = require('fs') +const { parseXrefTable } = require('../app/lib/pdfjs/parseXrefTable') + +const pdfPath = process.argv[2] + +async function main() { + const size = (await fs.promises.stat(pdfPath)).size + const xRefEntries = await parseXrefTable(pdfPath, size) + console.log('Xref entries', xRefEntries) +} + +main().catch(console.error) diff --git a/services/clsi/test/acceptance/fixtures/minimal.pdf b/services/clsi/test/acceptance/fixtures/minimal.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d578e90567e15149674581235b0d67a2f9340924 GIT binary patch literal 12313 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 QA+Q2D5hy7olq3=U7fM=w*#H0l literal 0 HcmV?d00001 diff --git a/services/clsi/test/unit/js/ContentCacheManagerTests.js b/services/clsi/test/unit/js/ContentCacheManagerTests.js index 35f22fffae..76c517d8ce 100644 --- a/services/clsi/test/unit/js/ContentCacheManagerTests.js +++ b/services/clsi/test/unit/js/ContentCacheManagerTests.js @@ -1,201 +1,123 @@ +const fs = require('fs') const Path = require('path') -const crypto = require('crypto') -const { Readable } = require('stream') -const SandboxedModule = require('sandboxed-module') -const sinon = require('sinon') const { expect } = require('chai') const MODULE_PATH = '../../../app/js/ContentCacheManager' -class FakeFile { - constructor() { - this.closed = false - this.contents = [] - } - - async write(blob) { - this.contents.push(blob) - return this - } - - async close() { - this.closed = true - return this - } - - toJSON() { - return { - contents: Buffer.concat(this.contents).toString(), - closed: this.closed - } - } -} - -function hash(blob) { - const hash = crypto.createHash('sha256') - hash.update(blob) - return hash.digest('hex') -} - describe('ContentCacheManager', function () { let contentDir, pdfPath - let ContentCacheManager, fs, files, Settings - function load() { - ContentCacheManager = SandboxedModule.require(MODULE_PATH, { - requires: { - fs, - 'settings-sharelatex': Settings - } - }) - } + let ContentCacheManager, files, Settings + before(function () { + Settings = require('settings-sharelatex') + ContentCacheManager = require(MODULE_PATH) + }) let contentRanges, newContentRanges, reclaimed - function run(filePath, done) { - ContentCacheManager.update(contentDir, filePath, (err, ranges) => { - if (err) return done(err) - let newlyReclaimed - ;[contentRanges, newContentRanges, newlyReclaimed] = ranges - reclaimed += newlyReclaimed - done() - }) - } + async function run(filePath, size) { + const result = await ContentCacheManager.promises.update( + contentDir, + filePath, + size + ) + let newlyReclaimed + ;[contentRanges, newContentRanges, newlyReclaimed] = result + reclaimed += newlyReclaimed - beforeEach(function () { - reclaimed = 0 + const fileNames = await fs.promises.readdir(contentDir) + files = {} + for (const fileName of fileNames) { + const path = Path.join(contentDir, fileName) + files[path] = await fs.promises.readFile(path) + } + } + before(function () { contentDir = '/app/output/602cee6f6460fca0ba7921e6/content/1797a7f48f9-5abc1998509dea1f' pdfPath = '/app/output/602cee6f6460fca0ba7921e6/generated-files/1797a7f48ea-8ac6805139f43351/output.pdf' - Settings = { - pdfCachingMinChunkSize: 1024, - enablePdfCachingDark: false - } - files = {} - fs = { - createReadStream: sinon.stub().returns(Readable.from([])), - promises: { - async writeFile(name, blob) { - const file = new FakeFile() - await file.write(Buffer.from(blob)) - await file.close() - files[name] = file - }, - async readFile(name) { - if (!files[name]) { - throw new Error() - } - return files[name].toJSON().contents - }, - async open(name) { - files[name] = new FakeFile() - return files[name] - }, - async stat(name) { - if (!files[name]) { - throw new Error() - } - }, - async rename(oldName, newName) { - if (!files[oldName]) { - throw new Error() - } - files[newName] = files[oldName] - delete files[oldName] - }, - async unlink(name) { - if (!files[name]) { - throw new Error() - } - delete files[name] - } - } - } + + reclaimed = 0 + Settings.pdfCachingMinChunkSize = 1024 }) - describe('with a small minChunkSize', function () { - beforeEach(function () { - Settings.pdfCachingMinChunkSize = 1 - load() + before(async function () { + await fs.promises.rmdir(contentDir, { recursive: true }) + await fs.promises.mkdir(contentDir, { recursive: true }) + await fs.promises.mkdir(Path.dirname(pdfPath), { recursive: true }) + }) + + describe('minimal', function () { + const PATH_MINIMAL = 'test/acceptance/fixtures/minimal.pdf' + const OBJECT_ID_1 = '9 0 ' + const HASH_LARGE = + 'd7cfc73ad2fba4578a437517923e3714927bbf35e63ea88bd93c7a8076cf1fcd' + const OBJECT_ID_2 = '10 0 ' + const HASH_SMALL = + '896749b8343851b0dc385f71616916a7ba0434fcfb56d1fc7e27cd139eaa2f71' + function getChunkPath(hash) { + return Path.join('test/unit/js/snapshots/minimalCompile/chunks', hash) + } + let MINIMAL_SIZE, RANGE_1, RANGE_2, h1, h2, START_1, START_2, END_1, END_2 + before(async function () { + await fs.promises.copyFile(PATH_MINIMAL, pdfPath) + const MINIMAL = await fs.promises.readFile(PATH_MINIMAL) + MINIMAL_SIZE = (await fs.promises.stat(PATH_MINIMAL)).size + RANGE_1 = await fs.promises.readFile(getChunkPath(HASH_LARGE)) + RANGE_2 = await fs.promises.readFile(getChunkPath(HASH_SMALL)) + h1 = HASH_LARGE + h2 = HASH_SMALL + START_1 = MINIMAL.indexOf(RANGE_1) + END_1 = START_1 + RANGE_1.byteLength + START_2 = MINIMAL.indexOf(RANGE_2) + END_2 = START_2 + RANGE_2.byteLength }) + async function runWithMinimal() { + await run(pdfPath, MINIMAL_SIZE) + } - describe('when the ranges are split across chunks', function () { - const RANGE_1 = 'stream123endstream' - const RANGE_2 = 'stream(||)endstream' - const RANGE_3 = 'stream!$%/=endstream' - const h1 = hash(RANGE_1) - const h2 = hash(RANGE_2) - const h3 = hash(RANGE_3) - function runWithSplitStream(done) { - fs.createReadStream - .withArgs(pdfPath) - .returns( - Readable.from([ - Buffer.from('abcstr'), - Buffer.from('eam123endstreamABC'), - Buffer.from('str'), - Buffer.from('eam(||'), - Buffer.from(')end'), - Buffer.from('stream-_~stream!$%/=endstream') - ]) - ) - run(pdfPath, done) - } - beforeEach(function (done) { - runWithSplitStream(done) + describe('with two ranges qualifying', function () { + before(function () { + Settings.pdfCachingMinChunkSize = 500 }) - - it('should produce three ranges', function () { - expect(contentRanges).to.have.length(3) + before(async function () { + await runWithMinimal() + }) + it('should produce two ranges', function () { + expect(contentRanges).to.have.length(2) }) it('should find the correct offsets', function () { expect(contentRanges).to.deep.equal([ { - start: 3, - end: 21, - hash: hash(RANGE_1) + objectId: OBJECT_ID_1, + start: START_1, + end: END_1, + hash: h1 }, { - start: 24, - end: 43, - hash: hash(RANGE_2) - }, - { - start: 46, - end: 66, - hash: hash(RANGE_3) + objectId: OBJECT_ID_2, + start: START_2, + end: END_2, + hash: h2 } ]) }) it('should store the contents', function () { - expect(JSON.parse(JSON.stringify(files))).to.deep.equal({ - [Path.join(contentDir, h1)]: { - contents: RANGE_1, - closed: true - }, - [Path.join(contentDir, h2)]: { - contents: RANGE_2, - closed: true - }, - [Path.join(contentDir, h3)]: { - contents: RANGE_3, - closed: true - }, - [Path.join(contentDir, '.state.v0.json')]: { - contents: JSON.stringify({ + expect(files).to.deep.equal({ + [Path.join(contentDir, h1)]: RANGE_1, + [Path.join(contentDir, h2)]: RANGE_2, + [Path.join(contentDir, '.state.v0.json')]: Buffer.from( + JSON.stringify({ hashAge: [ [h1, 0], - [h2, 0], - [h3, 0] + [h2, 0] ], hashSize: [ - [h1, 18], - [h2, 19], - [h3, 20] + [h1, RANGE_1.byteLength], + [h2, RANGE_2.byteLength] ] - }), - closed: true - } + }) + ) }) }) @@ -203,71 +125,46 @@ describe('ContentCacheManager', function () { expect(contentRanges).to.deep.equal(newContentRanges) }) - describe('when re-running with one stream removed', function () { - function runWithOneSplitStreamRemoved(done) { - fs.createReadStream - .withArgs(pdfPath) - .returns( - Readable.from([ - Buffer.from('abcstr'), - Buffer.from('eam123endstreamABC'), - Buffer.from('stream!$%/=endstream') - ]) - ) - run(pdfPath, done) - } - beforeEach(function (done) { - runWithOneSplitStreamRemoved(done) + describe('when re-running with one range too small', function () { + before(function () { + Settings.pdfCachingMinChunkSize = 1024 }) - it('should produce two ranges', function () { - expect(contentRanges).to.have.length(2) + before(async function () { + await runWithMinimal() + }) + + it('should produce one range', function () { + expect(contentRanges).to.have.length(1) }) it('should find the correct offsets', function () { expect(contentRanges).to.deep.equal([ { - start: 3, - end: 21, - hash: hash(RANGE_1) - }, - { - start: 24, - end: 44, - hash: hash(RANGE_3) + objectId: OBJECT_ID_1, + start: START_1, + end: END_1, + hash: h1 } ]) }) it('should update the age of the 2nd range', function () { - expect(JSON.parse(JSON.stringify(files))).to.deep.equal({ - [Path.join(contentDir, h1)]: { - contents: RANGE_1, - closed: true - }, - [Path.join(contentDir, h2)]: { - contents: RANGE_2, - closed: true - }, - [Path.join(contentDir, h3)]: { - contents: RANGE_3, - closed: true - }, - [Path.join(contentDir, '.state.v0.json')]: { - contents: JSON.stringify({ + expect(files).to.deep.equal({ + [Path.join(contentDir, h1)]: RANGE_1, + [Path.join(contentDir, h2)]: RANGE_2, + [Path.join(contentDir, '.state.v0.json')]: Buffer.from( + JSON.stringify({ hashAge: [ [h1, 0], - [h2, 1], - [h3, 0] + [h2, 1] ], hashSize: [ - [h1, 18], - [h2, 19], - [h3, 20] + [h1, RANGE_1.byteLength], + [h2, RANGE_2.byteLength] ] - }), - closed: true - } + }) + ) }) }) @@ -277,53 +174,35 @@ describe('ContentCacheManager', function () { describe('when re-running 5 more times', function () { for (let i = 0; i < 5; i++) { - beforeEach(function (done) { - runWithOneSplitStreamRemoved(done) + before(async function () { + await runWithMinimal() }) } - it('should still produce two ranges', function () { - expect(contentRanges).to.have.length(2) + it('should still produce one range', function () { + expect(contentRanges).to.have.length(1) }) it('should still find the correct offsets', function () { expect(contentRanges).to.deep.equal([ { - start: 3, - end: 21, - hash: hash(RANGE_1) - }, - { - start: 24, - end: 44, - hash: hash(RANGE_3) + objectId: OBJECT_ID_1, + start: START_1, + end: END_1, + hash: h1 } ]) }) it('should delete the 2nd range', function () { - expect(JSON.parse(JSON.stringify(files))).to.deep.equal({ - [Path.join(contentDir, h1)]: { - contents: RANGE_1, - closed: true - }, - [Path.join(contentDir, h3)]: { - contents: RANGE_3, - closed: true - }, - [Path.join(contentDir, '.state.v0.json')]: { - contents: JSON.stringify({ - hashAge: [ - [h1, 0], - [h3, 0] - ], - hashSize: [ - [h1, 18], - [h3, 20] - ] - }), - closed: true - } + expect(files).to.deep.equal({ + [Path.join(contentDir, h1)]: RANGE_1, + [Path.join(contentDir, '.state.v0.json')]: Buffer.from( + JSON.stringify({ + hashAge: [[h1, 0]], + hashSize: [[h1, RANGE_1.byteLength]] + }) + ) }) }) @@ -332,7 +211,7 @@ describe('ContentCacheManager', function () { }) it('should yield the reclaimed space', function () { - expect(reclaimed).to.equal(RANGE_2.length) + expect(reclaimed).to.equal(RANGE_2.byteLength) }) }) }) diff --git a/services/clsi/test/unit/js/snapshots/minimalCompile/chunks/896749b8343851b0dc385f71616916a7ba0434fcfb56d1fc7e27cd139eaa2f71 b/services/clsi/test/unit/js/snapshots/minimalCompile/chunks/896749b8343851b0dc385f71616916a7ba0434fcfb56d1fc7e27cd139eaa2f71 new file mode 100644 index 0000000000..bb9f891be7 --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/minimalCompile/chunks/896749b8343851b0dc385f71616916a7ba0434fcfb56d1fc7e27cd139eaa2f71 @@ -0,0 +1,7 @@ +obj +<< /Type /ObjStm /Length 447 /Filter /FlateDecode /N 5 /First 32 >> +stream +xœ…RQkÛ0~߯¸ÇK'É’ ¥Ð$„vkGIJ[(yð‘Ò8Ø*ëþýî$¯IûR°déîû>ÝI"H@¥¤Ï9@J!` Vú/gg fí>BZx†J˜Ãù9ˆÛ®]-B„'ZNg îÂk„%¥i\÷!ÅÍõüêÇôûäfŽ’4š®“mÝ©²ô4ô«®9ĶãCY]\ב@‹—ßñï!Ð 4cþ U‰‡f·=ËJgOÚÊòg³î©>—’ËÿÌÛzzÆ>A)å§C9Wøw³K³qªPÃœ#®”à¤/48•¢VX/ ¶TŒð¦p §-%2"³­*¡B;ä‡XâêÚ2£,9’õG¥z;ž†E–Î¥øØJj/‰c +ón­%¯ˆÌ¢ê¨áµ¦f3æˆÎ]!µ¢«¤ç=µyšÍ ,Ós]ô«@ïe+COW.C³ÙÒÖkÚ’ Øc_Å¥X‹ v¢í>ˆNüù‚ÃÌ2u¶«7=˜ìŒñ¸}¥— #•®HVér–9?kvªì6ÞÿªŸÃG«^Åz׬.ö‚ÒvÃó=UyjÀïŠÇ¡p£‘ÍõzìË2 +endstream +endobj diff --git a/services/clsi/test/unit/js/snapshots/minimalCompile/chunks/d7cfc73ad2fba4578a437517923e3714927bbf35e63ea88bd93c7a8076cf1fcd b/services/clsi/test/unit/js/snapshots/minimalCompile/chunks/d7cfc73ad2fba4578a437517923e3714927bbf35e63ea88bd93c7a8076cf1fcd new file mode 100644 index 0000000000000000000000000000000000000000..d503090d3a7389ff13f6c9a2106c66e0ecfbd73b GIT binary patch literal 10161 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 literal 0 HcmV?d00001 diff --git a/services/clsi/test/unit/lib/pdfjsTests.js b/services/clsi/test/unit/lib/pdfjsTests.js new file mode 100644 index 0000000000..64f062c0b8 --- /dev/null +++ b/services/clsi/test/unit/lib/pdfjsTests.js @@ -0,0 +1,78 @@ +const fs = require('fs') +const Path = require('path') +const { expect } = require('chai') +const { parseXrefTable } = require('../../../app/lib/pdfjs/parseXrefTable') +const PATH_EXAMPLES = 'test/acceptance/fixtures/examples/' +const PATH_SNAPSHOTS = 'test/unit/lib/snapshots/' +const EXAMPLES = fs.readdirSync(PATH_EXAMPLES) + +function snapshotPath(example) { + return Path.join(PATH_SNAPSHOTS, example, 'XrefTable.json') +} + +function pdfPath(example) { + return Path.join(PATH_EXAMPLES, example, 'output.pdf') +} + +async function loadContext(example) { + const size = (await fs.promises.stat(pdfPath(example))).size + + let blob + try { + blob = await fs.promises.readFile(snapshotPath(example)) + } catch (e) { + if (e.code !== 'ENOENT') { + throw e + } + } + const snapshot = blob ? JSON.parse(blob) : null + return { + size, + snapshot + } +} + +async function backFillSnapshot(example, size) { + const table = await parseXrefTable(pdfPath(example), size) + await fs.promises.mkdir(Path.dirname(snapshotPath(example)), { + recursive: true + }) + await fs.promises.writeFile( + snapshotPath(example), + JSON.stringify(table, null, 2) + ) + return table +} + +describe('pdfjs', function () { + describe('when the pdf is an empty file', function () { + it('should yield no entries', async function () { + const path = 'does/not/matter.pdf' + const table = await parseXrefTable(path, 0) + expect(table).to.deep.equal([]) + }) + }) + + for (const example of EXAMPLES) { + describe(example, function () { + let size, snapshot + before('load snapshot', async function () { + const ctx = await loadContext(example) + size = ctx.size + snapshot = ctx.snapshot + }) + + before('back fill new snapshot', async function () { + if (snapshot === null) { + console.error('back filling snapshot for', example) + snapshot = await backFillSnapshot(example, size) + } + }) + + it('should produce the expected xRef table', async function () { + const table = await parseXrefTable(pdfPath(example), size) + expect(table).to.deep.equal(snapshot) + }) + }) + } +}) diff --git a/services/clsi/test/unit/lib/snapshots/asymptote/XrefTable.json b/services/clsi/test/unit/lib/snapshots/asymptote/XrefTable.json new file mode 100644 index 0000000000..be9f29164d --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/asymptote/XrefTable.json @@ -0,0 +1,356 @@ +[ + { + "offset": 0, + "gen": 0, + "free": true + }, + { + "offset": 123086, + "gen": 0, + "uncompressed": true + }, + { + "offset": 123405, + "gen": 0, + "uncompressed": true + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 216, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1084, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1244, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4001, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4155, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4297, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4932, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5307, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5495, + "gen": 0, + "uncompressed": true + }, + { + "offset": 30246, + "gen": 0, + "uncompressed": true + }, + { + "offset": 31466, + "gen": 0, + "uncompressed": true + }, + { + "offset": 38398, + "gen": 0, + "uncompressed": true + }, + { + "offset": 39039, + "gen": 0, + "uncompressed": true + }, + { + "offset": 40158, + "gen": 0, + "uncompressed": true + }, + { + "offset": 40897, + "gen": 0, + "uncompressed": true + }, + { + "offset": 65550, + "gen": 0, + "uncompressed": true + }, + { + "offset": 74691, + "gen": 0, + "uncompressed": true + }, + { + "offset": 81693, + "gen": 0, + "uncompressed": true + }, + { + "offset": 97169, + "gen": 0, + "uncompressed": true + }, + { + "offset": 104103, + "gen": 0, + "uncompressed": true + }, + { + "offset": 111180, + "gen": 0, + "uncompressed": true + }, + { + "offset": 118555, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 0 + }, + { + "offset": 6, + "gen": 1 + }, + { + "offset": 6, + "gen": 2 + }, + { + "offset": 6, + "gen": 3 + }, + { + "offset": 6, + "gen": 4 + }, + { + "offset": 6, + "gen": 5 + }, + { + "offset": 6, + "gen": 6 + }, + { + "offset": 6, + "gen": 7 + }, + { + "offset": 6, + "gen": 8 + }, + { + "offset": 6, + "gen": 9 + }, + { + "offset": 6, + "gen": 10 + }, + { + "offset": 6, + "gen": 11 + }, + { + "offset": 6, + "gen": 12 + }, + { + "offset": 6, + "gen": 13 + }, + { + "offset": 6, + "gen": 14 + }, + { + "offset": 6, + "gen": 15 + }, + { + "offset": 6, + "gen": 16 + }, + { + "offset": 6, + "gen": 17 + }, + { + "offset": 6, + "gen": 18 + }, + { + "offset": 6, + "gen": 19 + }, + { + "offset": 6, + "gen": 20 + }, + { + "offset": 6, + "gen": 21 + }, + { + "offset": 6, + "gen": 22 + }, + { + "offset": 6, + "gen": 23 + }, + { + "offset": 6, + "gen": 24 + }, + { + "offset": 6, + "gen": 25 + }, + { + "offset": 6, + "gen": 26 + }, + { + "offset": 6, + "gen": 27 + }, + { + "offset": 6, + "gen": 28 + }, + { + "offset": 6, + "gen": 29 + }, + { + "offset": 6, + "gen": 30 + }, + { + "offset": 6, + "gen": 31 + }, + { + "offset": 6, + "gen": 32 + }, + { + "offset": 6, + "gen": 33 + }, + { + "offset": 6, + "gen": 34 + }, + { + "offset": 6, + "gen": 35 + }, + { + "offset": 6, + "gen": 36 + }, + { + "offset": 6, + "gen": 37 + }, + { + "offset": 6, + "gen": 38 + }, + { + "offset": 6, + "gen": 39 + }, + { + "offset": 6, + "gen": 40 + }, + { + "offset": 6, + "gen": 41 + }, + { + "offset": 6, + "gen": 42 + }, + { + "offset": 6, + "gen": 43 + }, + { + "offset": 6, + "gen": 44 + }, + { + "offset": 6, + "gen": 45 + }, + { + "offset": 6, + "gen": 46 + }, + { + "offset": 6, + "gen": 47 + }, + { + "offset": 6, + "gen": 48 + }, + { + "offset": 6, + "gen": 49 + }, + { + "offset": 6, + "gen": 50 + }, + { + "offset": 6, + "gen": 51 + }, + { + "offset": 6, + "gen": 52 + }, + { + "offset": 6, + "gen": 53 + }, + { + "offset": 6, + "gen": 54 + }, + { + "offset": 6, + "gen": 55 + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/biber_bibliography/XrefTable.json b/services/clsi/test/unit/lib/snapshots/biber_bibliography/XrefTable.json new file mode 100644 index 0000000000..94c0fddf42 --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/biber_bibliography/XrefTable.json @@ -0,0 +1,128 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 9, + "gen": 1 + }, + { + "offset": 9, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 14 + }, + { + "offset": 9, + "gen": 12 + }, + { + "offset": 9, + "gen": 15 + }, + { + "offset": 9, + "gen": 13 + }, + { + "offset": 9, + "gen": 16 + }, + { + "offset": 57274, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 17 + }, + { + "offset": 9, + "gen": 2 + }, + { + "offset": 9, + "gen": 3 + }, + { + "offset": 9, + "gen": 4 + }, + { + "offset": 9, + "gen": 5 + }, + { + "offset": 9, + "gen": 6 + }, + { + "offset": 522, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 7 + }, + { + "offset": 8788, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 8 + }, + { + "offset": 17289, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 9 + }, + { + "offset": 32619, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 10 + }, + { + "offset": 44596, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 11 + }, + { + "offset": 9, + "gen": 18 + }, + { + "offset": 57027, + "gen": 0, + "uncompressed": true + }, + { + "offset": 58655, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/epstopdf/XrefTable.json b/services/clsi/test/unit/lib/snapshots/epstopdf/XrefTable.json new file mode 100644 index 0000000000..fca6e5447d --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/epstopdf/XrefTable.json @@ -0,0 +1,125 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 208, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 11 + }, + { + "offset": 6, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 14 + }, + { + "offset": 29580, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 15 + }, + { + "offset": 6, + "gen": 1 + }, + { + "offset": 6, + "gen": 2 + }, + { + "offset": 6, + "gen": 3 + }, + { + "offset": 6, + "gen": 4 + }, + { + "offset": 6, + "gen": 5 + }, + { + "offset": 6, + "gen": 6 + }, + { + "offset": 6, + "gen": 7 + }, + { + "offset": 6, + "gen": 8 + }, + { + "offset": 18310, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 9 + }, + { + "offset": 6, + "gen": 10 + }, + { + "offset": 18554, + "gen": 0, + "uncompressed": true + }, + { + "offset": 18945, + "gen": 0, + "uncompressed": true + }, + { + "offset": 19674, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 12 + }, + { + "offset": 22318, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 13 + }, + { + "offset": 6, + "gen": 16 + }, + { + "offset": 29321, + "gen": 0, + "uncompressed": true + }, + { + "offset": 30697, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/feynmf/XrefTable.json b/services/clsi/test/unit/lib/snapshots/feynmf/XrefTable.json new file mode 100644 index 0000000000..0da87fc092 --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/feynmf/XrefTable.json @@ -0,0 +1,115 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 8, + "gen": 1 + }, + { + "offset": 8, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 5 + }, + { + "offset": 8, + "gen": 12 + }, + { + "offset": 8, + "gen": 14 + }, + { + "offset": 8, + "gen": 13 + }, + { + "offset": 25628, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 15 + }, + { + "offset": 8, + "gen": 2 + }, + { + "offset": 8, + "gen": 3 + }, + { + "offset": 8, + "gen": 4 + }, + { + "offset": 250, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 6 + }, + { + "offset": 8, + "gen": 7 + }, + { + "offset": 8, + "gen": 8 + }, + { + "offset": 3854, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 9 + }, + { + "offset": 11227, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 10 + }, + { + "offset": 18230, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 11 + }, + { + "offset": 8, + "gen": 16 + }, + { + "offset": 25381, + "gen": 0, + "uncompressed": true + }, + { + "offset": 26423, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/feynmp/XrefTable.json b/services/clsi/test/unit/lib/snapshots/feynmp/XrefTable.json new file mode 100644 index 0000000000..34048b037d --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/feynmp/XrefTable.json @@ -0,0 +1,102 @@ +[ + { + "offset": 0, + "gen": 65535, + "free": true + }, + { + "offset": 1094, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5512, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1035, + "gen": 0, + "uncompressed": true + }, + { + "offset": 875, + "gen": 0, + "uncompressed": true + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 856, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1159, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1593, + "gen": 0, + "uncompressed": true + }, + { + "offset": 3149, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1436, + "gen": 0, + "uncompressed": true + }, + { + "offset": 2412, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1282, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1768, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1200, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1230, + "gen": 0, + "uncompressed": true + }, + { + "offset": 2006, + "gen": 0, + "uncompressed": true + }, + { + "offset": 2625, + "gen": 0, + "uncompressed": true + }, + { + "offset": 3381, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4069, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/fontawesome/XrefTable.json b/services/clsi/test/unit/lib/snapshots/fontawesome/XrefTable.json new file mode 100644 index 0000000000..1951e17f7f --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/fontawesome/XrefTable.json @@ -0,0 +1,93 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 7, + "gen": 1 + }, + { + "offset": 7, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 11 + }, + { + "offset": 7, + "gen": 10 + }, + { + "offset": 7, + "gen": 9 + }, + { + "offset": 29476, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 12 + }, + { + "offset": 7, + "gen": 7 + }, + { + "offset": 7, + "gen": 2 + }, + { + "offset": 7, + "gen": 8 + }, + { + "offset": 7, + "gen": 3 + }, + { + "offset": 7, + "gen": 4 + }, + { + "offset": 255, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 5 + }, + { + "offset": 17910, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 6 + }, + { + "offset": 7, + "gen": 13 + }, + { + "offset": 29228, + "gen": 0, + "uncompressed": true + }, + { + "offset": 30448, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/fontawesome_xelatex/XrefTable.json b/services/clsi/test/unit/lib/snapshots/fontawesome_xelatex/XrefTable.json new file mode 100644 index 0000000000..e6315a28e7 --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/fontawesome_xelatex/XrefTable.json @@ -0,0 +1,126 @@ +[ + { + "offset": 0, + "gen": 0, + "free": true + }, + { + "offset": 6338, + "gen": 0, + "uncompressed": true + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 216, + "gen": 0, + "uncompressed": true + }, + { + "offset": 707, + "gen": 0, + "uncompressed": true + }, + { + "offset": 757, + "gen": 0, + "uncompressed": true + }, + { + "offset": 888, + "gen": 0, + "uncompressed": true + }, + { + "offset": 991, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1257, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1678, + "gen": 0, + "uncompressed": true + }, + { + "offset": 2050, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4246, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4339, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5382, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5475, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5513, + "gen": 0, + "uncompressed": true + }, + { + "offset": 15, + "gen": 0 + }, + { + "offset": 15, + "gen": 1 + }, + { + "offset": 15, + "gen": 2 + }, + { + "offset": 15, + "gen": 3 + }, + { + "offset": 15, + "gen": 4 + }, + { + "offset": 15, + "gen": 5 + }, + { + "offset": 15, + "gen": 6 + }, + { + "offset": 15, + "gen": 7 + }, + { + "offset": 15, + "gen": 8 + }, + { + "offset": 15, + "gen": 9 + }, + { + "offset": 15, + "gen": 10 + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/glossaries/XrefTable.json b/services/clsi/test/unit/lib/snapshots/glossaries/XrefTable.json new file mode 100644 index 0000000000..51f7dca8bb --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/glossaries/XrefTable.json @@ -0,0 +1,94 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 7, + "gen": 1 + }, + { + "offset": 7, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 10 + }, + { + "offset": 7, + "gen": 9 + }, + { + "offset": 7, + "gen": 8 + }, + { + "offset": 33085, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 11 + }, + { + "offset": 7, + "gen": 2 + }, + { + "offset": 7, + "gen": 3 + }, + { + "offset": 7, + "gen": 4 + }, + { + "offset": 445, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 5 + }, + { + "offset": 10048, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 6 + }, + { + "offset": 18151, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 7 + }, + { + "offset": 7, + "gen": 12 + }, + { + "offset": 32838, + "gen": 0, + "uncompressed": true + }, + { + "offset": 34157, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/gnuplot/XrefTable.json b/services/clsi/test/unit/lib/snapshots/gnuplot/XrefTable.json new file mode 100644 index 0000000000..5148059666 --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/gnuplot/XrefTable.json @@ -0,0 +1,89 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 4, + "gen": 0 + }, + { + "offset": 4, + "gen": 1 + }, + { + "offset": 4, + "gen": 2 + }, + { + "offset": 22047, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4, + "gen": 4 + }, + { + "offset": 4, + "gen": 3 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4, + "gen": 10 + }, + { + "offset": 4, + "gen": 9 + }, + { + "offset": 4, + "gen": 11 + }, + { + "offset": 4, + "gen": 5 + }, + { + "offset": 4, + "gen": 6 + }, + { + "offset": 6451, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4, + "gen": 7 + }, + { + "offset": 14825, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4, + "gen": 8 + }, + { + "offset": 4, + "gen": 12 + }, + { + "offset": 21800, + "gen": 0, + "uncompressed": true + }, + { + "offset": 22696, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/hebrew/XrefTable.json b/services/clsi/test/unit/lib/snapshots/hebrew/XrefTable.json new file mode 100644 index 0000000000..d3e8221a2e --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/hebrew/XrefTable.json @@ -0,0 +1,81 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 6, + "gen": 1 + }, + { + "offset": 6, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 8 + }, + { + "offset": 6, + "gen": 7 + }, + { + "offset": 22744, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 9 + }, + { + "offset": 6, + "gen": 2 + }, + { + "offset": 6, + "gen": 6 + }, + { + "offset": 6, + "gen": 3 + }, + { + "offset": 364, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 4 + }, + { + "offset": 12163, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 5 + }, + { + "offset": 6, + "gen": 10 + }, + { + "offset": 22496, + "gen": 0, + "uncompressed": true + }, + { + "offset": 23856, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/knitr/XrefTable.json b/services/clsi/test/unit/lib/snapshots/knitr/XrefTable.json new file mode 100644 index 0000000000..d57e124754 --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/knitr/XrefTable.json @@ -0,0 +1,145 @@ +[ + { + "offset": 0, + "gen": 0, + "free": true + }, + { + "offset": 43543, + "gen": 0, + "uncompressed": true + }, + { + "offset": 43792, + "gen": 0, + "uncompressed": true + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 216, + "gen": 0, + "uncompressed": true + }, + { + "offset": 734, + "gen": 0, + "uncompressed": true + }, + { + "offset": 784, + "gen": 0, + "uncompressed": true + }, + { + "offset": 912, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1020, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1544, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5791, + "gen": 0, + "uncompressed": true + }, + { + "offset": 12911, + "gen": 0, + "uncompressed": true + }, + { + "offset": 23655, + "gen": 0, + "uncompressed": true + }, + { + "offset": 30651, + "gen": 0, + "uncompressed": true + }, + { + "offset": 42597, + "gen": 0, + "uncompressed": true + }, + { + "offset": 14, + "gen": 0 + }, + { + "offset": 14, + "gen": 1 + }, + { + "offset": 14, + "gen": 2 + }, + { + "offset": 14, + "gen": 3 + }, + { + "offset": 14, + "gen": 4 + }, + { + "offset": 14, + "gen": 5 + }, + { + "offset": 14, + "gen": 6 + }, + { + "offset": 14, + "gen": 7 + }, + { + "offset": 14, + "gen": 8 + }, + { + "offset": 14, + "gen": 9 + }, + { + "offset": 14, + "gen": 10 + }, + { + "offset": 14, + "gen": 11 + }, + { + "offset": 14, + "gen": 12 + }, + { + "offset": 14, + "gen": 13 + }, + { + "offset": 14, + "gen": 14 + }, + { + "offset": 14, + "gen": 15 + }, + { + "offset": 14, + "gen": 16 + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/knitr_utf8/XrefTable.json b/services/clsi/test/unit/lib/snapshots/knitr_utf8/XrefTable.json new file mode 100644 index 0000000000..14982555fc --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/knitr_utf8/XrefTable.json @@ -0,0 +1,179 @@ +[ + { + "offset": 0, + "gen": 0, + "free": true + }, + { + "offset": 75291, + "gen": 0, + "uncompressed": true + }, + { + "offset": 75540, + "gen": 0, + "uncompressed": true + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 216, + "gen": 0, + "uncompressed": true + }, + { + "offset": 790, + "gen": 0, + "uncompressed": true + }, + { + "offset": 840, + "gen": 0, + "uncompressed": true + }, + { + "offset": 975, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1083, + "gen": 0, + "uncompressed": true + }, + { + "offset": 2127, + "gen": 0, + "uncompressed": true + }, + { + "offset": 13797, + "gen": 0, + "uncompressed": true + }, + { + "offset": 23679, + "gen": 0, + "uncompressed": true + }, + { + "offset": 31863, + "gen": 0, + "uncompressed": true + }, + { + "offset": 36111, + "gen": 0, + "uncompressed": true + }, + { + "offset": 50346, + "gen": 0, + "uncompressed": true + }, + { + "offset": 61562, + "gen": 0, + "uncompressed": true + }, + { + "offset": 73508, + "gen": 0, + "uncompressed": true + }, + { + "offset": 16, + "gen": 0 + }, + { + "offset": 16, + "gen": 1 + }, + { + "offset": 16, + "gen": 2 + }, + { + "offset": 16, + "gen": 3 + }, + { + "offset": 16, + "gen": 4 + }, + { + "offset": 16, + "gen": 5 + }, + { + "offset": 16, + "gen": 6 + }, + { + "offset": 16, + "gen": 7 + }, + { + "offset": 16, + "gen": 8 + }, + { + "offset": 16, + "gen": 9 + }, + { + "offset": 16, + "gen": 10 + }, + { + "offset": 16, + "gen": 11 + }, + { + "offset": 16, + "gen": 12 + }, + { + "offset": 16, + "gen": 13 + }, + { + "offset": 16, + "gen": 14 + }, + { + "offset": 16, + "gen": 15 + }, + { + "offset": 16, + "gen": 16 + }, + { + "offset": 16, + "gen": 17 + }, + { + "offset": 16, + "gen": 18 + }, + { + "offset": 16, + "gen": 19 + }, + { + "offset": 16, + "gen": 20 + }, + { + "offset": 16, + "gen": 21 + }, + { + "offset": 16, + "gen": 22 + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/latex_compiler/XrefTable.json b/services/clsi/test/unit/lib/snapshots/latex_compiler/XrefTable.json new file mode 100644 index 0000000000..91e596e54f --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/latex_compiler/XrefTable.json @@ -0,0 +1,132 @@ +[ + { + "offset": 0, + "gen": 65535, + "free": true + }, + { + "offset": 17349, + "gen": 0, + "uncompressed": true + }, + { + "offset": 25448, + "gen": 0, + "uncompressed": true + }, + { + "offset": 17290, + "gen": 0, + "uncompressed": true + }, + { + "offset": 17130, + "gen": 0, + "uncompressed": true + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 17109, + "gen": 0, + "uncompressed": true + }, + { + "offset": 17414, + "gen": 0, + "uncompressed": true + }, + { + "offset": 17548, + "gen": 0, + "uncompressed": true + }, + { + "offset": 18912, + "gen": 0, + "uncompressed": true + }, + { + "offset": 18381, + "gen": 0, + "uncompressed": true + }, + { + "offset": 22406, + "gen": 0, + "uncompressed": true + }, + { + "offset": 17897, + "gen": 0, + "uncompressed": true + }, + { + "offset": 21796, + "gen": 0, + "uncompressed": true + }, + { + "offset": 18758, + "gen": 0, + "uncompressed": true + }, + { + "offset": 23361, + "gen": 0, + "uncompressed": true + }, + { + "offset": 17455, + "gen": 0, + "uncompressed": true + }, + { + "offset": 17485, + "gen": 0, + "uncompressed": true + }, + { + "offset": 19218, + "gen": 0, + "uncompressed": true + }, + { + "offset": 22021, + "gen": 0, + "uncompressed": true + }, + { + "offset": 22638, + "gen": 0, + "uncompressed": true + }, + { + "offset": 23599, + "gen": 0, + "uncompressed": true + }, + { + "offset": 18051, + "gen": 0, + "uncompressed": true + }, + { + "offset": 18142, + "gen": 0, + "uncompressed": true + }, + { + "offset": 18657, + "gen": 0, + "uncompressed": true + }, + { + "offset": 24005, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/lualatex_compiler/XrefTable.json b/services/clsi/test/unit/lib/snapshots/lualatex_compiler/XrefTable.json new file mode 100644 index 0000000000..a403bb4f6a --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/lualatex_compiler/XrefTable.json @@ -0,0 +1,74 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 6, + "gen": 1 + }, + { + "offset": 6, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 4 + }, + { + "offset": 6, + "gen": 6 + }, + { + "offset": 2320, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 2 + }, + { + "offset": 202, + "gen": 0, + "uncompressed": true + }, + { + "offset": 298, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 3 + }, + { + "offset": 1642, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 5 + }, + { + "offset": 6, + "gen": 7 + }, + { + "offset": 2120, + "gen": 0, + "uncompressed": true + }, + { + "offset": 2892, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/makeindex-custom-style/XrefTable.json b/services/clsi/test/unit/lib/snapshots/makeindex-custom-style/XrefTable.json new file mode 100644 index 0000000000..b2b9dcef35 --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/makeindex-custom-style/XrefTable.json @@ -0,0 +1,107 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 5, + "gen": 1 + }, + { + "offset": 5, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 12 + }, + { + "offset": 30470, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 13 + }, + { + "offset": 5, + "gen": 3 + }, + { + "offset": 5, + "gen": 2 + }, + { + "offset": 366, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 11 + }, + { + "offset": 5, + "gen": 10 + }, + { + "offset": 5, + "gen": 4 + }, + { + "offset": 5, + "gen": 5 + }, + { + "offset": 5, + "gen": 6 + }, + { + "offset": 638, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 7 + }, + { + "offset": 7838, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 8 + }, + { + "offset": 15674, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 9 + }, + { + "offset": 5, + "gen": 14 + }, + { + "offset": 30223, + "gen": 0, + "uncompressed": true + }, + { + "offset": 31457, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/makeindex/XrefTable.json b/services/clsi/test/unit/lib/snapshots/makeindex/XrefTable.json new file mode 100644 index 0000000000..9a58bed4cb --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/makeindex/XrefTable.json @@ -0,0 +1,90 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 5, + "gen": 1 + }, + { + "offset": 5, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 9 + }, + { + "offset": 23352, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 10 + }, + { + "offset": 5, + "gen": 3 + }, + { + "offset": 5, + "gen": 2 + }, + { + "offset": 366, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 8 + }, + { + "offset": 5, + "gen": 4 + }, + { + "offset": 5, + "gen": 5 + }, + { + "offset": 589, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 6 + }, + { + "offset": 8425, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 7 + }, + { + "offset": 5, + "gen": 11 + }, + { + "offset": 23105, + "gen": 0, + "uncompressed": true + }, + { + "offset": 24245, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/minted/XrefTable.json b/services/clsi/test/unit/lib/snapshots/minted/XrefTable.json new file mode 100644 index 0000000000..f0d2f2a195 --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/minted/XrefTable.json @@ -0,0 +1,77 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 6, + "gen": 1 + }, + { + "offset": 6, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 7 + }, + { + "offset": 6, + "gen": 6 + }, + { + "offset": 19462, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 8 + }, + { + "offset": 6, + "gen": 2 + }, + { + "offset": 6, + "gen": 3 + }, + { + "offset": 340, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 4 + }, + { + "offset": 7343, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 5 + }, + { + "offset": 6, + "gen": 9 + }, + { + "offset": 19215, + "gen": 0, + "uncompressed": true + }, + { + "offset": 20089, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/multibib_bibliography/XrefTable.json b/services/clsi/test/unit/lib/snapshots/multibib_bibliography/XrefTable.json new file mode 100644 index 0000000000..ffd44df50b --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/multibib_bibliography/XrefTable.json @@ -0,0 +1,133 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 6, + "gen": 1 + }, + { + "offset": 6, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 14 + }, + { + "offset": 6, + "gen": 15 + }, + { + "offset": 40591, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 17 + }, + { + "offset": 6, + "gen": 3 + }, + { + "offset": 6, + "gen": 2 + }, + { + "offset": 290, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 16 + }, + { + "offset": 6, + "gen": 5 + }, + { + "offset": 6, + "gen": 4 + }, + { + "offset": 595, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 7 + }, + { + "offset": 6, + "gen": 6 + }, + { + "offset": 844, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 8 + }, + { + "offset": 6, + "gen": 9 + }, + { + "offset": 6, + "gen": 10 + }, + { + "offset": 1107, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 11 + }, + { + "offset": 11816, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 12 + }, + { + "offset": 28180, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 13 + }, + { + "offset": 6, + "gen": 18 + }, + { + "offset": 40344, + "gen": 0, + "uncompressed": true + }, + { + "offset": 41851, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/nomenclature/XrefTable.json b/services/clsi/test/unit/lib/snapshots/nomenclature/XrefTable.json new file mode 100644 index 0000000000..51286af2bc --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/nomenclature/XrefTable.json @@ -0,0 +1,94 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 7, + "gen": 1 + }, + { + "offset": 7, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 8 + }, + { + "offset": 7, + "gen": 9 + }, + { + "offset": 7, + "gen": 10 + }, + { + "offset": 32363, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 11 + }, + { + "offset": 7, + "gen": 2 + }, + { + "offset": 7, + "gen": 3 + }, + { + "offset": 7, + "gen": 4 + }, + { + "offset": 565, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 5 + }, + { + "offset": 10031, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 6 + }, + { + "offset": 18203, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 7 + }, + { + "offset": 7, + "gen": 12 + }, + { + "offset": 32116, + "gen": 0, + "uncompressed": true + }, + { + "offset": 33492, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/references_in_include/XrefTable.json b/services/clsi/test/unit/lib/snapshots/references_in_include/XrefTable.json new file mode 100644 index 0000000000..b0c6731240 --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/references_in_include/XrefTable.json @@ -0,0 +1,90 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 5, + "gen": 1 + }, + { + "offset": 5, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 9 + }, + { + "offset": 15855, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 10 + }, + { + "offset": 5, + "gen": 3 + }, + { + "offset": 5, + "gen": 2 + }, + { + "offset": 167, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 8 + }, + { + "offset": 5, + "gen": 4 + }, + { + "offset": 5, + "gen": 5 + }, + { + "offset": 341, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 6 + }, + { + "offset": 7911, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 7 + }, + { + "offset": 5, + "gen": 11 + }, + { + "offset": 15608, + "gen": 0, + "uncompressed": true + }, + { + "offset": 16597, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/simple_bibliography/XrefTable.json b/services/clsi/test/unit/lib/snapshots/simple_bibliography/XrefTable.json new file mode 100644 index 0000000000..d633b7ba6f --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/simple_bibliography/XrefTable.json @@ -0,0 +1,94 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 7, + "gen": 1 + }, + { + "offset": 7, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 9 + }, + { + "offset": 7, + "gen": 8 + }, + { + "offset": 7, + "gen": 10 + }, + { + "offset": 35573, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 11 + }, + { + "offset": 7, + "gen": 2 + }, + { + "offset": 7, + "gen": 3 + }, + { + "offset": 7, + "gen": 4 + }, + { + "offset": 373, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 5 + }, + { + "offset": 8639, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 6 + }, + { + "offset": 23349, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 7 + }, + { + "offset": 7, + "gen": 12 + }, + { + "offset": 35326, + "gen": 0, + "uncompressed": true + }, + { + "offset": 36668, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/subdirectories/XrefTable.json b/services/clsi/test/unit/lib/snapshots/subdirectories/XrefTable.json new file mode 100644 index 0000000000..06ed0fdf7d --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/subdirectories/XrefTable.json @@ -0,0 +1,108 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 548, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 1 + }, + { + "offset": 9, + "gen": 2 + }, + { + "offset": 9, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 10 + }, + { + "offset": 9, + "gen": 9 + }, + { + "offset": 9, + "gen": 11 + }, + { + "offset": 46398, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 12 + }, + { + "offset": 8100, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 3 + }, + { + "offset": 9, + "gen": 4 + }, + { + "offset": 9, + "gen": 5 + }, + { + "offset": 9693, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 6 + }, + { + "offset": 17959, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 7 + }, + { + "offset": 34174, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 8 + }, + { + "offset": 9, + "gen": 13 + }, + { + "offset": 46151, + "gen": 0, + "uncompressed": true + }, + { + "offset": 47562, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/tikz_feynman/XrefTable.json b/services/clsi/test/unit/lib/snapshots/tikz_feynman/XrefTable.json new file mode 100644 index 0000000000..afeaa84536 --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/tikz_feynman/XrefTable.json @@ -0,0 +1,145 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 8, + "gen": 8 + }, + { + "offset": 8, + "gen": 9 + }, + { + "offset": 8, + "gen": 10 + }, + { + "offset": 8, + "gen": 1 + }, + { + "offset": 8, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 20 + }, + { + "offset": 33577, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 3 + }, + { + "offset": 8, + "gen": 2 + }, + { + "offset": 1151, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 17 + }, + { + "offset": 8, + "gen": 19 + }, + { + "offset": 8, + "gen": 18 + }, + { + "offset": 8, + "gen": 5 + }, + { + "offset": 8, + "gen": 4 + }, + { + "offset": 2721, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 7 + }, + { + "offset": 8, + "gen": 6 + }, + { + "offset": 5757, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 11 + }, + { + "offset": 8, + "gen": 12 + }, + { + "offset": 8, + "gen": 13 + }, + { + "offset": 9558, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 14 + }, + { + "offset": 18967, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 15 + }, + { + "offset": 26388, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 16 + }, + { + "offset": 8, + "gen": 21 + }, + { + "offset": 33354, + "gen": 0, + "uncompressed": true + }, + { + "offset": 34451, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/lib/snapshots/xelatex_compiler/XrefTable.json b/services/clsi/test/unit/lib/snapshots/xelatex_compiler/XrefTable.json new file mode 100644 index 0000000000..31080439b5 --- /dev/null +++ b/services/clsi/test/unit/lib/snapshots/xelatex_compiler/XrefTable.json @@ -0,0 +1,73 @@ +[ + { + "offset": 0, + "gen": 65535, + "free": true + }, + { + "offset": 8, + "gen": 4 + }, + { + "offset": 8, + "gen": 3 + }, + { + "offset": 8, + "gen": 1 + }, + { + "offset": 8, + "gen": 6 + }, + { + "offset": 8, + "gen": 8 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 0 + }, + { + "offset": 6857, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 2 + }, + { + "offset": 265, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 7 + }, + { + "offset": 8, + "gen": 5 + }, + { + "offset": 701, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6750, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7655, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file From 1127bf640e9662613916b19acc63b18929c8e4d6 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 1 Jun 2021 15:51:09 +0100 Subject: [PATCH 725/754] [misc] wait for refreshing of expiry timeout before triggering cleanup --- services/clsi/app.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/clsi/app.js b/services/clsi/app.js index 77ab922987..8dd8a8cbf3 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -412,8 +412,9 @@ if (!module.parent) { module.exports = app setInterval(() => { - ProjectPersistenceManager.refreshExpiryTimeout() - ProjectPersistenceManager.clearExpiredProjects() + ProjectPersistenceManager.refreshExpiryTimeout(() => { + ProjectPersistenceManager.clearExpiredProjects() + }) }, tenMinutes) function __guard__(value, transform) { From 7380b54900b8d2d0580f70ebe374a2b21870bdd4 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 1 Jun 2021 15:52:41 +0100 Subject: [PATCH 726/754] [ProjectPersistenceManager] check all user content dirs for full disk --- .../clsi/app/js/ProjectPersistenceManager.js | 41 ++++++++++++------- .../unit/js/ProjectPersistenceManagerTests.js | 18 ++++---- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/services/clsi/app/js/ProjectPersistenceManager.js b/services/clsi/app/js/ProjectPersistenceManager.js index d0fd4c2439..ca903f4410 100644 --- a/services/clsi/app/js/ProjectPersistenceManager.js +++ b/services/clsi/app/js/ProjectPersistenceManager.js @@ -21,34 +21,45 @@ const logger = require('logger-sharelatex') const oneDay = 24 * 60 * 60 * 1000 const Settings = require('settings-sharelatex') const diskusage = require('diskusage') +const { callbackify } = require('util') -module.exports = ProjectPersistenceManager = { - EXPIRY_TIMEOUT: Settings.project_cache_length_ms || oneDay * 2.5, - - refreshExpiryTimeout(callback) { - if (callback == null) { - callback = function (error) {} - } - diskusage.check('/', function (err, stats) { - if (err) { - logger.err({ err: err }, 'error getting disk usage') - return callback(err) - } +async function refreshExpiryTimeout() { + const paths = [ + Settings.path.compilesDir, + Settings.path.outputDir, + Settings.path.clsiCacheDir + ] + for (const path of paths) { + try { + const stats = await diskusage.check(path) const lowDisk = stats.available / stats.total < 0.1 + const lowerExpiry = ProjectPersistenceManager.EXPIRY_TIMEOUT * 0.9 if (lowDisk && Settings.project_cache_length_ms / 2 < lowerExpiry) { logger.warn( { - stats: stats, + stats, newExpiryTimeoutInDays: (lowerExpiry / oneDay).toFixed(2) }, 'disk running low on space, modifying EXPIRY_TIMEOUT' ) ProjectPersistenceManager.EXPIRY_TIMEOUT = lowerExpiry + break } - callback() - }) + } catch (err) { + logger.err({ err, path }, 'error getting disk usage') + } + } +} + +module.exports = ProjectPersistenceManager = { + EXPIRY_TIMEOUT: Settings.project_cache_length_ms || oneDay * 2.5, + + promises: { + refreshExpiryTimeout }, + + refreshExpiryTimeout: callbackify(refreshExpiryTimeout), markProjectAsJustAccessed(project_id, callback) { if (callback == null) { callback = function (error) {} diff --git a/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js b/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js index 5fb1827206..e60de54ff3 100644 --- a/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js +++ b/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js @@ -28,7 +28,12 @@ describe('ProjectPersistenceManager', function () { './CompileManager': (this.CompileManager = {}), diskusage: (this.diskusage = { check: sinon.stub() }), 'settings-sharelatex': (this.settings = { - project_cache_length_ms: 1000 + project_cache_length_ms: 1000, + path: { + compilesDir: '/compiles', + outputDir: '/output', + clsiCacheDir: '/cache' + } }), './db': (this.db = {}) } @@ -40,7 +45,7 @@ describe('ProjectPersistenceManager', function () { describe('refreshExpiryTimeout', function () { it('should leave expiry alone if plenty of disk', function (done) { - this.diskusage.check.callsArgWith(1, null, { + this.diskusage.check.resolves({ available: 40, total: 100 }) @@ -54,7 +59,7 @@ describe('ProjectPersistenceManager', function () { }) it('should drop EXPIRY_TIMEOUT 10% if low disk usage', function (done) { - this.diskusage.check.callsArgWith(1, null, { + this.diskusage.check.resolves({ available: 5, total: 100 }) @@ -66,7 +71,7 @@ describe('ProjectPersistenceManager', function () { }) it('should not drop EXPIRY_TIMEOUT to below 50% of project_cache_length_ms', function (done) { - this.diskusage.check.callsArgWith(1, null, { + this.diskusage.check.resolves({ available: 5, total: 100 }) @@ -78,10 +83,7 @@ describe('ProjectPersistenceManager', function () { }) it('should not modify EXPIRY_TIMEOUT if there is an error getting disk values', function (done) { - this.diskusage.check.callsArgWith(1, 'Error', { - available: 5, - total: 100 - }) + this.diskusage.check.throws(new Error()) this.ProjectPersistenceManager.refreshExpiryTimeout(() => { this.ProjectPersistenceManager.EXPIRY_TIMEOUT.should.equal(1000) done() From 0c06d0daa6f27b00092020d3bd604b1118802013 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Jun 2021 07:58:49 +0000 Subject: [PATCH 727/754] Bump glob-parent from 5.1.0 to 5.1.2 Bumps [glob-parent](https://github.com/gulpjs/glob-parent) from 5.1.0 to 5.1.2. - [Release notes](https://github.com/gulpjs/glob-parent/releases) - [Changelog](https://github.com/gulpjs/glob-parent/blob/main/CHANGELOG.md) - [Commits](https://github.com/gulpjs/glob-parent/compare/v5.1.0...v5.1.2) --- updated-dependencies: - dependency-name: glob-parent dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> --- services/clsi/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index 52571c6cc7..cca636d5b0 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -3474,9 +3474,9 @@ } }, "glob-parent": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", - "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { "is-glob": "^4.0.1" From 138119a06325e46af7f9635bac822bb158ba71d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 12 Jun 2021 07:23:12 +0000 Subject: [PATCH 728/754] Bump normalize-url from 4.5.0 to 4.5.1 Bumps [normalize-url](https://github.com/sindresorhus/normalize-url) from 4.5.0 to 4.5.1. - [Release notes](https://github.com/sindresorhus/normalize-url/releases) - [Commits](https://github.com/sindresorhus/normalize-url/commits) --- updated-dependencies: - dependency-name: normalize-url dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> --- services/clsi/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index 52571c6cc7..3bc09ef468 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -5271,9 +5271,9 @@ "dev": true }, "normalize-url": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", - "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", "dev": true }, "npm-bundled": { From d40065fd8eb7ead39ae351611ec7fb866b1129d6 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Mon, 14 Jun 2021 12:24:48 +0100 Subject: [PATCH 729/754] [misc] track delay of using sqlite for last access time of a project --- services/clsi/app/js/ProjectPersistenceManager.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/js/ProjectPersistenceManager.js b/services/clsi/app/js/ProjectPersistenceManager.js index d0fd4c2439..f5d32caaca 100644 --- a/services/clsi/app/js/ProjectPersistenceManager.js +++ b/services/clsi/app/js/ProjectPersistenceManager.js @@ -21,6 +21,7 @@ const logger = require('logger-sharelatex') const oneDay = 24 * 60 * 60 * 1000 const Settings = require('settings-sharelatex') const diskusage = require('diskusage') +const Metrics = require('./Metrics') module.exports = ProjectPersistenceManager = { EXPIRY_TIMEOUT: Settings.project_cache_length_ms || oneDay * 2.5, @@ -53,6 +54,7 @@ module.exports = ProjectPersistenceManager = { if (callback == null) { callback = function (error) {} } + const timer = new Metrics.Timer('db-bump-last-accessed') const job = (cb) => db.Project.findOrCreate({ where: { project_id } }) .spread((project, created) => @@ -62,7 +64,10 @@ module.exports = ProjectPersistenceManager = { .error(cb) ) .error(cb) - return dbQueue.queue.push(job, callback) + dbQueue.queue.push(job, (error) => { + timer.done() + callback(error) + }) }, clearExpiredProjects(callback) { From 8f1ea7f0d1f5cdb6ae7e071671389f88e8d9d520 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Mon, 14 Jun 2021 13:03:02 +0100 Subject: [PATCH 730/754] [misc] track delays of using sqlite for url caching details --- services/clsi/app/js/UrlCache.js | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/services/clsi/app/js/UrlCache.js b/services/clsi/app/js/UrlCache.js index 23afafaaa8..a8b2b19bea 100644 --- a/services/clsi/app/js/UrlCache.js +++ b/services/clsi/app/js/UrlCache.js @@ -21,6 +21,7 @@ const crypto = require('crypto') const fs = require('fs') const logger = require('logger-sharelatex') const async = require('async') +const Metrics = require('./Metrics') module.exports = UrlCache = { downloadUrlToFile(project_id, url, destPath, lastModified, callback) { @@ -206,17 +207,22 @@ module.exports = UrlCache = { if (callback == null) { callback = function (error, urlDetails) {} } + const timer = new Metrics.Timer('db-find-url-details') const job = (cb) => db.UrlCache.findOne({ where: { url, project_id } }) .then((urlDetails) => cb(null, urlDetails)) .error(cb) - return dbQueue.queue.push(job, callback) + dbQueue.queue.push(job, (error, urlDetails) => { + timer.done() + callback(error, urlDetails) + }) }, _updateOrCreateUrlDetails(project_id, url, lastModified, callback) { if (callback == null) { callback = function (error) {} } + const timer = new Metrics.Timer('db-update-or-create-url-details') const job = (cb) => db.UrlCache.findOrCreate({ where: { url, project_id } }) .spread((urlDetails, created) => @@ -226,24 +232,32 @@ module.exports = UrlCache = { .error(cb) ) .error(cb) - return dbQueue.queue.push(job, callback) + dbQueue.queue.push(job, (error) => { + timer.done() + callback(error) + }) }, _clearUrlDetails(project_id, url, callback) { if (callback == null) { callback = function (error) {} } + const timer = new Metrics.Timer('db-clear-url-details') const job = (cb) => db.UrlCache.destroy({ where: { url, project_id } }) .then(() => cb(null)) .error(cb) - return dbQueue.queue.push(job, callback) + dbQueue.queue.push(job, (error) => { + timer.done() + callback(error) + }) }, _findAllUrlsInProject(project_id, callback) { if (callback == null) { callback = function (error, urls) {} } + const timer = new Metrics.Timer('db-find-urls-in-project') const job = (cb) => db.UrlCache.findAll({ where: { project_id } }) .then((urlEntries) => @@ -253,6 +267,9 @@ module.exports = UrlCache = { ) ) .error(cb) - return dbQueue.queue.push(job, callback) + dbQueue.queue.push(job, (err, urls) => { + timer.done() + callback(err, urls) + }) } } From 8cc4517d4c3151778a23081e15143570a7e113f4 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 15 Jun 2021 09:08:32 +0100 Subject: [PATCH 731/754] [misc] move import to avoid git conflict --- services/clsi/app/js/ProjectPersistenceManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/js/ProjectPersistenceManager.js b/services/clsi/app/js/ProjectPersistenceManager.js index f5d32caaca..d0e3af3a67 100644 --- a/services/clsi/app/js/ProjectPersistenceManager.js +++ b/services/clsi/app/js/ProjectPersistenceManager.js @@ -12,6 +12,7 @@ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ let ProjectPersistenceManager +const Metrics = require('./Metrics') const UrlCache = require('./UrlCache') const CompileManager = require('./CompileManager') const db = require('./db') @@ -21,7 +22,6 @@ const logger = require('logger-sharelatex') const oneDay = 24 * 60 * 60 * 1000 const Settings = require('settings-sharelatex') const diskusage = require('diskusage') -const Metrics = require('./Metrics') module.exports = ProjectPersistenceManager = { EXPIRY_TIMEOUT: Settings.project_cache_length_ms || oneDay * 2.5, From a76a9a3ef8d5488814d1c9fec04860bb8d9f32d7 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 15 Jun 2021 09:24:23 +0100 Subject: [PATCH 732/754] [misc] add missing dependency entry for send --- services/clsi/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/services/clsi/package.json b/services/clsi/package.json index 51870b025a..3b3e971934 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -35,6 +35,7 @@ "p-limit": "^3.1.0", "pdfjs-dist": "^2.7.570", "request": "^2.88.2", + "send": "^0.17.1", "sequelize": "^5.21.5", "settings-sharelatex": "^1.1.0", "sqlite3": "^4.1.1", From 8e2cd57cb934686b66db0c6eee617ab64b60f52f Mon Sep 17 00:00:00 2001 From: Henry Oswald <henry.oswald@sharelatex.com> Date: Mon, 21 Jun 2021 15:18:51 +0100 Subject: [PATCH 733/754] very basic mvp for shedding load When the box is over 100% capacity start to shed load. Use 5 min load average to make the system less likely to start moving projects off after a temporary cpu spike --- services/clsi/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app.js b/services/clsi/app.js index 8dd8a8cbf3..b2c261bc2e 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -331,7 +331,7 @@ const loadTcpServer = net.createServer(function (socket) { const freeLoad = availableWorkingCpus - currentLoad let freeLoadPercentage = Math.round((freeLoad / availableWorkingCpus) * 100) if (freeLoadPercentage <= 0) { - freeLoadPercentage = 1 // when its 0 the server is set to drain and will move projects to different servers + freeLoadPercentage = 0 // when its 0 the server is set to drain and will move projects to different servers } socket.write(`up, ${freeLoadPercentage}%\n`, 'ASCII') return socket.end() From fb3966ef354e823b4b1828c0a0d3c3d4219044ae Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 22 Jun 2021 12:13:19 +0100 Subject: [PATCH 734/754] [misc] CompileController: simplify composing of outputFiles --- services/clsi/app/js/CompileController.js | 14 ++------------ .../clsi/test/unit/js/CompileControllerTests.js | 12 ++---------- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/services/clsi/app/js/CompileController.js b/services/clsi/app/js/CompileController.js index 2813bca984..4307736d5a 100644 --- a/services/clsi/app/js/CompileController.js +++ b/services/clsi/app/js/CompileController.js @@ -123,7 +123,7 @@ module.exports = CompileController = { stats, timings, outputFiles: outputFiles.map((file) => { - const record = { + return { url: `${Settings.apis.clsi.url}/project/${request.project_id}` + (request.user_id != null @@ -131,18 +131,8 @@ module.exports = CompileController = { : '') + (file.build != null ? `/build/${file.build}` : '') + `/output/${file.path}`, - path: file.path, - type: file.type, - build: file.build, - contentId: file.contentId + ...file } - if (file.ranges != null) { - record.ranges = file.ranges - } - if (file.size != null) { - record.size = file.size - } - return record }) } }) diff --git a/services/clsi/test/unit/js/CompileControllerTests.js b/services/clsi/test/unit/js/CompileControllerTests.js index 6d6b34ee58..6236be77f0 100644 --- a/services/clsi/test/unit/js/CompileControllerTests.js +++ b/services/clsi/test/unit/js/CompileControllerTests.js @@ -157,11 +157,7 @@ describe('CompileController', function () { outputFiles: this.output_files.map((file) => { return { url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`, - path: file.path, - type: file.type, - build: file.build, - // gets dropped by JSON.stringify - contentId: undefined + ...file } }) } @@ -202,11 +198,7 @@ describe('CompileController', function () { outputFiles: this.output_files.map((file) => { return { url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`, - path: file.path, - type: file.type, - build: file.build, - // gets dropped by JSON.stringify - contentId: undefined + ...file } }) } From ffaff1bd7232de94be7cc58f0f84e6354eb3832e Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 22 Jun 2021 12:14:33 +0100 Subject: [PATCH 735/754] [CompileController] emit status=failure for an empty output.pdf file --- services/clsi/app/js/CompileController.js | 2 +- .../test/unit/js/CompileControllerTests.js | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/js/CompileController.js b/services/clsi/app/js/CompileController.js index 4307736d5a..63a4106aa0 100644 --- a/services/clsi/app/js/CompileController.js +++ b/services/clsi/app/js/CompileController.js @@ -88,7 +88,7 @@ module.exports = CompileController = { let file status = 'failure' for (file of Array.from(outputFiles)) { - if (file.path === 'output.pdf') { + if (file.path === 'output.pdf' && file.size > 0) { status = 'success' } } diff --git a/services/clsi/test/unit/js/CompileControllerTests.js b/services/clsi/test/unit/js/CompileControllerTests.js index 6236be77f0..3b29c867c4 100644 --- a/services/clsi/test/unit/js/CompileControllerTests.js +++ b/services/clsi/test/unit/js/CompileControllerTests.js @@ -99,6 +99,7 @@ describe('CompileController', function () { { path: 'output.pdf', type: 'pdf', + size: 1337, build: 1234 }, { @@ -207,6 +208,48 @@ describe('CompileController', function () { }) }) + describe('with an empty output.pdf', function () { + beforeEach(function () { + this.output_files = [ + { + path: 'output.pdf', + type: 'pdf', + size: 0, + build: 1234 + }, + { + path: 'output.log', + type: 'log', + build: 1234 + } + ] + this.CompileManager.doCompileWithLock = sinon + .stub() + .yields(null, this.output_files, this.stats, this.timings) + this.CompileController.compile(this.req, this.res) + }) + + it('should return the JSON response with status failure', function () { + this.res.status.calledWith(200).should.equal(true) + this.res.send + .calledWith({ + compile: { + status: 'failure', + error: null, + stats: this.stats, + timings: this.timings, + outputFiles: this.output_files.map((file) => { + return { + url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`, + ...file + } + }) + } + }) + .should.equal(true) + }) + }) + describe('with an error', function () { beforeEach(function () { this.CompileManager.doCompileWithLock = sinon From a4389fb761b670c6424ccd9fdb71d89dd7a46038 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Wed, 23 Jun 2021 09:15:05 +0100 Subject: [PATCH 736/754] [misc] ContentCacheMetrics: log slow pdf caching performance --- services/clsi/app/js/ContentCacheMetrics.js | 26 +++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/services/clsi/app/js/ContentCacheMetrics.js b/services/clsi/app/js/ContentCacheMetrics.js index f5f9188ddc..d2da9d8601 100644 --- a/services/clsi/app/js/ContentCacheMetrics.js +++ b/services/clsi/app/js/ContentCacheMetrics.js @@ -1,4 +1,20 @@ +const logger = require('logger-sharelatex') const Metrics = require('./Metrics') +const os = require('os') + +let CACHED_LOAD = { + expires: -1, + load: [0, 0, 0] +} +function getSystemLoad() { + if (CACHED_LOAD.expires < Date.now()) { + CACHED_LOAD = { + expires: Date.now() + 10 * 1000, + load: os.loadavg() + } + } + return CACHED_LOAD.load +} const ONE_MB = 1024 * 1024 @@ -20,6 +36,16 @@ function emitPdfCachingStats(stats, timings) { ? timings.compileE2E / (timings.compileE2E - timings['compute-pdf-caching']) : 1 + if (fraction > 1.5) { + logger.warn( + { + stats, + timings, + load: getSystemLoad() + }, + 'slow pdf caching' + ) + } Metrics.summary('overhead-compute-pdf-ranges', fraction * 100 - 100) // How does the hashing scale to pdf size in MB? From 11a44ff07ce5b3b2cb053ac315b13d105a53d225 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Wed, 23 Jun 2021 11:27:19 +0100 Subject: [PATCH 737/754] [misc] ContentCacheMetrics: apply review feedback: ignore fast compiles Co-Authored-By: Brian Gough <brian.gough@overleaf.com> --- services/clsi/app/js/ContentCacheMetrics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app/js/ContentCacheMetrics.js b/services/clsi/app/js/ContentCacheMetrics.js index d2da9d8601..5c54ba0861 100644 --- a/services/clsi/app/js/ContentCacheMetrics.js +++ b/services/clsi/app/js/ContentCacheMetrics.js @@ -36,7 +36,7 @@ function emitPdfCachingStats(stats, timings) { ? timings.compileE2E / (timings.compileE2E - timings['compute-pdf-caching']) : 1 - if (fraction > 1.5) { + if (fraction > 1.5 && timings.compileE2E > 10 * 1000) { logger.warn( { stats, From 97693b49c2a6cffc2899def1568915eec02da279 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Wed, 23 Jun 2021 11:28:31 +0100 Subject: [PATCH 738/754] [ContentCacheMetrics] add new metric for absolute time spent in PDF.js --- services/clsi/app/js/ContentCacheMetrics.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/clsi/app/js/ContentCacheMetrics.js b/services/clsi/app/js/ContentCacheMetrics.js index 5c54ba0861..0078406926 100644 --- a/services/clsi/app/js/ContentCacheMetrics.js +++ b/services/clsi/app/js/ContentCacheMetrics.js @@ -30,6 +30,9 @@ function emitPdfStats(stats, timings) { function emitPdfCachingStats(stats, timings) { if (!stats['pdf-size']) return // double check + // How much extra time did we spent in PDF.js? + Metrics.timing('compute-pdf-caching', timings['compute-pdf-caching']) + // How large is the overhead of hashing up-front? const fraction = timings.compileE2E - timings['compute-pdf-caching'] !== 0 From b09e52510f9dc009963a7f7e84612371e21ddb5a Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Wed, 23 Jun 2021 14:14:28 +0100 Subject: [PATCH 739/754] [misc] bail out from pdf caching processing after 10s or earlier ...for fast compiles. --- services/clsi/app/js/CompileManager.js | 5 ++- services/clsi/app/js/ContentCacheManager.js | 44 +++++++++++++++++-- services/clsi/app/js/ContentCacheMetrics.js | 3 ++ services/clsi/app/js/Errors.js | 5 +++ services/clsi/app/js/OutputCacheManager.js | 10 +++++ services/clsi/app/lib/pdfjs/FSPdfManager.js | 4 +- services/clsi/app/lib/pdfjs/FSStream.js | 14 +++++- services/clsi/app/lib/pdfjs/parseXrefTable.js | 7 ++- services/clsi/config/settings.defaults.js | 4 +- services/clsi/test/unit/lib/pdfjsTests.js | 22 +++++++++- 10 files changed, 103 insertions(+), 15 deletions(-) diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js index c771082bfd..1d681e8de7 100644 --- a/services/clsi/app/js/CompileManager.js +++ b/services/clsi/app/js/CompileManager.js @@ -292,6 +292,8 @@ module.exports = CompileManager = { timings['cpu-time'] / stats['latex-runs'] ) } + // Emit compile time. + timings.compile = ts return OutputFileFinder.findOutputFiles( resourceList, @@ -317,8 +319,7 @@ module.exports = CompileManager = { ) } - // Emit compile time. - timings.compile = ts + // Emit e2e compile time. timings.compileE2E = timerE2E.done() if (stats['pdf-size']) { diff --git a/services/clsi/app/js/ContentCacheManager.js b/services/clsi/app/js/ContentCacheManager.js index ab73c61d93..cbd6f3cde0 100644 --- a/services/clsi/app/js/ContentCacheManager.js +++ b/services/clsi/app/js/ContentCacheManager.js @@ -10,21 +10,26 @@ const Settings = require('settings-sharelatex') const OError = require('@overleaf/o-error') const pLimit = require('p-limit') const { parseXrefTable } = require('../lib/pdfjs/parseXrefTable') +const { TimedOutError } = require('./Errors') /** * * @param {String} contentDir path to directory where content hash files are cached * @param {String} filePath the pdf file to scan for streams * @param {number} size the pdf size + * @param {number} compileTime */ -async function update(contentDir, filePath, size) { +async function update(contentDir, filePath, size, compileTime) { + const checkDeadline = getDeadlineChecker(compileTime) const ranges = [] const newRanges = [] // keep track of hashes expire old ones when they reach a generation > N. const tracker = await HashFileTracker.from(contentDir) tracker.updateAge() - const rawTable = await parseXrefTable(filePath, size) + checkDeadline('after init HashFileTracker') + + const rawTable = await parseXrefTable(filePath, size, checkDeadline) rawTable.sort((a, b) => { return a.offset - b.offset }) @@ -32,6 +37,8 @@ async function update(contentDir, filePath, size) { obj.idx = idx }) + checkDeadline('after parsing') + const uncompressedObjects = [] for (const object of rawTable) { if (!object.uncompressed) { @@ -50,12 +57,14 @@ async function update(contentDir, filePath, size) { if (size < Settings.pdfCachingMinChunkSize) { continue } - uncompressedObjects.push(object) + uncompressedObjects.push({ object, idx: uncompressedObjects.length }) } + checkDeadline('after finding uncompressed') + const handle = await fs.promises.open(filePath) try { - for (const object of uncompressedObjects) { + for (const { object, idx } of uncompressedObjects) { let buffer = Buffer.alloc(object.size, 0) const { bytesRead } = await handle.read( buffer, @@ -63,6 +72,7 @@ async function update(contentDir, filePath, size) { object.size, object.offset ) + checkDeadline('after read ' + idx) if (bytesRead !== object.size) { throw new OError('could not read full chunk', { object, @@ -80,6 +90,7 @@ async function update(contentDir, filePath, size) { buffer = buffer.subarray(objectIdRaw.byteLength) const hash = pdfStreamHash(buffer) + checkDeadline('after hash ' + idx) const range = { objectId: objectIdRaw.toString(), start: object.offset + objectIdRaw.byteLength, @@ -92,12 +103,15 @@ async function update(contentDir, filePath, size) { if (tracker.track(range)) continue await writePdfStream(contentDir, hash, buffer) + checkDeadline('after write ' + idx) newRanges.push(range) } } finally { await handle.close() } + // NOTE: Bailing out below does not make sense. + // Let the next compile use the already written ranges. const reclaimedSpace = await tracker.deleteStaleHashes(5) await tracker.flush() return [ranges, newRanges, reclaimedSpace] @@ -219,6 +233,28 @@ async function writePdfStream(dir, hash, buffer) { } } +function getDeadlineChecker(compileTime) { + const maxOverhead = Math.min( + // Adding 10s to a 40s compile time is OK. + compileTime / 4, + // Adding 30s to a 120s compile time is not OK, limit to 10s. + Settings.pdfCachingMaxProcessingTime + ) + + const deadline = Date.now() + maxOverhead + let lastStage = { stage: 'start', now: Date.now() } + return function (stage) { + const now = Date.now() + if (now > deadline) { + throw new TimedOutError(stage, { + lastStage: lastStage.stage, + diffToLastStage: now - lastStage.now + }) + } + lastStage = { stage, now } + } +} + function promiseMapWithLimit(concurrency, array, fn) { const limit = pLimit(concurrency) return Promise.all(array.map((x) => limit(() => fn(x)))) diff --git a/services/clsi/app/js/ContentCacheMetrics.js b/services/clsi/app/js/ContentCacheMetrics.js index 0078406926..6b7a33de8a 100644 --- a/services/clsi/app/js/ContentCacheMetrics.js +++ b/services/clsi/app/js/ContentCacheMetrics.js @@ -19,6 +19,9 @@ function getSystemLoad() { const ONE_MB = 1024 * 1024 function emitPdfStats(stats, timings) { + if (stats['pdf-caching-timed-out']) { + Metrics.inc('pdf-caching-timed-out') + } if (timings['compute-pdf-caching']) { emitPdfCachingStats(stats, timings) } else { diff --git a/services/clsi/app/js/Errors.js b/services/clsi/app/js/Errors.js index 9b014f8be0..0b16803414 100644 --- a/services/clsi/app/js/Errors.js +++ b/services/clsi/app/js/Errors.js @@ -4,6 +4,8 @@ */ // TODO: This file was created by bulk-decaffeinate. // Fix any style issues and re-enable lint. +const OError = require('@overleaf/o-error') + let Errors var NotFoundError = function (message) { const error = new Error(message) @@ -29,7 +31,10 @@ var AlreadyCompilingError = function (message) { } AlreadyCompilingError.prototype.__proto__ = Error.prototype +class TimedOutError extends OError {} + module.exports = Errors = { + TimedOutError, NotFoundError, FilesOutOfSyncError, AlreadyCompilingError diff --git a/services/clsi/app/js/OutputCacheManager.js b/services/clsi/app/js/OutputCacheManager.js index 4d2e64132b..539b1bc99d 100644 --- a/services/clsi/app/js/OutputCacheManager.js +++ b/services/clsi/app/js/OutputCacheManager.js @@ -26,6 +26,7 @@ const Metrics = require('./Metrics') const OutputFileOptimiser = require('./OutputFileOptimiser') const ContentCacheManager = require('./ContentCacheManager') +const { TimedOutError } = require('./Errors') module.exports = OutputCacheManager = { CONTENT_SUBDIR: 'content', @@ -281,7 +282,16 @@ module.exports = OutputCacheManager = { contentDir, outputFilePath, pdfSize, + timings.compile, function (err, result) { + if (err && err instanceof TimedOutError) { + logger.warn( + { err, outputDir, stats, timings }, + 'pdf caching timed out' + ) + stats['pdf-caching-timed-out'] = 1 + return callback(null, outputFiles) + } if (err) return callback(err, outputFiles) const [contentRanges, newContentRanges, reclaimedSpace] = result diff --git a/services/clsi/app/lib/pdfjs/FSPdfManager.js b/services/clsi/app/lib/pdfjs/FSPdfManager.js index a0450c1e85..8fb1606bf0 100644 --- a/services/clsi/app/lib/pdfjs/FSPdfManager.js +++ b/services/clsi/app/lib/pdfjs/FSPdfManager.js @@ -4,10 +4,10 @@ const { MissingDataException } = require('pdfjs-dist/lib/core/core_utils') const { FSStream } = require('./FSStream') class FSPdfManager extends LocalPdfManager { - constructor(docId, options) { + constructor(docId, { fh, size, checkDeadline }) { const nonEmptyDummyBuffer = Buffer.alloc(1, 0) super(docId, nonEmptyDummyBuffer) - this.stream = new FSStream(options.fh, 0, options.size) + this.stream = new FSStream(fh, 0, size, null, null, checkDeadline) this.pdfDocument = new PDFDocument(this, this.stream) } diff --git a/services/clsi/app/lib/pdfjs/FSStream.js b/services/clsi/app/lib/pdfjs/FSStream.js index 9179e83f0e..748d74362a 100644 --- a/services/clsi/app/lib/pdfjs/FSStream.js +++ b/services/clsi/app/lib/pdfjs/FSStream.js @@ -4,11 +4,12 @@ const { MissingDataException } = require('pdfjs-dist/lib/core/core_utils') const BUF_SIZE = 1024 // read from the file in 1024 byte pages class FSStream extends Stream { - constructor(fh, start, length, dict, cachedBytes) { + constructor(fh, start, length, dict, cachedBytes, checkDeadline) { const nonEmptyDummyBuffer = Buffer.alloc(1, 0) super(nonEmptyDummyBuffer, start, length, dict) delete this.bytes this.fh = fh + this.checkDeadline = checkDeadline this.cachedBytes = cachedBytes || [] } @@ -23,6 +24,7 @@ class FSStream extends Stream { // Manage cached reads from the file requestRange(begin, end) { + this.checkDeadline(`request range ${begin} - ${end}`) // expand small ranges to read a larger amount if (end - begin < BUF_SIZE) { end = begin + BUF_SIZE @@ -123,6 +125,7 @@ class FSStream extends Stream { } makeSubStream(start, length, dict = null) { + this.checkDeadline(`make sub stream start=${start}/length=${length}`) // BG: had to add this check for null length, it is being called with only // the start value at one point in the xref decoding. The intent is clear // enough @@ -131,7 +134,14 @@ class FSStream extends Stream { if (!length) { length = this.end - start } - return new FSStream(this.fh, start, length, dict, this.cachedBytes) + return new FSStream( + this.fh, + start, + length, + dict, + this.cachedBytes, + this.checkDeadline + ) } } diff --git a/services/clsi/app/lib/pdfjs/parseXrefTable.js b/services/clsi/app/lib/pdfjs/parseXrefTable.js index 4db1e0cfc7..de7a386f48 100644 --- a/services/clsi/app/lib/pdfjs/parseXrefTable.js +++ b/services/clsi/app/lib/pdfjs/parseXrefTable.js @@ -1,18 +1,21 @@ const fs = require('fs') const { FSPdfManager } = require('./FSPdfManager') -async function parseXrefTable(path, size) { +async function parseXrefTable(path, size, checkDeadline) { if (size === 0) { return [] } const file = await fs.promises.open(path) try { - const manager = new FSPdfManager(0, { fh: file, size }) + const manager = new FSPdfManager(0, { fh: file, size, checkDeadline }) await manager.ensureDoc('checkHeader') + checkDeadline('pdfjs: after checkHeader') await manager.ensureDoc('parseStartXRef') + checkDeadline('pdfjs: after parseStartXRef') await manager.ensureDoc('parse') + checkDeadline('pdfjs: after parse') return manager.pdfDocument.catalog.xref.entries } finally { file.close() diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index b65a019365..ae6086f1d4 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -68,7 +68,9 @@ module.exports = { enablePdfCaching: process.env.ENABLE_PDF_CACHING === 'true', enablePdfCachingDark: process.env.ENABLE_PDF_CACHING_DARK === 'true', pdfCachingMinChunkSize: - parseInt(process.env.PDF_CACHING_MIN_CHUNK_SIZE, 10) || 1024 + parseInt(process.env.PDF_CACHING_MIN_CHUNK_SIZE, 10) || 1024, + pdfCachingMaxProcessingTime: + parseInt(process.env.PDF_CACHING_MAX_PROCESSING_TIME, 10) || 10 * 1000 } if (process.env.ALLOWED_COMPILE_GROUPS) { diff --git a/services/clsi/test/unit/lib/pdfjsTests.js b/services/clsi/test/unit/lib/pdfjsTests.js index 64f062c0b8..72b36a3355 100644 --- a/services/clsi/test/unit/lib/pdfjsTests.js +++ b/services/clsi/test/unit/lib/pdfjsTests.js @@ -33,7 +33,7 @@ async function loadContext(example) { } async function backFillSnapshot(example, size) { - const table = await parseXrefTable(pdfPath(example), size) + const table = await parseXrefTable(pdfPath(example), size, () => {}) await fs.promises.mkdir(Path.dirname(snapshotPath(example)), { recursive: true }) @@ -53,6 +53,24 @@ describe('pdfjs', function () { }) }) + describe('when the operation times out', function () { + it('should bail out', async function () { + const path = pdfPath(EXAMPLES[0]) + const { size } = await loadContext(EXAMPLES[0]) + const err = new Error() + let table + try { + table = await parseXrefTable(path, size, () => { + throw err + }) + } catch (e) { + expect(e).to.equal(err) + return + } + expect(table).to.not.exist + }) + }) + for (const example of EXAMPLES) { describe(example, function () { let size, snapshot @@ -70,7 +88,7 @@ describe('pdfjs', function () { }) it('should produce the expected xRef table', async function () { - const table = await parseXrefTable(pdfPath(example), size) + const table = await parseXrefTable(pdfPath(example), size, () => {}) expect(table).to.deep.equal(snapshot) }) }) From 743bfced644d24ba0c86c86a448bbb8e7b5d41c2 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Wed, 23 Jun 2021 14:42:39 +0100 Subject: [PATCH 740/754] [misc] ContentCacheManager: apply review feedback - count stages - lower bound is 1s Co-Authored-By: Brian Gough <brian.gough@overleaf.com> --- services/clsi/app/js/ContentCacheManager.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/js/ContentCacheManager.js b/services/clsi/app/js/ContentCacheManager.js index cbd6f3cde0..34e8a8cd73 100644 --- a/services/clsi/app/js/ContentCacheManager.js +++ b/services/clsi/app/js/ContentCacheManager.js @@ -235,22 +235,26 @@ async function writePdfStream(dir, hash, buffer) { function getDeadlineChecker(compileTime) { const maxOverhead = Math.min( - // Adding 10s to a 40s compile time is OK. - compileTime / 4, + // Adding 10s to a 40s compile time is OK. + // Adding 1s to a 3s compile time is OK. + Math.max(compileTime / 4, 1000), // Adding 30s to a 120s compile time is not OK, limit to 10s. Settings.pdfCachingMaxProcessingTime ) const deadline = Date.now() + maxOverhead let lastStage = { stage: 'start', now: Date.now() } + let completedStages = 0 return function (stage) { const now = Date.now() if (now > deadline) { throw new TimedOutError(stage, { + completedStages, lastStage: lastStage.stage, diffToLastStage: now - lastStage.now }) } + completedStages++ lastStage = { stage, now } } } From efa0c54ca51b44dd02bac70ded9bb3b3f009ab85 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Wed, 30 Jun 2021 11:45:34 +0100 Subject: [PATCH 741/754] [misc] add timings for the sync stage and output stage --- services/clsi/app/js/CompileManager.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js index 1d681e8de7..765c5e4d8e 100644 --- a/services/clsi/app/js/CompileManager.js +++ b/services/clsi/app/js/CompileManager.js @@ -114,7 +114,7 @@ module.exports = CompileManager = { }, 'written files to disk' ) - timer.done() + const syncStage = timer.done() const injectDraftModeIfRequired = function (callback) { if (request.draft) { @@ -295,6 +295,8 @@ module.exports = CompileManager = { // Emit compile time. timings.compile = ts + timer = new Metrics.Timer('process-output-files') + return OutputFileFinder.findOutputFiles( resourceList, compileDir, @@ -319,6 +321,10 @@ module.exports = CompileManager = { ) } + const outputStage = timer.done() + timings.sync = syncStage + timings.output = outputStage + // Emit e2e compile time. timings.compileE2E = timerE2E.done() From b62b96cb1cace882ec639e1482b9e47c008e2d6e Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <das7pad@outlook.com> Date: Thu, 1 Jul 2021 11:57:03 +0100 Subject: [PATCH 742/754] Revert "very basic mvp for shedding load" --- services/clsi/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/clsi/app.js b/services/clsi/app.js index b2c261bc2e..8dd8a8cbf3 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -331,7 +331,7 @@ const loadTcpServer = net.createServer(function (socket) { const freeLoad = availableWorkingCpus - currentLoad let freeLoadPercentage = Math.round((freeLoad / availableWorkingCpus) * 100) if (freeLoadPercentage <= 0) { - freeLoadPercentage = 0 // when its 0 the server is set to drain and will move projects to different servers + freeLoadPercentage = 1 // when its 0 the server is set to drain and will move projects to different servers } socket.write(`up, ${freeLoadPercentage}%\n`, 'ASCII') return socket.end() From 516525126bb66ae9fb4a0f1357202ba095f12d3a Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Fri, 2 Jul 2021 09:17:29 +0100 Subject: [PATCH 743/754] [UrlFetcher] do not override domain for clsi-perf requests --- services/clsi/app/js/UrlFetcher.js | 9 +++++--- services/clsi/config/settings.defaults.js | 5 +++++ services/clsi/test/unit/js/UrlFetcherTests.js | 21 ++++++++++++++++++- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/services/clsi/app/js/UrlFetcher.js b/services/clsi/app/js/UrlFetcher.js index b3ed8c465d..adfb55da4c 100644 --- a/services/clsi/app/js/UrlFetcher.js +++ b/services/clsi/app/js/UrlFetcher.js @@ -42,9 +42,12 @@ module.exports = UrlFetcher = { return (_callback = function () {}) } - if (settings.filestoreDomainOveride != null) { - const p = URL.parse(url).path - url = `${settings.filestoreDomainOveride}${p}` + const u = URL.parse(url) + if ( + settings.filestoreDomainOveride && + u.host !== settings.apis.clsiPerf.host + ) { + url = `${settings.filestoreDomainOveride}${u.path}` } var timeoutHandler = setTimeout( function () { diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index ae6086f1d4..b53f871b6d 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -51,6 +51,11 @@ module.exports = { apis: { clsi: { url: `http://${process.env.CLSI_HOST || 'localhost'}:3013` + }, + clsiPerf: { + host: `${process.env.CLSI_PERF_HOST || 'localhost'}:${ + process.env.CLSI_PERF_PORT || '3043' + }` } }, diff --git a/services/clsi/test/unit/js/UrlFetcherTests.js b/services/clsi/test/unit/js/UrlFetcherTests.js index 238e5d8aa9..6a5bc1f3dd 100644 --- a/services/clsi/test/unit/js/UrlFetcherTests.js +++ b/services/clsi/test/unit/js/UrlFetcherTests.js @@ -24,7 +24,13 @@ describe('UrlFetcher', function () { defaults: (this.defaults = sinon.stub().returns((this.request = {}))) }, fs: (this.fs = {}), - 'settings-sharelatex': (this.settings = {}) + 'settings-sharelatex': (this.settings = { + apis: { + clsiPerf: { + host: 'localhost:3043' + } + } + }) } })) }) @@ -94,6 +100,19 @@ describe('UrlFetcher', function () { return this.fileStream.emit('finish') }) + it('should not use override clsiPerf domain when filestoreDomainOveride is set', function (done) { + this.settings.filestoreDomainOveride = '192.11.11.11' + const url = 'http://localhost:3043/file/here?query=string' + this.UrlFetcher.pipeUrlToFile(url, this.path, () => { + this.request.get.args[0][0].url.should.equal(url) + done() + }) + this.res = { statusCode: 200 } + this.urlStream.emit('response', this.res) + this.urlStream.emit('end') + this.fileStream.emit('finish') + }) + return it('should use override domain when filestoreDomainOveride is set', function (done) { this.settings.filestoreDomainOveride = '192.11.11.11' this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { From ec49c4c9a645f6263dc4c213d029cc4768d485da Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Mon, 12 Jul 2021 17:35:51 +0100 Subject: [PATCH 744/754] [misc] install bunyan as production dependency ``` Error: Cannot find module 'bunyan' Require stack: - .../node_modules/@google-cloud/logging-bunyan/build/src/middleware/express.js - .../node_modules/@google-cloud/logging-bunyan/build/src/index.js - .../node_modules/logger-sharelatex/logging-manager.js - .../node_modules/logger-sharelatex/index.js - .../app.js ``` --- services/clsi/package-lock.json | 9 ++++----- services/clsi/package.json | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index f542d9f57c..418dbab158 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -1809,13 +1809,12 @@ "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==" }, "bunyan": { - "version": "1.8.12", - "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz", - "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=", - "dev": true, + "version": "1.8.15", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.15.tgz", + "integrity": "sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==", "requires": { "dtrace-provider": "~0.8", - "moment": "^2.10.6", + "moment": "^2.19.3", "mv": "~2", "safe-json-stringify": "~1" } diff --git a/services/clsi/package.json b/services/clsi/package.json index 467242c023..b29aad82eb 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -23,6 +23,7 @@ "@overleaf/o-error": "^3.3.1", "async": "3.2.0", "body-parser": "^1.19.0", + "bunyan": "^1.8.15", "diskusage": "^1.1.3", "dockerode": "^3.1.0", "express": "^4.17.1", @@ -44,7 +45,6 @@ }, "devDependencies": { "babel-eslint": "^10.1.0", - "bunyan": "^1.8.12", "chai": "~4.2.0", "eslint": "^6.8.0", "eslint-config-prettier": "^6.10.0", From 64551f01989caa9f3226e0cf2918024d1f2c745c Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Mon, 12 Jul 2021 17:47:21 +0100 Subject: [PATCH 745/754] [misc] switch from settings-sharelatex to @overleaf/settings --- services/clsi/app.js | 2 +- services/clsi/app/js/CommandRunner.js | 2 +- services/clsi/app/js/CompileController.js | 2 +- services/clsi/app/js/CompileManager.js | 2 +- services/clsi/app/js/ContentCacheManager.js | 2 +- services/clsi/app/js/ContentController.js | 2 +- services/clsi/app/js/DbQueue.js | 2 +- services/clsi/app/js/DockerRunner.js | 2 +- services/clsi/app/js/LatexRunner.js | 2 +- services/clsi/app/js/LockManager.js | 2 +- services/clsi/app/js/OutputCacheManager.js | 2 +- .../clsi/app/js/ProjectPersistenceManager.js | 2 +- services/clsi/app/js/RequestParser.js | 2 +- services/clsi/app/js/ResourceWriter.js | 2 +- .../clsi/app/js/StaticServerForbidSymlinks.js | 2 +- services/clsi/app/js/UrlCache.js | 2 +- services/clsi/app/js/UrlFetcher.js | 2 +- services/clsi/app/js/db.js | 2 +- services/clsi/package-lock.json | 18 +++++------------- services/clsi/package.json | 2 +- services/clsi/test/acceptance/js/Stats.js | 2 +- .../clsi/test/acceptance/js/helpers/Client.js | 2 +- .../clsi/test/acceptance/js/helpers/ClsiApp.js | 2 +- services/clsi/test/load/js/loadTest.js | 2 +- services/clsi/test/smoke/js/SmokeTests.js | 2 +- .../test/unit/js/CompileControllerTests.js | 2 +- .../clsi/test/unit/js/CompileManagerTests.js | 2 +- .../test/unit/js/ContentCacheManagerTests.js | 2 +- .../test/unit/js/DockerLockManagerTests.js | 2 +- .../clsi/test/unit/js/DockerRunnerTests.js | 2 +- services/clsi/test/unit/js/LatexRunnerTests.js | 2 +- services/clsi/test/unit/js/LockManagerTests.js | 2 +- .../unit/js/ProjectPersistenceManagerTests.js | 2 +- .../clsi/test/unit/js/RequestParserTests.js | 2 +- .../unit/js/StaticServerForbidSymlinksTests.js | 2 +- services/clsi/test/unit/js/UrlCacheTests.js | 2 +- services/clsi/test/unit/js/UrlFetcherTests.js | 2 +- 37 files changed, 41 insertions(+), 49 deletions(-) diff --git a/services/clsi/app.js b/services/clsi/app.js index 8dd8a8cbf3..214d20fbab 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -11,7 +11,7 @@ Metrics.initialize('clsi') const CompileController = require('./app/js/CompileController') const ContentController = require('./app/js/ContentController') -const Settings = require('settings-sharelatex') +const Settings = require('@overleaf/settings') const logger = require('logger-sharelatex') logger.initialize('clsi') if ((Settings.sentry != null ? Settings.sentry.dsn : undefined) != null) { diff --git a/services/clsi/app/js/CommandRunner.js b/services/clsi/app/js/CommandRunner.js index 8e07dacf6d..782707b3a8 100644 --- a/services/clsi/app/js/CommandRunner.js +++ b/services/clsi/app/js/CommandRunner.js @@ -6,7 +6,7 @@ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ let commandRunnerPath -const Settings = require('settings-sharelatex') +const Settings = require('@overleaf/settings') const logger = require('logger-sharelatex') if ((Settings.clsi != null ? Settings.clsi.dockerRunner : undefined) === true) { diff --git a/services/clsi/app/js/CompileController.js b/services/clsi/app/js/CompileController.js index 63a4106aa0..acb6626ae8 100644 --- a/services/clsi/app/js/CompileController.js +++ b/services/clsi/app/js/CompileController.js @@ -15,7 +15,7 @@ let CompileController const RequestParser = require('./RequestParser') const CompileManager = require('./CompileManager') -const Settings = require('settings-sharelatex') +const Settings = require('@overleaf/settings') const Metrics = require('./Metrics') const ProjectPersistenceManager = require('./ProjectPersistenceManager') const logger = require('logger-sharelatex') diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js index 765c5e4d8e..f56bfafa32 100644 --- a/services/clsi/app/js/CompileManager.js +++ b/services/clsi/app/js/CompileManager.js @@ -20,7 +20,7 @@ const ResourceWriter = require('./ResourceWriter') const LatexRunner = require('./LatexRunner') const OutputFileFinder = require('./OutputFileFinder') const OutputCacheManager = require('./OutputCacheManager') -const Settings = require('settings-sharelatex') +const Settings = require('@overleaf/settings') const Path = require('path') const logger = require('logger-sharelatex') const Metrics = require('./Metrics') diff --git a/services/clsi/app/js/ContentCacheManager.js b/services/clsi/app/js/ContentCacheManager.js index 34e8a8cd73..7dfc40f0b7 100644 --- a/services/clsi/app/js/ContentCacheManager.js +++ b/services/clsi/app/js/ContentCacheManager.js @@ -6,7 +6,7 @@ const { callbackify } = require('util') const fs = require('fs') const crypto = require('crypto') const Path = require('path') -const Settings = require('settings-sharelatex') +const Settings = require('@overleaf/settings') const OError = require('@overleaf/o-error') const pLimit = require('p-limit') const { parseXrefTable } = require('../lib/pdfjs/parseXrefTable') diff --git a/services/clsi/app/js/ContentController.js b/services/clsi/app/js/ContentController.js index 76478defe8..b154bea175 100644 --- a/services/clsi/app/js/ContentController.js +++ b/services/clsi/app/js/ContentController.js @@ -1,6 +1,6 @@ const Path = require('path') const send = require('send') -const Settings = require('settings-sharelatex') +const Settings = require('@overleaf/settings') const OutputCacheManager = require('./OutputCacheManager') const ONE_DAY_S = 24 * 60 * 60 diff --git a/services/clsi/app/js/DbQueue.js b/services/clsi/app/js/DbQueue.js index 7589370c98..ca2155d230 100644 --- a/services/clsi/app/js/DbQueue.js +++ b/services/clsi/app/js/DbQueue.js @@ -6,7 +6,7 @@ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ const async = require('async') -const Settings = require('settings-sharelatex') +const Settings = require('@overleaf/settings') const logger = require('logger-sharelatex') const queue = async.queue( (task, cb) => task(cb), diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index 0ff8109585..2d0810d14f 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -1,4 +1,4 @@ -const Settings = require('settings-sharelatex') +const Settings = require('@overleaf/settings') const logger = require('logger-sharelatex') const Docker = require('dockerode') const dockerode = new Docker() diff --git a/services/clsi/app/js/LatexRunner.js b/services/clsi/app/js/LatexRunner.js index 4800021932..7cd67a0396 100644 --- a/services/clsi/app/js/LatexRunner.js +++ b/services/clsi/app/js/LatexRunner.js @@ -15,7 +15,7 @@ */ let LatexRunner const Path = require('path') -const Settings = require('settings-sharelatex') +const Settings = require('@overleaf/settings') const logger = require('logger-sharelatex') const Metrics = require('./Metrics') const CommandRunner = require('./CommandRunner') diff --git a/services/clsi/app/js/LockManager.js b/services/clsi/app/js/LockManager.js index 1246cc9848..a3bdf1bcfc 100644 --- a/services/clsi/app/js/LockManager.js +++ b/services/clsi/app/js/LockManager.js @@ -12,7 +12,7 @@ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ let LockManager -const Settings = require('settings-sharelatex') +const Settings = require('@overleaf/settings') const logger = require('logger-sharelatex') const Lockfile = require('lockfile') // from https://github.com/npm/lockfile const Errors = require('./Errors') diff --git a/services/clsi/app/js/OutputCacheManager.js b/services/clsi/app/js/OutputCacheManager.js index 539b1bc99d..682b85aa1a 100644 --- a/services/clsi/app/js/OutputCacheManager.js +++ b/services/clsi/app/js/OutputCacheManager.js @@ -20,7 +20,7 @@ const fse = require('fs-extra') const Path = require('path') const logger = require('logger-sharelatex') const _ = require('lodash') -const Settings = require('settings-sharelatex') +const Settings = require('@overleaf/settings') const crypto = require('crypto') const Metrics = require('./Metrics') diff --git a/services/clsi/app/js/ProjectPersistenceManager.js b/services/clsi/app/js/ProjectPersistenceManager.js index 8145cccd84..814f7e9916 100644 --- a/services/clsi/app/js/ProjectPersistenceManager.js +++ b/services/clsi/app/js/ProjectPersistenceManager.js @@ -20,7 +20,7 @@ const dbQueue = require('./DbQueue') const async = require('async') const logger = require('logger-sharelatex') const oneDay = 24 * 60 * 60 * 1000 -const Settings = require('settings-sharelatex') +const Settings = require('@overleaf/settings') const diskusage = require('diskusage') const { callbackify } = require('util') diff --git a/services/clsi/app/js/RequestParser.js b/services/clsi/app/js/RequestParser.js index 66c917aeed..702cf5593c 100644 --- a/services/clsi/app/js/RequestParser.js +++ b/services/clsi/app/js/RequestParser.js @@ -18,7 +18,7 @@ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ let RequestParser -const settings = require('settings-sharelatex') +const settings = require('@overleaf/settings') module.exports = RequestParser = { VALID_COMPILERS: ['pdflatex', 'latex', 'xelatex', 'lualatex'], diff --git a/services/clsi/app/js/ResourceWriter.js b/services/clsi/app/js/ResourceWriter.js index d8cc2a4cab..25f1b979de 100644 --- a/services/clsi/app/js/ResourceWriter.js +++ b/services/clsi/app/js/ResourceWriter.js @@ -23,7 +23,7 @@ const OutputFileFinder = require('./OutputFileFinder') const ResourceStateManager = require('./ResourceStateManager') const Metrics = require('./Metrics') const logger = require('logger-sharelatex') -const settings = require('settings-sharelatex') +const settings = require('@overleaf/settings') const parallelFileDownloads = settings.parallelFileDownloads || 1 diff --git a/services/clsi/app/js/StaticServerForbidSymlinks.js b/services/clsi/app/js/StaticServerForbidSymlinks.js index edde77742c..bcfc5015b3 100644 --- a/services/clsi/app/js/StaticServerForbidSymlinks.js +++ b/services/clsi/app/js/StaticServerForbidSymlinks.js @@ -17,7 +17,7 @@ let ForbidSymlinks const Path = require('path') const fs = require('fs') -const Settings = require('settings-sharelatex') +const Settings = require('@overleaf/settings') const logger = require('logger-sharelatex') const url = require('url') diff --git a/services/clsi/app/js/UrlCache.js b/services/clsi/app/js/UrlCache.js index a8b2b19bea..b6378a5504 100644 --- a/services/clsi/app/js/UrlCache.js +++ b/services/clsi/app/js/UrlCache.js @@ -16,7 +16,7 @@ let UrlCache const db = require('./db') const dbQueue = require('./DbQueue') const UrlFetcher = require('./UrlFetcher') -const Settings = require('settings-sharelatex') +const Settings = require('@overleaf/settings') const crypto = require('crypto') const fs = require('fs') const logger = require('logger-sharelatex') diff --git a/services/clsi/app/js/UrlFetcher.js b/services/clsi/app/js/UrlFetcher.js index adfb55da4c..28155b94be 100644 --- a/services/clsi/app/js/UrlFetcher.js +++ b/services/clsi/app/js/UrlFetcher.js @@ -16,7 +16,7 @@ let UrlFetcher const request = require('request').defaults({ jar: false }) const fs = require('fs') const logger = require('logger-sharelatex') -const settings = require('settings-sharelatex') +const settings = require('@overleaf/settings') const URL = require('url') const async = require('async') diff --git a/services/clsi/app/js/db.js b/services/clsi/app/js/db.js index 6b7e50ec9c..135f3a52d4 100644 --- a/services/clsi/app/js/db.js +++ b/services/clsi/app/js/db.js @@ -9,7 +9,7 @@ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ const Sequelize = require('sequelize') -const Settings = require('settings-sharelatex') +const Settings = require('@overleaf/settings') const _ = require('lodash') const logger = require('logger-sharelatex') diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index 418dbab158..77bb2cb4ce 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -1022,6 +1022,11 @@ } } }, + "@overleaf/settings": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@overleaf/settings/-/settings-2.1.1.tgz", + "integrity": "sha512-vcJwqCGFKmQxTP/syUqCeMaSRjHmBcQgKOACR9He2uJcErg2GZPa1go+nGvszMbkElM4HfRKm/MfxvqHhoN4TQ==" + }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -2065,11 +2070,6 @@ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, - "coffee-script": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", - "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=" - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -7047,14 +7047,6 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, - "settings-sharelatex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/settings-sharelatex/-/settings-sharelatex-1.1.0.tgz", - "integrity": "sha512-f7D+0lnlohoteSn6IKTH72NE+JnAdMWTKwQglAuimZWTID2FRRItZSGeYMTRpvEnaQApkoVwRp//WRMsiddnqw==", - "requires": { - "coffee-script": "1.6.0" - } - }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", diff --git a/services/clsi/package.json b/services/clsi/package.json index b29aad82eb..8a065d5a5e 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -21,6 +21,7 @@ "dependencies": { "@overleaf/metrics": "^3.5.1", "@overleaf/o-error": "^3.3.1", + "@overleaf/settings": "^2.1.1", "async": "3.2.0", "body-parser": "^1.19.0", "bunyan": "^1.8.15", @@ -38,7 +39,6 @@ "request": "^2.88.2", "send": "^0.17.1", "sequelize": "^5.21.5", - "settings-sharelatex": "^1.1.0", "sqlite3": "^4.1.1", "v8-profiler-node8": "^6.1.1", "wrench": "~1.5.9" diff --git a/services/clsi/test/acceptance/js/Stats.js b/services/clsi/test/acceptance/js/Stats.js index 87b20b1cc9..d96a8fcbce 100644 --- a/services/clsi/test/acceptance/js/Stats.js +++ b/services/clsi/test/acceptance/js/Stats.js @@ -1,5 +1,5 @@ const request = require('request') -const Settings = require('settings-sharelatex') +const Settings = require('@overleaf/settings') after(function (done) { request( { diff --git a/services/clsi/test/acceptance/js/helpers/Client.js b/services/clsi/test/acceptance/js/helpers/Client.js index c5814ff3ef..1da6601efc 100644 --- a/services/clsi/test/acceptance/js/helpers/Client.js +++ b/services/clsi/test/acceptance/js/helpers/Client.js @@ -15,7 +15,7 @@ let Client const request = require('request') const fs = require('fs') -const Settings = require('settings-sharelatex') +const Settings = require('@overleaf/settings') const host = 'localhost' diff --git a/services/clsi/test/acceptance/js/helpers/ClsiApp.js b/services/clsi/test/acceptance/js/helpers/ClsiApp.js index 160b07e51e..343c3c7d95 100644 --- a/services/clsi/test/acceptance/js/helpers/ClsiApp.js +++ b/services/clsi/test/acceptance/js/helpers/ClsiApp.js @@ -15,7 +15,7 @@ const app = require('../../../../app') require('logger-sharelatex').logger.level('info') const logger = require('logger-sharelatex') -const Settings = require('settings-sharelatex') +const Settings = require('@overleaf/settings') module.exports = { running: false, diff --git a/services/clsi/test/load/js/loadTest.js b/services/clsi/test/load/js/loadTest.js index 65090b8b5f..7e2ae5fa79 100644 --- a/services/clsi/test/load/js/loadTest.js +++ b/services/clsi/test/load/js/loadTest.js @@ -10,7 +10,7 @@ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ const request = require('request') -const Settings = require('settings-sharelatex') +const Settings = require('@overleaf/settings') const async = require('async') const fs = require('fs') const _ = require('lodash') diff --git a/services/clsi/test/smoke/js/SmokeTests.js b/services/clsi/test/smoke/js/SmokeTests.js index 8cfb190b30..8dbdc0a348 100644 --- a/services/clsi/test/smoke/js/SmokeTests.js +++ b/services/clsi/test/smoke/js/SmokeTests.js @@ -1,5 +1,5 @@ const request = require('request') -const Settings = require('settings-sharelatex') +const Settings = require('@overleaf/settings') const buildUrl = (path) => `http://${Settings.internal.clsi.host}:${Settings.internal.clsi.port}/${path}` diff --git a/services/clsi/test/unit/js/CompileControllerTests.js b/services/clsi/test/unit/js/CompileControllerTests.js index 3b29c867c4..e8739379da 100644 --- a/services/clsi/test/unit/js/CompileControllerTests.js +++ b/services/clsi/test/unit/js/CompileControllerTests.js @@ -66,7 +66,7 @@ describe('CompileController', function () { requires: { './CompileManager': (this.CompileManager = {}), './RequestParser': (this.RequestParser = {}), - 'settings-sharelatex': (this.Settings = { + '@overleaf/settings': (this.Settings = { apis: { clsi: { url: 'http://clsi.example.com' diff --git a/services/clsi/test/unit/js/CompileManagerTests.js b/services/clsi/test/unit/js/CompileManagerTests.js index 97e318a4a6..7f4046c2ee 100644 --- a/services/clsi/test/unit/js/CompileManagerTests.js +++ b/services/clsi/test/unit/js/CompileManagerTests.js @@ -31,7 +31,7 @@ describe('CompileManager', function () { './ResourceWriter': (this.ResourceWriter = {}), './OutputFileFinder': (this.OutputFileFinder = {}), './OutputCacheManager': (this.OutputCacheManager = {}), - 'settings-sharelatex': (this.Settings = { + '@overleaf/settings': (this.Settings = { path: { compilesDir: '/compiles/dir', outputDir: '/output/dir' diff --git a/services/clsi/test/unit/js/ContentCacheManagerTests.js b/services/clsi/test/unit/js/ContentCacheManagerTests.js index 76c517d8ce..6e8490babd 100644 --- a/services/clsi/test/unit/js/ContentCacheManagerTests.js +++ b/services/clsi/test/unit/js/ContentCacheManagerTests.js @@ -8,7 +8,7 @@ describe('ContentCacheManager', function () { let contentDir, pdfPath let ContentCacheManager, files, Settings before(function () { - Settings = require('settings-sharelatex') + Settings = require('@overleaf/settings') ContentCacheManager = require(MODULE_PATH) }) let contentRanges, newContentRanges, reclaimed diff --git a/services/clsi/test/unit/js/DockerLockManagerTests.js b/services/clsi/test/unit/js/DockerLockManagerTests.js index 6adfa1231a..f06f1afa0e 100644 --- a/services/clsi/test/unit/js/DockerLockManagerTests.js +++ b/services/clsi/test/unit/js/DockerLockManagerTests.js @@ -20,7 +20,7 @@ describe('LockManager', function () { beforeEach(function () { return (this.LockManager = SandboxedModule.require(modulePath, { requires: { - 'settings-sharelatex': (this.Settings = { clsi: { docker: {} } }) + '@overleaf/settings': (this.Settings = { clsi: { docker: {} } }) } })) }) diff --git a/services/clsi/test/unit/js/DockerRunnerTests.js b/services/clsi/test/unit/js/DockerRunnerTests.js index 67290e440d..b47ed609a9 100644 --- a/services/clsi/test/unit/js/DockerRunnerTests.js +++ b/services/clsi/test/unit/js/DockerRunnerTests.js @@ -28,7 +28,7 @@ describe('DockerRunner', function () { this.container = container = {} this.DockerRunner = SandboxedModule.require(modulePath, { requires: { - 'settings-sharelatex': (this.Settings = { + '@overleaf/settings': (this.Settings = { clsi: { docker: {} }, path: {} }), diff --git a/services/clsi/test/unit/js/LatexRunnerTests.js b/services/clsi/test/unit/js/LatexRunnerTests.js index f763f39cb0..13858ff42a 100644 --- a/services/clsi/test/unit/js/LatexRunnerTests.js +++ b/services/clsi/test/unit/js/LatexRunnerTests.js @@ -23,7 +23,7 @@ describe('LatexRunner', function () { let Timer this.LatexRunner = SandboxedModule.require(modulePath, { requires: { - 'settings-sharelatex': (this.Settings = { + '@overleaf/settings': (this.Settings = { docker: { socketPath: '/var/run/docker.sock' } diff --git a/services/clsi/test/unit/js/LockManagerTests.js b/services/clsi/test/unit/js/LockManagerTests.js index 58580583ca..4f9cc31f21 100644 --- a/services/clsi/test/unit/js/LockManagerTests.js +++ b/services/clsi/test/unit/js/LockManagerTests.js @@ -22,7 +22,7 @@ describe('DockerLockManager', function () { beforeEach(function () { this.LockManager = SandboxedModule.require(modulePath, { requires: { - 'settings-sharelatex': {}, + '@overleaf/settings': {}, fs: { lstat: sinon.stub().callsArgWith(1), readdir: sinon.stub().callsArgWith(1) diff --git a/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js b/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js index e60de54ff3..be8050aa47 100644 --- a/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js +++ b/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js @@ -27,7 +27,7 @@ describe('ProjectPersistenceManager', function () { './UrlCache': (this.UrlCache = {}), './CompileManager': (this.CompileManager = {}), diskusage: (this.diskusage = { check: sinon.stub() }), - 'settings-sharelatex': (this.settings = { + '@overleaf/settings': (this.settings = { project_cache_length_ms: 1000, path: { compilesDir: '/compiles', diff --git a/services/clsi/test/unit/js/RequestParserTests.js b/services/clsi/test/unit/js/RequestParserTests.js index ff2ef7a9d1..840e55ddb6 100644 --- a/services/clsi/test/unit/js/RequestParserTests.js +++ b/services/clsi/test/unit/js/RequestParserTests.js @@ -40,7 +40,7 @@ describe('RequestParser', function () { } return (this.RequestParser = SandboxedModule.require(modulePath, { requires: { - 'settings-sharelatex': (this.settings = {}) + '@overleaf/settings': (this.settings = {}) } })) }) diff --git a/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js b/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js index 65a66f3dc3..86e279e687 100644 --- a/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js +++ b/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js @@ -30,7 +30,7 @@ describe('StaticServerForbidSymlinks', function () { this.fs = {} this.ForbidSymlinks = SandboxedModule.require(modulePath, { requires: { - 'settings-sharelatex': this.settings, + '@overleaf/settings': this.settings, fs: this.fs } }) diff --git a/services/clsi/test/unit/js/UrlCacheTests.js b/services/clsi/test/unit/js/UrlCacheTests.js index 40652c5899..32e175f7ca 100644 --- a/services/clsi/test/unit/js/UrlCacheTests.js +++ b/services/clsi/test/unit/js/UrlCacheTests.js @@ -24,7 +24,7 @@ describe('UrlCache', function () { requires: { './db': {}, './UrlFetcher': (this.UrlFetcher = {}), - 'settings-sharelatex': (this.Settings = { + '@overleaf/settings': (this.Settings = { path: { clsiCacheDir: '/cache/dir' } }), fs: (this.fs = { copyFile: sinon.stub().yields() }) diff --git a/services/clsi/test/unit/js/UrlFetcherTests.js b/services/clsi/test/unit/js/UrlFetcherTests.js index 6a5bc1f3dd..ac94540f70 100644 --- a/services/clsi/test/unit/js/UrlFetcherTests.js +++ b/services/clsi/test/unit/js/UrlFetcherTests.js @@ -24,7 +24,7 @@ describe('UrlFetcher', function () { defaults: (this.defaults = sinon.stub().returns((this.request = {}))) }, fs: (this.fs = {}), - 'settings-sharelatex': (this.settings = { + '@overleaf/settings': (this.settings = { apis: { clsiPerf: { host: 'localhost:3043' From 4b066f615f2a7384bce1aacb62b9dd4cbd6d7377 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Mon, 12 Jul 2021 17:51:07 +0100 Subject: [PATCH 746/754] [misc] run npm dedupe --- services/clsi/package-lock.json | 431 +++----------------------------- 1 file changed, 37 insertions(+), 394 deletions(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index 77bb2cb4ce..122b9e6ad7 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -193,25 +193,6 @@ "jws": "^4.0.0", "lru-cache": "^5.0.0" } - }, - "jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "requires": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } } } }, @@ -269,11 +250,6 @@ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==" }, - "coffeescript": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.5.1.tgz", - "integrity": "sha512-J2jRPX0eeFh5VKyVnoLrfVFgLZtnnmp96WQSLAS8OrLm2wtQLcnikYKe1gViJKDH7vucjuhHvBKKBP3rKcD1tQ==" - }, "duplexify": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", @@ -361,14 +337,6 @@ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "requires": { - "yocto-queue": "^0.1.0" - } - }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -453,25 +421,6 @@ "lru-cache": "^5.0.0" } }, - "jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "requires": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, "type-fest": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", @@ -1094,14 +1043,6 @@ "dev": true, "requires": { "type-detect": "4.0.8" - }, - "dependencies": { - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - } } }, "@sinonjs/fake-timers": { @@ -1132,14 +1073,6 @@ "@sinonjs/commons": "^1.6.0", "lodash.get": "^4.4.2", "type-detect": "^4.0.8" - }, - "dependencies": { - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - } } }, "@sinonjs/text-encoding": { @@ -1295,6 +1228,11 @@ "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", "dev": true }, + "agent-base": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", + "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==" + }, "ajv": { "version": "6.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", @@ -1438,14 +1376,6 @@ "requires": { "ast-types-flow": "0.0.7", "commander": "^2.11.0" - }, - "dependencies": { - "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 - } } }, "array-flatten": { @@ -1914,12 +1844,6 @@ "supports-color": "^5.3.0" }, "dependencies": { - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -2070,6 +1994,11 @@ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, + "coffeescript": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.5.1.tgz", + "integrity": "sha512-J2jRPX0eeFh5VKyVnoLrfVFgLZtnnmp96WQSLAS8OrLm2wtQLcnikYKe1gViJKDH7vucjuhHvBKKBP3rKcD1tQ==" + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -2093,6 +2022,12 @@ "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", @@ -2366,6 +2301,12 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, "diskusage": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/diskusage/-/diskusage-1.1.3.tgz", @@ -2456,14 +2397,6 @@ "optional": true, "requires": { "nan": "^2.14.0" - }, - "dependencies": { - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "optional": true - } } }, "duplexer3": { @@ -2643,18 +2576,6 @@ "v8-compile-cache": "^2.0.3" }, "dependencies": { - "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "ansi-regex": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", @@ -2670,12 +2591,6 @@ "ms": "^2.1.1" } }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", - "dev": true - }, "globals": { "version": "12.4.0", "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", @@ -2685,21 +2600,6 @@ "type-fest": "^0.8.1" } }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -3573,25 +3473,6 @@ "bignumber.js": "^9.0.0" } }, - "jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "requires": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -3655,25 +3536,6 @@ "lru-cache": "^5.0.0" } }, - "jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "requires": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, "protobufjs": { "version": "6.10.1", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.1.tgz", @@ -3740,6 +3602,12 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, "gtoken": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.1.4.tgz", @@ -3751,25 +3619,6 @@ "mime": "^2.2.0" }, "dependencies": { - "jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "requires": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, "mime": { "version": "2.4.6", "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", @@ -3844,13 +3693,6 @@ "integrity": "sha512-n8aSFscI9r3gfhOcAECAtXFaQ1uy4QSke6bnaL+iymYZ/dWs9cqDqHM+rALfsHUwukUbxsdlECZ0pKmJdQ/4OA==", "requires": { "nan": "^2.13.2" - }, - "dependencies": { - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" - } } }, "hex2dec": { @@ -3892,14 +3734,6 @@ "debug": "4" }, "dependencies": { - "agent-base": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", - "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", - "requires": { - "debug": "4" - } - }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -3934,14 +3768,6 @@ "debug": "4" }, "dependencies": { - "agent-base": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", - "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", - "requires": { - "debug": "4" - } - }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -4116,12 +3942,6 @@ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, "run-async": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", @@ -4510,12 +4330,6 @@ "strip-bom": "^3.0.0" }, "dependencies": { - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -4613,17 +4427,6 @@ "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" - } - }, "yn": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yn/-/yn-4.0.0.tgz", @@ -4906,12 +4709,6 @@ "ms": "^2.1.1" } }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -4921,12 +4718,6 @@ "locate-path": "^3.0.0" } }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -4937,15 +4728,6 @@ "path-exists": "^3.0.0" } }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", @@ -5027,15 +4809,6 @@ "path-is-absolute": "^1.0.0" } }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, "rimraf": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", @@ -5713,14 +5486,6 @@ "tar": "^4.4.2" } }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "requires": { - "yocto-queue": "^0.1.0" - } - }, "protobufjs": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz", @@ -5787,18 +5552,6 @@ "vue-eslint-parser": "^2.0.2" }, "dependencies": { - "ajv": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz", - "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "ansi-escapes": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", @@ -5894,12 +5647,6 @@ "eslint-visitor-keys": "^1.0.0" } }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", - "dev": true - }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", @@ -5965,15 +5712,6 @@ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -6057,18 +5795,6 @@ "yargs": "^13.2.4" }, "dependencies": { - "ajv": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz", - "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "ansi-escapes": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", @@ -6172,12 +5898,6 @@ "eslint-visitor-keys": "^1.0.0" } }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", - "dev": true - }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", @@ -6288,15 +6008,6 @@ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -6742,15 +6453,6 @@ "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } } } }, @@ -7388,18 +7090,6 @@ "string-width": "^3.0.0" }, "dependencies": { - "ajv": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz", - "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "ansi-regex": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", @@ -7412,24 +7102,12 @@ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", - "dev": true - }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -7464,16 +7142,6 @@ "mkdirp": "^0.5.0", "safe-buffer": "^5.1.2", "yallist": "^3.0.2" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - } } }, "tar-fs": { @@ -7485,16 +7153,6 @@ "mkdirp": "^0.5.1", "pump": "^3.0.0", "tar-stream": "^2.0.0" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - } } }, "tar-stream": { @@ -7665,6 +7323,15 @@ } } }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", @@ -7886,19 +7553,6 @@ "node-pre-gyp": "^0.13.0" }, "dependencies": { - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - }, - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" - }, "node-pre-gyp": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz", @@ -8175,17 +7829,6 @@ "dev": true, "requires": { "mkdirp": "^0.5.1" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - } } }, "write-file-atomic": { From 72a19ad8952b9f3e412d333c9cc6302ddaa84975 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 13 Jul 2021 10:07:04 +0100 Subject: [PATCH 747/754] [misc] goodbye coffee-script --- services/clsi/package-lock.json | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index 122b9e6ad7..06e971de8f 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -1231,7 +1231,25 @@ "agent-base": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", - "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==" + "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } }, "ajv": { "version": "6.12.0", From 631873fc8357b56e65053f239220802fee27cdcc Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 13 Jul 2021 11:55:19 +0100 Subject: [PATCH 748/754] [misc] upgrade build scripts to version 3.11.0 and cleanup packages ``` npm uninstall prettier-eslint-cli eslint-plugin-standard eslint-plugin-jsx-a11y eslint-plugin-react eslint-config-standard-jsx eslint-config-standard-react babel-eslint npm dedupe ``` --- services/clsi/.eslintrc | 2 +- services/clsi/.github/dependabot.yml | 2 +- services/clsi/.prettierrc | 6 +- services/clsi/buildscript.txt | 2 +- services/clsi/package-lock.json | 3518 +++++++++----------------- services/clsi/package.json | 37 +- 6 files changed, 1161 insertions(+), 2406 deletions(-) diff --git a/services/clsi/.eslintrc b/services/clsi/.eslintrc index 321353f971..1c14f50efe 100644 --- a/services/clsi/.eslintrc +++ b/services/clsi/.eslintrc @@ -3,9 +3,9 @@ // https://github.com/sharelatex/sharelatex-dev-environment { "extends": [ + "eslint:recommended", "standard", "prettier", - "prettier/standard" ], "parserOptions": { "ecmaVersion": 2018 diff --git a/services/clsi/.github/dependabot.yml b/services/clsi/.github/dependabot.yml index e2c64a3351..c856753655 100644 --- a/services/clsi/.github/dependabot.yml +++ b/services/clsi/.github/dependabot.yml @@ -20,4 +20,4 @@ updates: # future if we reorganise teams labels: - "dependencies" - - "Team-Magma" + - "type:maintenance" diff --git a/services/clsi/.prettierrc b/services/clsi/.prettierrc index 24f9ec526f..c92c3526e7 100644 --- a/services/clsi/.prettierrc +++ b/services/clsi/.prettierrc @@ -2,6 +2,10 @@ # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment { + "arrowParens": "avoid", "semi": false, - "singleQuote": true + "singleQuote": true, + "trailingComma": "es5", + "tabWidth": 2, + "useTabs": false } diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index 84a43ecb83..e04c135353 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -6,4 +6,4 @@ clsi --env-pass-through=TEXLIVE_IMAGE --node-version=12.21.0 --public-repo=True ---script-version=3.8.0 +--script-version=3.11.0 diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json index 06e971de8f..86789cafbc 100644 --- a/services/clsi/package-lock.json +++ b/services/clsi/package-lock.json @@ -5,142 +5,80 @@ "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "dev": true, "requires": { - "@babel/highlight": "^7.8.3" + "@babel/highlight": "^7.10.4" } }, - "@babel/generator": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.7.tgz", - "integrity": "sha512-DQwjiKJqH4C3qGiyQCAExJHoZssn49JTMJgZ8SANGgVFdkupcUhLOdkAeoC6kmHZCPfoDG5M0b6cFlSN5wW7Ew==", - "dev": true, - "requires": { - "@babel/types": "^7.8.7", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.7.tgz", - "integrity": "sha512-9JWls8WilDXFGxs0phaXAZgpxTZhSk/yOYH2hTHC0X1yC7Z78IJfvR1vJ+rmJKq3I35td2XzXzN6ZLYlna+r/A==", + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", "dev": true }, - "@babel/runtime": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", - "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, "requires": { - "regenerator-runtime": "^0.13.2" - } - }, - "@babel/runtime-corejs3": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.8.7.tgz", - "integrity": "sha512-sc7A+H4I8kTd7S61dgB9RomXu/C+F4IrRr4Ytze4dnfx7AXEpCrejSNpjx7vq6y/Bak9S6Kbk65a/WgMLtg43Q==", - "dev": true, - "requires": { - "core-js-pure": "^3.0.0", - "regenerator-runtime": "^0.13.4" + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" }, "dependencies": { - "regenerator-runtime": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz", - "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==", - "dev": true + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } } } }, - "@babel/template": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", - "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", + "@eslint/eslintrc": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz", + "integrity": "sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg==", "dev": true, "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.6", - "@babel/types": "^7.8.6" - } - }, - "@babel/traverse": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.6.tgz", - "integrity": "sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.6", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.6", - "@babel/types": "^7.8.6", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" }, "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { - "ms": "^2.1.1" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" } }, "ms": { @@ -148,20 +86,20 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" } } }, - "@babel/types": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.7.tgz", - "integrity": "sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, "@google-cloud/common": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-2.4.0.tgz", @@ -240,11 +178,6 @@ "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.3.tgz", "integrity": "sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw==" }, - "acorn": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.0.5.tgz", - "integrity": "sha512-v+DieK/HJkJOpFBETDJioequtc3PfxsWMaxIdIwujtF7FEV/MAyDQLlm6/zPvr7Mix07mLh6ccVwIsloceodlg==" - }, "bignumber.js": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", @@ -913,6 +846,40 @@ "protobufjs": "^6.8.6" } }, + "@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", + "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "dev": true + }, "@opencensus/core": { "version": "0.0.20", "resolved": "https://registry.npmjs.org/@opencensus/core/-/core-0.0.20.tgz", @@ -962,13 +929,6 @@ "integrity": "sha512-1FRBYZO0lbJ0U+FRGZVS8ou6RhEw3e2B86WW/NbtBw554g0h5iC8ESf+juIfPMU/WDf/JDIFbg3eB/LnP2RSow==", "requires": { "core-js": "^3.8.3" - }, - "dependencies": { - "core-js": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.13.0.tgz", - "integrity": "sha512-iWDbiyha1M5vFwPFmQnvRv+tJzGbFAm6XimJUT0NgHYW3xZEs1SkCAcasWSVFxpI2Xb/V1DDJckq3v90+bQnog==" - } } }, "@overleaf/settings": { @@ -1095,23 +1055,11 @@ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, "@types/console-log-level": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@types/console-log-level/-/console-log-level-1.4.0.tgz", "integrity": "sha512-x+OscEQwcx5Biair4enH7ov9W+clcqUWaZRaxn5IkT4yNWWjRr2oiYDkY/x1uXSTVZOQ2xlbFQySaQGB+VdXGQ==" }, - "@types/eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", - "dev": true - }, "@types/fs-extra": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.1.tgz", @@ -1120,12 +1068,6 @@ "@types/node": "*" } }, - "@types/json-schema": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", - "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==", - "dev": true - }, "@types/long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz", @@ -1141,58 +1083,11 @@ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.4.tgz", "integrity": "sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ==" }, - "@typescript-eslint/experimental-utils": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz", - "integrity": "sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/typescript-estree": "1.13.0", - "eslint-scope": "^4.0.0" - }, - "dependencies": { - "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - } - } - }, - "@typescript-eslint/parser": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.13.0.tgz", - "integrity": "sha512-ITMBs52PCPgLb2nGPoeT4iU3HdQZHcPaZVw+7CsFagRJHUhyeTgorEwHXhFf3e7Evzi8oujKNpHc8TONth8AdQ==", - "dev": true, - "requires": { - "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "1.13.0", - "@typescript-eslint/typescript-estree": "1.13.0", - "eslint-visitor-keys": "^1.0.0" - } - }, - "@typescript-eslint/typescript-estree": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz", - "integrity": "sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==", - "dev": true, - "requires": { - "lodash.unescape": "4.0.1", - "semver": "5.5.0" - }, - "dependencies": { - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true - } - } + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true }, "abbrev": { "version": "1.1.1", @@ -1217,15 +1112,14 @@ } }, "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", - "dev": true + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.0.5.tgz", + "integrity": "sha512-v+DieK/HJkJOpFBETDJioequtc3PfxsWMaxIdIwujtF7FEV/MAyDQLlm6/zPvr7Mix07mLh6ccVwIsloceodlg==" }, "acorn-jsx": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", - "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true }, "agent-base": { @@ -1312,28 +1206,11 @@ } }, "ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, - "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", - "dev": true, - "requires": { - "type-fest": "^0.11.0" - }, - "dependencies": { - "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", - "dev": true - } - } - }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -1386,40 +1263,50 @@ "sprintf-js": "~1.0.2" } }, - "aria-query": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", - "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", - "dev": true, - "requires": { - "ast-types-flow": "0.0.7", - "commander": "^2.11.0" - } - }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "array-includes": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", - "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz", + "integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==", "dev": true, "requires": { + "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.17.0", + "es-abstract": "^1.18.0-next.2", + "get-intrinsic": "^1.1.1", "is-string": "^1.0.5" } }, "array.prototype.flat": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", - "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz", + "integrity": "sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==", "dev": true, "requires": { + "call-bind": "^1.0.0", "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" + "es-abstract": "^1.18.0-next.1" + }, + "dependencies": { + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + }, + "is-callable": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==" + }, + "is-string": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", + "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==" + } } }, "arrify": { @@ -1446,16 +1333,10 @@ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, - "ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", - "dev": true - }, "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, "async": { @@ -1487,26 +1368,6 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" }, - "axobject-query": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.1.2.tgz", - "integrity": "sha512-ICt34ZmrVt8UQnvPl6TVyDTkmhXmAyAT4Jh5ugfGUX4MOrZ+U/ZY6/sdylRw3qGNr9Ub5AJsaHeDMzNLehRdOQ==", - "dev": true - }, - "babel-eslint": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", - "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.0", - "@babel/traverse": "^7.7.0", - "@babel/types": "^7.7.0", - "eslint-visitor-keys": "^1.0.0", - "resolve": "^1.12.0" - } - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -1608,12 +1469,6 @@ } } }, - "boolify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/boolify/-/boolify-1.0.1.tgz", - "integrity": "sha1-tcCeF8rNET0Rt7s+04TMASmU2Gs=", - "dev": true - }, "boxen": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", @@ -1640,26 +1495,14 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "requires": { "color-convert": "^2.0.1" } }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -1667,14 +1510,12 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -1706,7 +1547,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -1809,6 +1649,16 @@ } } }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1821,17 +1671,6 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, - "camelcase-keys": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.1.2.tgz", - "integrity": "sha512-QfFrU0CIw2oltVvpndW32kuJ/9YOJwUnmWrjlXt1nnJZHCaS9i6bfOpg9R4Lw8aZjStkJWM+jc0cdXjWBgVJSw==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - } - }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -1851,32 +1690,19 @@ "type-detect": "^4.0.5" } }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "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": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "check-error": "^1.0.2" } }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true }, "charenc": { @@ -1923,70 +1749,13 @@ "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", "dev": true }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "wrap-ansi": "^7.0.0" } }, "clone-response": { @@ -2040,18 +1809,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", - "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==", - "dev": true - }, "compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -2133,12 +1890,6 @@ "resolved": "https://registry.npmjs.org/console-log-level/-/console-log-level-1.4.1.tgz", "integrity": "sha512-VZzbIORbP+PPcN/gg3DXClTLPLg5Slwd5fL2MIc+o1qZ4BXBvWyc6QxPk6T/Mkr6IVjRpoAGf32XxP3ZWMVRcQ==" }, - "contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", - "dev": true - }, "content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", @@ -2172,16 +1923,9 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "core-js": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", - "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==", - "dev": true - }, - "core-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==", - "dev": true + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.13.0.tgz", + "integrity": "sha512-iWDbiyha1M5vFwPFmQnvRv+tJzGbFAm6XimJUT0NgHYW3xZEs1SkCAcasWSVFxpI2Xb/V1DDJckq3v90+bQnog==" }, "core-util-is": { "version": "1.0.2", @@ -2189,16 +1933,14 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, "crypt": { @@ -2217,12 +1959,6 @@ "resolved": "https://registry.npmjs.org/d64/-/d64-1.0.0.tgz", "integrity": "sha1-QAKofoUMv8n52XBrYPymE6MzbpA=" }, - "damerau-levenshtein": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", - "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", - "dev": true - }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -2240,9 +1976,9 @@ } }, "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true }, "decompress-response": { @@ -2320,9 +2056,9 @@ "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, "diskusage": { @@ -2334,12 +2070,6 @@ "nan": "^2.14.0" } }, - "dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true - }, "docker-modem": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-2.1.1.tgz", @@ -2467,8 +2197,7 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "encodeurl": { "version": "1.0.2", @@ -2483,6 +2212,15 @@ "once": "^1.4.0" } }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, "ent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", @@ -2498,22 +2236,24 @@ } }, "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.18.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", + "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", "dev": true, "requires": { + "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.3", + "object-inspect": "^1.10.3", "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" } }, "es-to-primitive": { @@ -2532,6 +2272,12 @@ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, "escape-goat": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", @@ -2550,72 +2296,158 @@ "dev": true }, "eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "version": "7.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.30.0.tgz", + "integrity": "sha512-VLqz80i3as3NdloY44BQSJpFw534L9Oh+6zJOUaViV4JPd+DaHwutqP7tcpkW3YiXbK6s05RZl7yl7cQn+lijg==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.2", + "@humanwhocodes/config-array": "^0.5.0", "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", "debug": "^4.0.1", "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.3", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", - "esquery": "^1.0.1", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^7.0.0", "is-glob": "^4.0.0", "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.14", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.8.3", + "optionator": "^0.9.1", "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.0.1", - "table": "^5.2.3", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, - "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + }, + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "requires": { - "type-fest": "^0.8.1" + "yallist": "^4.0.0" } }, "ms": { @@ -2624,63 +2456,76 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "lru-cache": "^6.0.0" + } + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" } }, "strip-json-comments": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", - "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true } } }, "eslint-config-prettier": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.10.0.tgz", - "integrity": "sha512-AtndijGte1rPILInUdHjvKEGbIV06NuvPrqlIEaEaWtbtvJh464mDeyGMdZEQMsGvC0ZVkiex1fSNcC4HAbRGg==", - "dev": true, - "requires": { - "get-stdin": "^6.0.0" - } + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "dev": true }, "eslint-config-standard": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-14.1.0.tgz", - "integrity": "sha512-EF6XkrrGVbvv8hL/kYa/m6vnvmUT+K82pJJc4JJVMM6+Qgqh0pnwprSxdduDLB9p/7bIxD+YV5O0wfb8lmcPbA==", + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz", + "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", "dev": true }, - "eslint-config-standard-jsx": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-8.1.0.tgz", - "integrity": "sha512-ULVC8qH8qCqbU792ZOO6DaiaZyHNS/5CZt3hKqHkEhVlhPEPN3nfBqqxJCyp59XrjIBZPu1chMYe9T2DXZ7TMw==", - "dev": true - }, - "eslint-config-standard-react": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard-react/-/eslint-config-standard-react-9.2.0.tgz", - "integrity": "sha512-u+KRP2uCtthZ/W4DlLWCC59GZNV/y9k9yicWWammgTs/Omh8ZUUPF3EnYm81MAcbkYQq2Wg0oxutAhi/FQ8mIw==", - "dev": true, - "requires": { - "eslint-config-standard-jsx": "^8.0.0" - } - }, "eslint-import-resolver-node": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz", - "integrity": "sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==", + "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", @@ -2688,147 +2533,134 @@ }, "dependencies": { "resolve": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", "dev": true, "requires": { + "is-core-module": "^2.2.0", "path-parse": "^1.0.6" } } } }, "eslint-module-utils": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.5.2.tgz", - "integrity": "sha512-LGScZ/JSlqGKiT8OC+cYRxseMjyqt6QO54nl281CK93unD89ijSeRV6An8Ci/2nvWVKe8K/Tqdm75RQoIOCr+Q==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.1.tgz", + "integrity": "sha512-ZXI9B8cxAJIH4nfkhTwcRTEAnrVfobYqwjWy/QMCZ8rHkZHFjf9yO4BzpiF9kCSfNlMG54eKigISHpX0+AaT4A==", "dev": true, "requires": { - "debug": "^2.6.9", + "debug": "^3.2.7", "pkg-dir": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } } }, "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", "regexpp": "^3.0.0" - }, - "dependencies": { - "eslint-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", - "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "regexpp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz", - "integrity": "sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==", - "dev": true - } } }, "eslint-plugin-import": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz", - "integrity": "sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw==", + "version": "2.23.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.23.4.tgz", + "integrity": "sha512-6/wP8zZRsnQFiR3iaPFgh5ImVRM1WN5NUWfTIRqwOdeiGJlBcSk82o1FEVq8yXmy4lkIzTo7YhHCIxlU/2HyEQ==", "dev": true, "requires": { - "array-includes": "^3.0.3", - "array.prototype.flat": "^1.2.1", - "contains-path": "^0.1.0", + "array-includes": "^3.1.3", + "array.prototype.flat": "^1.2.4", "debug": "^2.6.9", - "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.2", - "eslint-module-utils": "^2.4.1", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.4", + "eslint-module-utils": "^2.6.1", + "find-up": "^2.0.0", "has": "^1.0.3", + "is-core-module": "^2.4.0", "minimatch": "^3.0.4", - "object.values": "^1.1.0", - "read-pkg-up": "^2.0.0", - "resolve": "^1.12.0" + "object.values": "^1.1.3", + "pkg-up": "^2.0.0", + "read-pkg-up": "^3.0.0", + "resolve": "^1.20.0", + "tsconfig-paths": "^3.9.0" }, "dependencies": { "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" + "esutils": "^2.0.2" + } + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + }, + "is-callable": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==" + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" } - } - } - }, - "eslint-plugin-jsx-a11y": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz", - "integrity": "sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.4.5", - "aria-query": "^3.0.0", - "array-includes": "^3.0.3", - "ast-types-flow": "^0.0.7", - "axobject-query": "^2.0.2", - "damerau-levenshtein": "^1.0.4", - "emoji-regex": "^7.0.2", - "has": "^1.0.3", - "jsx-ast-utils": "^2.2.1" - }, - "dependencies": { - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true } } }, "eslint-plugin-mocha": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-6.3.0.tgz", - "integrity": "sha512-Cd2roo8caAyG21oKaaNTj7cqeYRWW1I2B5SfpKRp0Ip1gkfwoR1Ow0IGlPWnNjzywdF4n+kHL8/9vM6zCJUxdg==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-8.2.0.tgz", + "integrity": "sha512-8oOR47Ejt+YJPNQzedbiklDqS1zurEaNrxXpRs+Uk4DMDPVmKNagShFeUaYsfvWP55AhI+P1non5QZAHV6K78A==", "dev": true, "requires": { - "eslint-utils": "^2.0.0", - "ramda": "^0.27.0" - }, - "dependencies": { - "eslint-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", - "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - } + "eslint-utils": "^2.1.0", + "ramda": "^0.27.1" } }, "eslint-plugin-node": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.0.0.tgz", - "integrity": "sha512-chUs/NVID+sknFiJzxoN9lM7uKSOEta8GC8365hw1nDfwIPIjjpRSwwPvQanWv8dt/pDe9EV4anmVSwdiSndNg==", + "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", @@ -2839,19 +2671,10 @@ "semver": "^6.1.0" }, "dependencies": { - "eslint-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", - "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, "ignore": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", - "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", "dev": true }, "semver": { @@ -2877,72 +2700,20 @@ "integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==", "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==", - "dev": true, - "requires": { - "array-includes": "^3.1.1", - "doctrine": "^2.1.0", - "has": "^1.0.3", - "jsx-ast-utils": "^2.2.3", - "object.entries": "^1.1.1", - "object.fromentries": "^2.0.2", - "object.values": "^1.1.1", - "prop-types": "^15.7.2", - "resolve": "^1.15.1", - "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.2", - "xregexp": "^4.3.0" - }, - "dependencies": { - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "resolve": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "eslint-plugin-standard": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz", - "integrity": "sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ==", - "dev": true - }, "eslint-scope": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", - "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "requires": { - "esrecurse": "^4.1.0", + "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "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" @@ -2955,26 +2726,26 @@ "dev": true }, "espree": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, "requires": { - "acorn": "^7.1.1", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" }, "dependencies": { "acorn": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", - "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true }, - "acorn-jsx": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", - "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true } } @@ -2986,21 +2757,26 @@ "dev": true }, "esquery": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.1.0.tgz", - "integrity": "sha512-MxYW9xKmROWF672KqjO75sszsA8Mxhw06YFeS5VHlB98KDHbOSurm3ArsjO60Eaf3QmGMCP1yn+0JQkNLo/97Q==", - "dev": true, - "requires": { - "estraverse": "^4.0.0" - } + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true }, "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { - "estraverse": "^4.1.0" + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } } }, "estraverse": { @@ -3083,28 +2859,6 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo=" }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "dependencies": { - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - } - } - }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -3137,22 +2891,13 @@ "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz", "integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ==" }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "requires": { - "flat-cache": "^2.0.1" + "flat-cache": "^3.0.4" } }, "file-uri-to-path": { @@ -3198,40 +2943,24 @@ "integrity": "sha1-WKRmaX34piBc39vzlVNri9d3pfY=" }, "flat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", - "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", - "dev": true, - "requires": { - "is-buffer": "~2.0.3" - } + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true }, "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - }, - "dependencies": { - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } + "flatted": "^3.1.0" } }, "flatted": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", - "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.1.tgz", + "integrity": "sha512-OMQjaErSFHmHqZe+PSidH5n8j3O0F2DdnVh8JB4j4eUQ2k6KvB0qGfrKIhapvez5JerBbmWkaLYUYWISaESoXg==", "dev": true }, "forever-agent": { @@ -3354,11 +3083,16 @@ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, - "get-stdin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", - "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", - "dev": true + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } }, "get-stream": { "version": "4.1.0", @@ -3417,9 +3151,9 @@ } }, "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.10.0.tgz", + "integrity": "sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==", "dev": true }, "google-auth-library": { @@ -3667,14 +3401,11 @@ "function-bind": "^1.1.1" } }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } + "has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true }, "has-flag": { "version": "3.0.0", @@ -3856,12 +3587,6 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, "inflection": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", @@ -3886,131 +3611,6 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, - "inquirer": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", - "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^3.0.0", - "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.15", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.5.3", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "run-async": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", - "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "internal-slot": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz", - "integrity": "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==", - "dev": true, - "requires": { - "es-abstract": "^1.17.0-next.1", - "has": "^1.0.3", - "side-channel": "^1.0.2" - } - }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -4027,6 +3627,12 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, + "is-bigint": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", + "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==", + "dev": true + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -4041,11 +3647,19 @@ "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=" }, + "is-boolean-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", + "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, "is-buffer": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", - "dev": true + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "is-callable": { "version": "1.1.5", @@ -4062,6 +3676,15 @@ "ci-info": "^2.0.0" } }, + "is-core-module": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.5.0.tgz", + "integrity": "sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, "is-date-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", @@ -4101,6 +3724,12 @@ "is-path-inside": "^3.0.1" } }, + "is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true + }, "is-npm": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", @@ -4113,6 +3742,12 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-number-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", + "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==", + "dev": true + }, "is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", @@ -4124,19 +3759,19 @@ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true }, - "is-promise": { + "is-plain-obj": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "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.3", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", + "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", "dev": true, "requires": { - "has": "^1.0.3" + "call-bind": "^1.0.2" } }, "is-stream": { @@ -4212,12 +3847,6 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, "json-bigint": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz", @@ -4232,6 +3861,12 @@ "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", "dev": true }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -4253,6 +3888,23 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, + "json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + } + } + }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -4273,16 +3925,6 @@ "verror": "1.10.0" } }, - "jsx-ast-utils": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz", - "integrity": "sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA==", - "dev": true, - "requires": { - "array-includes": "^3.0.3", - "object.assign": "^4.1.0" - } - }, "just-extend": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.0.tgz", @@ -4327,31 +3969,31 @@ } }, "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" } }, "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", + "parse-json": "^4.0.0", + "pify": "^3.0.0", "strip-bom": "^3.0.0" }, "dependencies": { "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true } } @@ -4389,6 +4031,12 @@ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -4400,22 +4048,16 @@ "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=" }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true - }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "lodash.unescape": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", - "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=", + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", "dev": true }, "log-driver": { @@ -4424,12 +4066,63 @@ "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==" }, "log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", "dev": true, "requires": { - "chalk": "^2.4.2" + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "logger-sharelatex": { @@ -4452,63 +4145,11 @@ } } }, - "loglevel": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz", - "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==", - "dev": true - }, - "loglevel-colored-level-prefix": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz", - "integrity": "sha1-akAhj9x64V/HbD0PPmdsRlOIYD4=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "loglevel": "^1.4.1" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -4540,24 +4181,6 @@ } } }, - "make-plural": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-4.3.0.tgz", - "integrity": "sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true, - "optional": true - } - } - }, "map-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", @@ -4571,13 +4194,6 @@ "charenc": "0.0.2", "crypt": "0.0.2", "is-buffer": "~1.1.6" - }, - "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": { @@ -4590,29 +4206,6 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, - "messageformat": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/messageformat/-/messageformat-2.3.0.tgz", - "integrity": "sha512-uTzvsv0lTeQxYI2y1NPa1lItL5VRI8Gb93Y2K2ue5gBPyrbJxfDi/EYWxh2PKv5yO42AJeeqblS9MJSh/IEk4w==", - "dev": true, - "requires": { - "make-plural": "^4.3.0", - "messageformat-formatters": "^2.0.1", - "messageformat-parser": "^4.1.2" - } - }, - "messageformat-formatters": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/messageformat-formatters/-/messageformat-formatters-2.0.1.tgz", - "integrity": "sha512-E/lQRXhtHwGuiQjI7qxkLp8AHbMD5r2217XNe/SREbBlSawe0lOqsFb7rflZJmlQFSULNLIqlcjjsCPlB3m3Mg==", - "dev": true - }, - "messageformat-parser": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-4.1.2.tgz", - "integrity": "sha512-7dWuifeyldz7vhEuL96Kwq1fhZXBW+TUfbnHN4UCrCxoXQTYjHnR78eI66Gk9LaLLsAvzPNVJBaa66DRfFNaiA==", - "dev": true - }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -4636,12 +4229,6 @@ "mime-db": "1.43.0" } }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, "mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", @@ -4687,89 +4274,222 @@ } }, "mocha": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.0.tgz", - "integrity": "sha512-MymHK8UkU0K15Q/zX7uflZgVoRWiTjy0fXE/QjKts6mowUvGxOdPhZ2qj3b0iZdUrNZlW9LAIMFHB4IW+2b3EQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", + "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", "dev": true, "requires": { - "ansi-colors": "3.2.3", + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.3.0", - "debug": "3.2.6", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", "growl": "1.10.5", "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "3.0.0", + "js-yaml": "4.0.0", + "log-symbols": "4.0.0", "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "ms": "2.1.1", - "node-environment-flags": "1.0.6", - "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", + "ms": "2.1.3", + "nanoid": "3.1.20", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", "wide-align": "1.1.3", - "yargs": "13.3.0", - "yargs-parser": "13.1.1", - "yargs-unparser": "1.6.0" + "workerpool": "6.1.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" }, "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "requires": { - "ms": "^2.1.1" + "color-convert": "^2.0.1" } }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "dev": true, + "requires": { + "argparse": "^2.0.1" } }, "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "p-locate": "^5.0.0" } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "requires": { - "p-limit": "^2.0.0" - }, - "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - } + "p-limit": "^3.0.2" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" } } } @@ -4797,12 +4517,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, "mv": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", @@ -4875,6 +4589,12 @@ "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" }, + "nanoid": { + "version": "3.1.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", + "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", + "dev": true + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -4902,12 +4622,6 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, "nise": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.3.tgz", @@ -4938,24 +4652,6 @@ } } }, - "node-environment-flags": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", - "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", - "dev": true, - "requires": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, "node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", @@ -5021,15 +4717,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } } } }, @@ -5107,9 +4794,9 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "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.11.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", + "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", "dev": true }, "object-keys": { @@ -5119,61 +4806,26 @@ "dev": true }, "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.entries": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz", - "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", "dev": true, "requires": { + "call-bind": "^1.0.0", "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "object.fromentries": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz", - "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", - "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" } }, "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.4.tgz", + "integrity": "sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==", "dev": true, "requires": { + "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" + "es-abstract": "^1.18.2" } }, "on-finished": { @@ -5197,27 +4849,18 @@ "wrappy": "1" } }, - "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "dev": true, "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" } }, "os-homedir": { @@ -5270,19 +4913,13 @@ "requires": { "p-try": "^1.0.0" } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true } } }, "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, "package-json": { @@ -5320,12 +4957,13 @@ "integrity": "sha512-KbAJuYGUhZkB9gotDiKLnZ7Z3VTacK3fgwmDdB6ZVDtJbMBT6MfLga0WJaYpPDu0mzqT0NgHtHDt5PY4l0nidg==" }, "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { - "error-ex": "^1.2.0" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" } }, "parse-ms": { @@ -5349,16 +4987,10 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, "path-parse": { @@ -5372,18 +5004,18 @@ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, "requires": { - "pify": "^2.0.0" + "pify": "^3.0.0" }, "dependencies": { "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true } } @@ -5424,6 +5056,15 @@ "find-up": "^2.1.0" } }, + "pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", + "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, "pprof": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pprof/-/pprof-3.0.0.tgz", @@ -5532,9 +5173,9 @@ } }, "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, "prepend-http": { @@ -5544,566 +5185,11 @@ "dev": true }, "prettier": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", - "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", + "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", "dev": true }, - "prettier-eslint": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-9.0.1.tgz", - "integrity": "sha512-KZT65QTosSAqBBqmrC+RpXbsMRe7Os2YSR9cAfFbDlyPAopzA/S5bioiZ3rpziNQNSJaOxmtXSx07EQ+o2Dlug==", - "dev": true, - "requires": { - "@typescript-eslint/parser": "^1.10.2", - "common-tags": "^1.4.0", - "core-js": "^3.1.4", - "dlv": "^1.1.0", - "eslint": "^5.0.0", - "indent-string": "^4.0.0", - "lodash.merge": "^4.6.0", - "loglevel-colored-level-prefix": "^1.0.0", - "prettier": "^1.7.0", - "pretty-format": "^23.0.1", - "require-relative": "^0.8.7", - "typescript": "^3.2.1", - "vue-eslint-parser": "^2.0.2" - }, - "dependencies": { - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "eslint": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", - "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.9.1", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^4.0.3", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^5.0.1", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.7.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^6.2.2", - "js-yaml": "^3.13.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.11", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^5.5.1", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0" - } - }, - "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "espree": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", - "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", - "dev": true, - "requires": { - "acorn": "^6.0.7", - "acorn-jsx": "^5.0.0", - "eslint-visitor-keys": "^1.0.0" - } - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "inquirer": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", - "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.2.0", - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.12", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.4.0", - "string-width": "^2.1.0", - "strip-ansi": "^5.1.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", - "dev": true - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", - "dev": true - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "prettier-eslint-cli": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/prettier-eslint-cli/-/prettier-eslint-cli-5.0.0.tgz", - "integrity": "sha512-cei9UbN1aTrz3sQs88CWpvY/10PYTevzd76zoG1tdJ164OhmNTFRKPTOZrutVvscoQWzbnLKkviS3gu5JXwvZg==", - "dev": true, - "requires": { - "arrify": "^2.0.1", - "boolify": "^1.0.0", - "camelcase-keys": "^6.0.0", - "chalk": "^2.4.2", - "common-tags": "^1.8.0", - "core-js": "^3.1.4", - "eslint": "^5.0.0", - "find-up": "^4.1.0", - "get-stdin": "^7.0.0", - "glob": "^7.1.4", - "ignore": "^5.1.2", - "lodash.memoize": "^4.1.2", - "loglevel-colored-level-prefix": "^1.0.0", - "messageformat": "^2.2.1", - "prettier-eslint": "^9.0.0", - "rxjs": "^6.5.2", - "yargs": "^13.2.4" - }, - "dependencies": { - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "eslint": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", - "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.9.1", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^4.0.3", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^5.0.1", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.7.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^6.2.2", - "js-yaml": "^3.13.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.11", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^5.5.1", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0" - }, - "dependencies": { - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - } - } - }, - "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "espree": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", - "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", - "dev": true, - "requires": { - "acorn": "^6.0.7", - "acorn-jsx": "^5.0.0", - "eslint-visitor-keys": "^1.0.0" - } - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "get-stdin": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", - "integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==", - "dev": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "ignore": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", - "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", - "dev": true - }, - "inquirer": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", - "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.2.0", - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.12", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.4.0", - "string-width": "^2.1.0", - "strip-ansi": "^5.1.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", - "dev": true - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - }, - "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - } - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, "prettier-linter-helpers": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", @@ -6113,24 +5199,6 @@ "fast-diff": "^1.1.2" } }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - } - } - }, "pretty-ms": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", @@ -6158,17 +5226,6 @@ "tdigest": "^0.1.1" } }, - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dev": true, - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, "protobufjs": { "version": "6.8.8", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", @@ -6270,17 +5327,20 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, - "quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "ramda": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", + "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==", "dev": true }, - "ramda": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.0.tgz", - "integrity": "sha512-pVzZdDpWwWqEVVLshWUHjNwuVP7SfcmPraYuqocJp1yo2U1R7P+5QAfDhdItkuoGqIBnBYrtPp7rEPqDn9HlZA==", - "dev": true + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } }, "range-parser": { "version": "1.2.1", @@ -6350,31 +5410,25 @@ } } }, - "react-is": { - "version": "16.13.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz", - "integrity": "sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA==", - "dev": true - }, "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", "dev": true, "requires": { - "load-json-file": "^2.0.0", + "load-json-file": "^4.0.0", "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" + "path-type": "^3.0.0" } }, "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", "dev": true, "requires": { "find-up": "^2.0.0", - "read-pkg": "^2.0.0" + "read-pkg": "^3.0.0" } }, "readable-stream": { @@ -6400,26 +5454,10 @@ "picomatch": "^2.0.4" } }, - "regenerator-runtime": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", - "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", - "dev": true - }, - "regexp.prototype.flags": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", - "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, "registry-auth-token": { @@ -6480,6 +5518,12 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, "require-in-the-middle": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.1.0.tgz", @@ -6511,18 +5555,6 @@ "integrity": "sha1-rW8wwTvs15cBDEaK+ndcDAprR/o=", "dev": true }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "require-relative": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", - "integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=", - "dev": true - }, "resolve": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", @@ -6546,16 +5578,6 @@ "lowercase-keys": "^1.0.0" } }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, "retry-as-promised": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", @@ -6596,24 +5618,6 @@ "glob": "^7.0.5" } }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } - }, - "rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -6746,6 +5750,15 @@ "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-2.3.0.tgz", "integrity": "sha512-Ibz08vnXvkZ8LJTiUOxRcj1Ckdn7qafNZ2t59jYHMX1VIebTAOYefWdRYFt6z6+hy52WGthAHAoLc9hvk3onqA==" }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, "serve-static": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", @@ -6768,18 +5781,18 @@ "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "^1.0.0" + "shebang-regex": "^3.0.0" } }, "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, "shimmer": { @@ -6787,16 +5800,6 @@ "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.0.tgz", "integrity": "sha1-+Wb3VVeJdj502IQRk2haXnhzZmU=" }, - "side-channel": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz", - "integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==", - "dev": true, - "requires": { - "es-abstract": "^1.17.0-next.1", - "object-inspect": "^1.7.0" - } - }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -6841,20 +5844,19 @@ } }, "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0" }, "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true } } @@ -6883,9 +5885,9 @@ } }, "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -6893,15 +5895,15 @@ } }, "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", "dev": true }, "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "requires": { "spdx-exceptions": "^2.1.0", @@ -6909,9 +5911,9 @@ } }, "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz", + "integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==", "dev": true }, "split": { @@ -7021,38 +6023,24 @@ "strip-ansi": "^3.0.0" } }, - "string.prototype.matchall": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz", - "integrity": "sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg==", + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0", - "has-symbols": "^1.0.1", - "internal-slot": "^1.0.2", - "regexp.prototype.flags": "^1.3.0", - "side-channel": "^1.0.2" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" } }, - "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.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" - } - }, - "string.prototype.trimright": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", - "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" } }, "string_decoder": { @@ -7088,62 +6076,35 @@ "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=" }, "supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" } }, "table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", + "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", "dev": true, "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "ajv": { + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.1.tgz", + "integrity": "sha512-42VLtQUOLefAvKFAQIxIZDaThq6om/PrfP0CYk3/vn+y4BMNkKnbli8ON2QCiHov4KkzOSJ/xSoBJdayiiYvVQ==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" + "fast-deep-equal": "^3.1.1", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" } } } @@ -7260,21 +6221,6 @@ "integrity": "sha512-W3AmPTJWZkRwu+iSNxPIsLZ2ByADsOLbbLxe46UJyWj3mlYLlwucKiq+/dPm0l9wTzqoF3/2PH0AGFCebjq23A==", "dev": true }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, "to-no-case": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/to-no-case/-/to-no-case-1.0.2.tgz", @@ -7350,11 +6296,24 @@ "punycode": "^2.1.1" } }, - "tslib": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", - "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", - "dev": true + "tsconfig-paths": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.10.1.tgz", + "integrity": "sha512-rETidPDgCpltxF7MjBZlAFPUHv5aHH2MymyPvh+vEyWAED4Eb/WeMbsnD/JDr4OKPOA1TssDHgIcpTN5Kh0p6Q==", + "dev": true, + "requires": { + "json5": "^2.2.0", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + } + } }, "tunnel-agent": { "version": "0.6.0", @@ -7370,12 +6329,12 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "requires": { - "prelude-ls": "~1.1.2" + "prelude-ls": "^1.2.1" } }, "type-detect": { @@ -7413,11 +6372,25 @@ "is-typedarray": "^1.0.0" } }, - "typescript": { - "version": "3.7.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", - "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", - "dev": true + "unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + }, + "dependencies": { + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + } + } }, "undefsafe": { "version": "2.0.3", @@ -7477,26 +6450,14 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "requires": { "color-convert": "^2.0.1" } }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -7504,20 +6465,17 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -7557,9 +6515,9 @@ "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" }, "v8-compile-cache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", - "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, "v8-profiler-node8": { @@ -7620,99 +6578,32 @@ "extsprintf": "^1.2.0" } }, - "vue-eslint-parser": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-2.0.3.tgz", - "integrity": "sha512-ZezcU71Owm84xVF6gfurBQUGg8WQ+WZGxgDEQu1IHFBZNx7BFZg3L1yHxrCBNNwbwFtE1GuvfJKMtb6Xuwc/Bw==", - "dev": true, - "requires": { - "debug": "^3.1.0", - "eslint-scope": "^3.7.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^3.5.2", - "esquery": "^1.0.0", - "lodash": "^4.17.4" - }, - "dependencies": { - "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", - "dev": true - }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "eslint-scope": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", - "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", - "dev": true, - "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, "walkdir": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==" }, "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { "isexe": "^2.0.0" } }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } }, "wide-align": { "version": "1.1.3", @@ -7779,56 +6670,17 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, + "workerpool": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", + "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", + "dev": true + }, "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true }, "wrappy": { "version": "1.0.2", @@ -7840,15 +6692,6 @@ "resolved": "https://registry.npmjs.org/wrench/-/wrench-1.5.9.tgz", "integrity": "sha1-QRaRxjqbJTGxcAJnJ5veyiOyFCo=" }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, "write-file-atomic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", @@ -7867,19 +6710,10 @@ "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", "dev": true }, - "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, - "requires": { - "@babel/runtime-corejs3": "^7.8.3" - } - }, "y18n": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true }, "yallist": { @@ -7888,121 +6722,43 @@ "integrity": "sha1-tLBJ4xS+VF486AIjbWzSLNkcPek=" }, "yargs": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", - "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.1" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - }, - "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - } - } - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" } }, "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true }, "yargs-unparser": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", - "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, "requires": { - "flat": "^4.1.0", - "lodash": "^4.17.15", - "yargs": "^13.3.0" + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + } } }, "yn": { diff --git a/services/clsi/package.json b/services/clsi/package.json index 8a065d5a5e..4f22ebd729 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -13,9 +13,10 @@ "test:unit:_run": "mocha --recursive --reporter spec $@ test/unit/js test/unit/lib", "test:unit": "npm run test:unit:_run -- --grep=$MOCHA_GREP", "nodemon": "nodemon --config nodemon.json", - "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" + "lint": "eslint --max-warnings 0 --format unix .", + "format": "prettier --list-different $PWD/'**/*.js'", + "format:fix": "prettier --write $PWD/'**/*.js'", + "lint:fix": "eslint --fix ." }, "author": "James Allen <james@sharelatex.com>", "dependencies": { @@ -44,27 +45,21 @@ "wrench": "~1.5.9" }, "devDependencies": { - "babel-eslint": "^10.1.0", - "chai": "~4.2.0", - "eslint": "^6.8.0", - "eslint-config-prettier": "^6.10.0", - "eslint-config-standard": "^14.1.0", - "eslint-config-standard-jsx": "^8.1.0", - "eslint-config-standard-react": "^9.2.0", - "eslint-plugin-chai-expect": "^2.1.0", - "eslint-plugin-chai-friendly": "^0.5.0", - "eslint-plugin-import": "^2.20.1", - "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-mocha": "^6.3.0", - "eslint-plugin-node": "^11.0.0", + "chai": "^4.2.0", + "chai-as-promised": "^7.1.1", + "eslint": "^7.21.0", + "eslint-config-prettier": "^8.1.0", + "eslint-config-standard": "^16.0.2", + "eslint-plugin-chai-expect": "^2.2.0", + "eslint-plugin-chai-friendly": "^0.6.0", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-mocha": "^8.0.0", + "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^3.1.2", "eslint-plugin-promise": "^4.2.1", - "eslint-plugin-react": "^7.19.0", - "eslint-plugin-standard": "^4.0.1", - "mocha": "^7.1.0", + "mocha": "^8.3.2", "nodemon": "^2.0.7", - "prettier": "^2.0.0", - "prettier-eslint-cli": "^5.0.0", + "prettier": "^2.2.1", "sandboxed-module": "^2.0.3", "sinon": "~9.0.1", "timekeeper": "2.2.0" From f285e503b49d84af573b80a6bb37a74d42bc526e Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 13 Jul 2021 12:04:48 +0100 Subject: [PATCH 749/754] [misc] run format_fix and lint:fix --- services/clsi/app.js | 57 +- services/clsi/app/js/CompileController.js | 191 +++---- services/clsi/app/js/CompileManager.js | 516 +++++++++--------- services/clsi/app/js/ContentCacheManager.js | 18 +- services/clsi/app/js/ContentCacheMetrics.js | 8 +- services/clsi/app/js/ContentTypeMapper.js | 2 +- services/clsi/app/js/DockerLockManager.js | 2 +- services/clsi/app/js/DockerRunner.js | 42 +- services/clsi/app/js/DraftModeManager.js | 4 +- services/clsi/app/js/Errors.js | 2 +- services/clsi/app/js/LatexRunner.js | 28 +- services/clsi/app/js/LocalCommandRunner.js | 6 +- services/clsi/app/js/LockManager.js | 6 +- services/clsi/app/js/OutputCacheManager.js | 104 ++-- services/clsi/app/js/OutputFileFinder.js | 14 +- services/clsi/app/js/OutputFileOptimiser.js | 20 +- .../clsi/app/js/ProjectPersistenceManager.js | 43 +- services/clsi/app/js/RequestParser.js | 29 +- services/clsi/app/js/ResourceStateManager.js | 73 +-- services/clsi/app/js/ResourceWriter.js | 253 ++++----- services/clsi/app/js/SafeReader.js | 25 +- .../clsi/app/js/StaticServerForbidSymlinks.js | 6 +- services/clsi/app/js/TikzManager.js | 96 ++-- services/clsi/app/js/UrlCache.js | 116 ++-- services/clsi/app/js/UrlFetcher.js | 2 +- services/clsi/app/js/db.js | 12 +- services/clsi/app/lib/pdfjs/FSPdfManager.js | 2 +- services/clsi/app/lib/pdfjs/FSStream.js | 6 +- services/clsi/app/lib/pdfjs/parseXrefTable.js | 2 +- services/clsi/config/settings.defaults.js | 44 +- .../acceptance/js/AllowedImageNamesTests.js | 14 +- .../acceptance/js/BrokenLatexFileTests.js | 12 +- .../test/acceptance/js/DeleteOldFilesTest.js | 6 +- .../acceptance/js/ExampleDocumentTests.js | 153 +++--- .../acceptance/js/SimpleLatexFileTests.js | 6 +- services/clsi/test/acceptance/js/Stats.js | 2 +- .../clsi/test/acceptance/js/SynctexTests.js | 18 +- .../clsi/test/acceptance/js/TimeoutTests.js | 10 +- .../test/acceptance/js/UrlCachingTests.js | 52 +- .../clsi/test/acceptance/js/WordcountTests.js | 10 +- .../clsi/test/acceptance/js/helpers/Client.js | 30 +- .../test/acceptance/js/helpers/ClsiApp.js | 6 +- .../test/acceptance/scripts/settings.test.js | 27 +- services/clsi/test/bench/hashbench.js | 18 +- services/clsi/test/load/js/loadTest.js | 19 +- services/clsi/test/setup.js | 6 +- services/clsi/test/smoke/js/SmokeTests.js | 18 +- .../test/unit/js/CompileControllerTests.js | 84 +-- .../clsi/test/unit/js/CompileManagerTests.js | 66 +-- .../test/unit/js/ContentCacheManagerTests.js | 34 +- .../test/unit/js/DockerLockManagerTests.js | 22 +- .../clsi/test/unit/js/DockerRunnerTests.js | 74 +-- .../test/unit/js/DraftModeManagerTests.js | 4 +- .../clsi/test/unit/js/LatexRunnerTests.js | 26 +- .../clsi/test/unit/js/LockManagerTests.js | 6 +- .../test/unit/js/OutputFileFinderTests.js | 12 +- .../test/unit/js/OutputFileOptimiserTests.js | 4 +- .../unit/js/ProjectPersistenceManagerTests.js | 16 +- .../clsi/test/unit/js/RequestParserTests.js | 16 +- .../test/unit/js/ResourceStateManagerTests.js | 10 +- .../clsi/test/unit/js/ResourceWriterTests.js | 57 +- .../js/StaticServerForbidSymlinksTests.js | 12 +- services/clsi/test/unit/js/TikzManager.js | 4 +- services/clsi/test/unit/js/UrlCacheTests.js | 8 +- services/clsi/test/unit/js/UrlFetcherTests.js | 22 +- services/clsi/test/unit/lib/pdfjsTests.js | 4 +- 66 files changed, 1315 insertions(+), 1302 deletions(-) diff --git a/services/clsi/app.js b/services/clsi/app.js index 214d20fbab..8916c0a04a 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -157,7 +157,7 @@ const staticCompileServer = ForbidSymlinks( res.set('Etag', etag(path, stat)) } return res.set('Content-Type', ContentTypeMapper.map(path)) - } + }, } ) @@ -177,7 +177,7 @@ const staticOutputServer = ForbidSymlinks( res.set('Etag', etag(path, stat)) } return res.set('Content-Type', ContentTypeMapper.map(path)) - } + }, } ) @@ -201,28 +201,29 @@ app.get( ContentController.getPdfRange ) -app.get('/project/:project_id/build/:build_id/output/*', function ( - req, - res, - next -) { - // for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) - req.url = - `/${req.params.project_id}/` + - OutputCacheManager.path(req.params.build_id, `/${req.params[0]}`) - return staticOutputServer(req, res, next) -}) +app.get( + '/project/:project_id/build/:build_id/output/*', + function (req, res, next) { + // for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) + req.url = + `/${req.params.project_id}/` + + OutputCacheManager.path(req.params.build_id, `/${req.params[0]}`) + return staticOutputServer(req, res, next) + } +) -app.get('/project/:project_id/user/:user_id/output/*', function ( - req, - res, - next -) { - // for specific user get the path to the top level file - logger.warn({ url: req.url }, 'direct request for file in compile directory') - req.url = `/${req.params.project_id}-${req.params.user_id}/${req.params[0]}` - return staticCompileServer(req, res, next) -}) +app.get( + '/project/:project_id/user/:user_id/output/*', + function (req, res, next) { + // for specific user get the path to the top level file + logger.warn( + { url: req.url }, + 'direct request for file in compile directory' + ) + req.url = `/${req.params.project_id}-${req.params.user_id}/${req.params[0]}` + return staticCompileServer(req, res, next) + } +) app.get('/project/:project_id/output/*', function (req, res, next) { logger.warn({ url: req.url }, 'direct request for file in compile directory') @@ -271,7 +272,7 @@ if (Settings.processLifespanLimitMs) { function runSmokeTest() { if (Settings.processTooOld) return logger.log('running smoke tests') - smokeTest.triggerRun((err) => { + smokeTest.triggerRun(err => { if (err) logger.error({ err }, 'smoke tests failed') setTimeout(runSmokeTest, 30 * 1000) }) @@ -364,12 +365,12 @@ loadHttpServer.post('/state/maint', function (req, res, next) { const port = __guard__( Settings.internal != null ? Settings.internal.clsi : undefined, - (x) => x.port + x => x.port ) || 3013 const host = __guard__( Settings.internal != null ? Settings.internal.clsi : undefined, - (x1) => x1.host + x1 => x1.host ) || 'localhost' const loadTcpPort = Settings.internal.load_balancer_agent.load_port @@ -381,12 +382,12 @@ if (!module.parent) { // handle uncaught exceptions when running in production if (Settings.catchErrors) { process.removeAllListeners('uncaughtException') - process.on('uncaughtException', (error) => + process.on('uncaughtException', error => logger.error({ err: error }, 'uncaughtException') ) } - app.listen(port, host, (error) => { + app.listen(port, host, error => { if (error) { logger.fatal({ error }, `Error starting CLSI on ${host}:${port}`) } else { diff --git a/services/clsi/app/js/CompileController.js b/services/clsi/app/js/CompileController.js index acb6626ae8..488d818157 100644 --- a/services/clsi/app/js/CompileController.js +++ b/services/clsi/app/js/CompileController.js @@ -47,96 +47,94 @@ module.exports = CompileController = { if (error != null) { return next(error) } - return CompileManager.doCompileWithLock(request, function ( - error, - outputFiles, - stats, - timings - ) { - let code, status - if (outputFiles == null) { - outputFiles = [] - } - if (error instanceof Errors.AlreadyCompilingError) { - code = 423 // Http 423 Locked - status = 'compile-in-progress' - } else if (error instanceof Errors.FilesOutOfSyncError) { - code = 409 // Http 409 Conflict - status = 'retry' - } else if (error && error.code === 'EPIPE') { - // docker returns EPIPE when shutting down - code = 503 // send 503 Unavailable response - status = 'unavailable' - } else if (error != null ? error.terminated : undefined) { - status = 'terminated' - } else if (error != null ? error.validate : undefined) { - status = `validation-${error.validate}` - } else if (error != null ? error.timedout : undefined) { - status = 'timedout' - logger.log( - { err: error, project_id: request.project_id }, - 'timeout running compile' - ) - } else if (error != null) { - status = 'error' - code = 500 - logger.warn( - { err: error, project_id: request.project_id }, - 'error running compile' - ) - } else { - let file - status = 'failure' - for (file of Array.from(outputFiles)) { - if (file.path === 'output.pdf' && file.size > 0) { - status = 'success' - } + return CompileManager.doCompileWithLock( + request, + function (error, outputFiles, stats, timings) { + let code, status + if (outputFiles == null) { + outputFiles = [] } - - if (status === 'failure') { - logger.warn( - { project_id: request.project_id, outputFiles }, - 'project failed to compile successfully, no output.pdf generated' + if (error instanceof Errors.AlreadyCompilingError) { + code = 423 // Http 423 Locked + status = 'compile-in-progress' + } else if (error instanceof Errors.FilesOutOfSyncError) { + code = 409 // Http 409 Conflict + status = 'retry' + } else if (error && error.code === 'EPIPE') { + // docker returns EPIPE when shutting down + code = 503 // send 503 Unavailable response + status = 'unavailable' + } else if (error != null ? error.terminated : undefined) { + status = 'terminated' + } else if (error != null ? error.validate : undefined) { + status = `validation-${error.validate}` + } else if (error != null ? error.timedout : undefined) { + status = 'timedout' + logger.log( + { err: error, project_id: request.project_id }, + 'timeout running compile' ) - } + } else if (error != null) { + status = 'error' + code = 500 + logger.warn( + { err: error, project_id: request.project_id }, + 'error running compile' + ) + } else { + let file + status = 'failure' + for (file of Array.from(outputFiles)) { + if (file.path === 'output.pdf' && file.size > 0) { + status = 'success' + } + } - // log an error if any core files are found - for (file of Array.from(outputFiles)) { - if (file.path === 'core') { - logger.error( - { project_id: request.project_id, req, outputFiles }, - 'core file found in output' + if (status === 'failure') { + logger.warn( + { project_id: request.project_id, outputFiles }, + 'project failed to compile successfully, no output.pdf generated' ) } - } - } - if (error != null) { - outputFiles = error.outputFiles || [] - } - - timer.done() - return res.status(code || 200).send({ - compile: { - status, - error: (error != null ? error.message : undefined) || error, - stats, - timings, - outputFiles: outputFiles.map((file) => { - return { - url: - `${Settings.apis.clsi.url}/project/${request.project_id}` + - (request.user_id != null - ? `/user/${request.user_id}` - : '') + - (file.build != null ? `/build/${file.build}` : '') + - `/output/${file.path}`, - ...file + // log an error if any core files are found + for (file of Array.from(outputFiles)) { + if (file.path === 'core') { + logger.error( + { project_id: request.project_id, req, outputFiles }, + 'core file found in output' + ) } - }) + } } - }) - }) + + if (error != null) { + outputFiles = error.outputFiles || [] + } + + timer.done() + return res.status(code || 200).send({ + compile: { + status, + error: (error != null ? error.message : undefined) || error, + stats, + timings, + outputFiles: outputFiles.map(file => { + return { + url: + `${Settings.apis.clsi.url}/project/${request.project_id}` + + (request.user_id != null + ? `/user/${request.user_id}` + : '') + + (file.build != null ? `/build/${file.build}` : '') + + `/output/${file.path}`, + ...file, + } + }), + }, + }) + } + ) } ) }) @@ -195,7 +193,7 @@ module.exports = CompileController = { return next(error) } return res.json({ - pdf: pdfPositions + pdf: pdfPositions, }) } ) @@ -227,7 +225,7 @@ module.exports = CompileController = { return next(error) } return res.json({ - code: codePositions + code: codePositions, }) } ) @@ -246,17 +244,20 @@ module.exports = CompileController = { } logger.log({ image, file, project_id }, 'word count request') - return CompileManager.wordcount(project_id, user_id, file, image, function ( - error, - result - ) { - if (error != null) { - return next(error) + return CompileManager.wordcount( + project_id, + user_id, + file, + image, + function (error, result) { + if (error != null) { + return next(error) + } + return res.json({ + texcount: result, + }) } - return res.json({ - texcount: result - }) - }) + ) }, status(req, res, next) { @@ -264,5 +265,5 @@ module.exports = CompileController = { next = function (error) {} } return res.send('OK') - } + }, } diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js index f56bfafa32..07a9033b3d 100644 --- a/services/clsi/app/js/CompileManager.js +++ b/services/clsi/app/js/CompileManager.js @@ -65,7 +65,7 @@ module.exports = CompileManager = { } return LockManager.runWithLock( lockFile, - (releaseLock) => CompileManager.doCompile(request, releaseLock), + releaseLock => CompileManager.doCompile(request, releaseLock), callback ) }) @@ -84,264 +84,266 @@ module.exports = CompileManager = { { project_id: request.project_id, user_id: request.user_id }, 'syncing resources to disk' ) - return ResourceWriter.syncResourcesToDisk(request, compileDir, function ( - error, - resourceList - ) { - // NOTE: resourceList is insecure, it should only be used to exclude files from the output list - if (error != null && error instanceof Errors.FilesOutOfSyncError) { - logger.warn( - { project_id: request.project_id, user_id: request.user_id }, - 'files out of sync, please retry' - ) - return callback(error) - } else if (error != null) { - logger.err( - { - err: error, - project_id: request.project_id, - user_id: request.user_id - }, - 'error writing resources to disk' - ) - return callback(error) - } - logger.log( - { - project_id: request.project_id, - user_id: request.user_id, - time_taken: Date.now() - timer.start - }, - 'written files to disk' - ) - const syncStage = timer.done() - - const injectDraftModeIfRequired = function (callback) { - if (request.draft) { - return DraftModeManager.injectDraftMode( - Path.join(compileDir, request.rootResourcePath), - callback + return ResourceWriter.syncResourcesToDisk( + request, + compileDir, + function (error, resourceList) { + // NOTE: resourceList is insecure, it should only be used to exclude files from the output list + if (error != null && error instanceof Errors.FilesOutOfSyncError) { + logger.warn( + { project_id: request.project_id, user_id: request.user_id }, + 'files out of sync, please retry' ) - } else { - return callback() - } - } - - const createTikzFileIfRequired = (callback) => - TikzManager.checkMainFile( - compileDir, - request.rootResourcePath, - resourceList, - function (error, needsMainFile) { - if (error != null) { - return callback(error) - } - if (needsMainFile) { - return TikzManager.injectOutputFile( - compileDir, - request.rootResourcePath, - callback - ) - } else { - return callback() - } - } - ) - // set up environment variables for chktex - const env = {} - if (Settings.texliveOpenoutAny && Settings.texliveOpenoutAny !== '') { - // override default texlive openout_any environment variable - env.openout_any = Settings.texliveOpenoutAny - } - // only run chktex on LaTeX files (not knitr .Rtex files or any others) - const isLaTeXFile = - request.rootResourcePath != null - ? request.rootResourcePath.match(/\.tex$/i) - : undefined - if (request.check != null && isLaTeXFile) { - env.CHKTEX_OPTIONS = '-nall -e9 -e10 -w15 -w16' - env.CHKTEX_ULIMIT_OPTIONS = '-t 5 -v 64000' - if (request.check === 'error') { - env.CHKTEX_EXIT_ON_ERROR = 1 - } - if (request.check === 'validate') { - env.CHKTEX_VALIDATE = 1 - } - } - - // apply a series of file modifications/creations for draft mode and tikz - return async.series( - [injectDraftModeIfRequired, createTikzFileIfRequired], - function (error) { - if (error != null) { - return callback(error) - } - timer = new Metrics.Timer('run-compile') - // find the image tag to log it as a metric, e.g. 2015.1 (convert . to - for graphite) - let tag = - __guard__( - __guard__( - request.imageName != null - ? request.imageName.match(/:(.*)/) - : undefined, - (x1) => x1[1] - ), - (x) => x.replace(/\./g, '-') - ) || 'default' - if (!request.project_id.match(/^[0-9a-f]{24}$/)) { - tag = 'other' - } // exclude smoke test - Metrics.inc('compiles') - Metrics.inc(`compiles-with-image.${tag}`) - const compileName = getCompileName( - request.project_id, - request.user_id - ) - return LatexRunner.runLatex( - compileName, + return callback(error) + } else if (error != null) { + logger.err( { - directory: compileDir, - mainFile: request.rootResourcePath, - compiler: request.compiler, - timeout: request.timeout, - image: request.imageName, - flags: request.flags, - environment: env, - compileGroup: request.compileGroup + err: error, + project_id: request.project_id, + user_id: request.user_id, }, - function (error, output, stats, timings) { - // request was for validation only - let metric_key, metric_value - if (request.check === 'validate') { - const result = (error != null ? error.code : undefined) - ? 'fail' - : 'pass' - error = new Error('validation') - error.validate = result - } - // request was for compile, and failed on validation - if ( - request.check === 'error' && - (error != null ? error.message : undefined) === 'exited' - ) { - error = new Error('compilation') - error.validate = 'fail' - } - // compile was killed by user, was a validation, or a compile which failed validation - if ( - (error != null ? error.terminated : undefined) || - (error != null ? error.validate : undefined) || - (error != null ? error.timedout : undefined) - ) { - OutputFileFinder.findOutputFiles( - resourceList, - compileDir, - function (err, outputFiles) { - if (err != null) { - return callback(err) - } - error.outputFiles = outputFiles // return output files so user can check logs - return callback(error) - } - ) - return - } - // compile completed normally + 'error writing resources to disk' + ) + return callback(error) + } + logger.log( + { + project_id: request.project_id, + user_id: request.user_id, + time_taken: Date.now() - timer.start, + }, + 'written files to disk' + ) + const syncStage = timer.done() + + const injectDraftModeIfRequired = function (callback) { + if (request.draft) { + return DraftModeManager.injectDraftMode( + Path.join(compileDir, request.rootResourcePath), + callback + ) + } else { + return callback() + } + } + + const createTikzFileIfRequired = callback => + TikzManager.checkMainFile( + compileDir, + request.rootResourcePath, + resourceList, + function (error, needsMainFile) { if (error != null) { return callback(error) } - Metrics.inc('compiles-succeeded') - stats = stats || {} - const object = stats || {} - for (metric_key in object) { - metric_value = object[metric_key] - Metrics.count(metric_key, metric_value) - } - timings = timings || {} - const object1 = timings || {} - for (metric_key in object1) { - metric_value = object1[metric_key] - Metrics.timing(metric_key, metric_value) - } - const loadavg = - typeof os.loadavg === 'function' ? os.loadavg() : undefined - if (loadavg != null) { - Metrics.gauge('load-avg', loadavg[0]) - } - const ts = timer.done() - logger.log( - { - project_id: request.project_id, - user_id: request.user_id, - time_taken: ts, - stats, - timings, - loadavg - }, - 'done compile' - ) - if ((stats != null ? stats['latex-runs'] : undefined) > 0) { - Metrics.timing('run-compile-per-pass', ts / stats['latex-runs']) - } - if ( - (stats != null ? stats['latex-runs'] : undefined) > 0 && - (timings != null ? timings['cpu-time'] : undefined) > 0 - ) { - Metrics.timing( - 'run-compile-cpu-time-per-pass', - timings['cpu-time'] / stats['latex-runs'] + if (needsMainFile) { + return TikzManager.injectOutputFile( + compileDir, + request.rootResourcePath, + callback ) + } else { + return callback() } - // Emit compile time. - timings.compile = ts - - timer = new Metrics.Timer('process-output-files') - - return OutputFileFinder.findOutputFiles( - resourceList, - compileDir, - function (error, outputFiles) { - if (error != null) { - return callback(error) - } - return OutputCacheManager.saveOutputFiles( - { request, stats, timings }, - outputFiles, - compileDir, - outputDir, - (err, newOutputFiles) => { - if (err) { - const { - project_id: projectId, - user_id: userId - } = request - logger.err( - { projectId, userId, err }, - 'failed to save output files' - ) - } - - const outputStage = timer.done() - timings.sync = syncStage - timings.output = outputStage - - // Emit e2e compile time. - timings.compileE2E = timerE2E.done() - - if (stats['pdf-size']) { - emitPdfStats(stats, timings) - } - - callback(null, newOutputFiles, stats, timings) - } - ) - } - ) } ) + // set up environment variables for chktex + const env = {} + if (Settings.texliveOpenoutAny && Settings.texliveOpenoutAny !== '') { + // override default texlive openout_any environment variable + env.openout_any = Settings.texliveOpenoutAny } - ) - }) + // only run chktex on LaTeX files (not knitr .Rtex files or any others) + const isLaTeXFile = + request.rootResourcePath != null + ? request.rootResourcePath.match(/\.tex$/i) + : undefined + if (request.check != null && isLaTeXFile) { + env.CHKTEX_OPTIONS = '-nall -e9 -e10 -w15 -w16' + env.CHKTEX_ULIMIT_OPTIONS = '-t 5 -v 64000' + if (request.check === 'error') { + env.CHKTEX_EXIT_ON_ERROR = 1 + } + if (request.check === 'validate') { + env.CHKTEX_VALIDATE = 1 + } + } + + // apply a series of file modifications/creations for draft mode and tikz + return async.series( + [injectDraftModeIfRequired, createTikzFileIfRequired], + function (error) { + if (error != null) { + return callback(error) + } + timer = new Metrics.Timer('run-compile') + // find the image tag to log it as a metric, e.g. 2015.1 (convert . to - for graphite) + let tag = + __guard__( + __guard__( + request.imageName != null + ? request.imageName.match(/:(.*)/) + : undefined, + x1 => x1[1] + ), + x => x.replace(/\./g, '-') + ) || 'default' + if (!request.project_id.match(/^[0-9a-f]{24}$/)) { + tag = 'other' + } // exclude smoke test + Metrics.inc('compiles') + Metrics.inc(`compiles-with-image.${tag}`) + const compileName = getCompileName( + request.project_id, + request.user_id + ) + return LatexRunner.runLatex( + compileName, + { + directory: compileDir, + mainFile: request.rootResourcePath, + compiler: request.compiler, + timeout: request.timeout, + image: request.imageName, + flags: request.flags, + environment: env, + compileGroup: request.compileGroup, + }, + function (error, output, stats, timings) { + // request was for validation only + let metric_key, metric_value + if (request.check === 'validate') { + const result = (error != null ? error.code : undefined) + ? 'fail' + : 'pass' + error = new Error('validation') + error.validate = result + } + // request was for compile, and failed on validation + if ( + request.check === 'error' && + (error != null ? error.message : undefined) === 'exited' + ) { + error = new Error('compilation') + error.validate = 'fail' + } + // compile was killed by user, was a validation, or a compile which failed validation + if ( + (error != null ? error.terminated : undefined) || + (error != null ? error.validate : undefined) || + (error != null ? error.timedout : undefined) + ) { + OutputFileFinder.findOutputFiles( + resourceList, + compileDir, + function (err, outputFiles) { + if (err != null) { + return callback(err) + } + error.outputFiles = outputFiles // return output files so user can check logs + return callback(error) + } + ) + return + } + // compile completed normally + if (error != null) { + return callback(error) + } + Metrics.inc('compiles-succeeded') + stats = stats || {} + const object = stats || {} + for (metric_key in object) { + metric_value = object[metric_key] + Metrics.count(metric_key, metric_value) + } + timings = timings || {} + const object1 = timings || {} + for (metric_key in object1) { + metric_value = object1[metric_key] + Metrics.timing(metric_key, metric_value) + } + const loadavg = + typeof os.loadavg === 'function' ? os.loadavg() : undefined + if (loadavg != null) { + Metrics.gauge('load-avg', loadavg[0]) + } + const ts = timer.done() + logger.log( + { + project_id: request.project_id, + user_id: request.user_id, + time_taken: ts, + stats, + timings, + loadavg, + }, + 'done compile' + ) + if ((stats != null ? stats['latex-runs'] : undefined) > 0) { + Metrics.timing( + 'run-compile-per-pass', + ts / stats['latex-runs'] + ) + } + if ( + (stats != null ? stats['latex-runs'] : undefined) > 0 && + (timings != null ? timings['cpu-time'] : undefined) > 0 + ) { + Metrics.timing( + 'run-compile-cpu-time-per-pass', + timings['cpu-time'] / stats['latex-runs'] + ) + } + // Emit compile time. + timings.compile = ts + + timer = new Metrics.Timer('process-output-files') + + return OutputFileFinder.findOutputFiles( + resourceList, + compileDir, + function (error, outputFiles) { + if (error != null) { + return callback(error) + } + return OutputCacheManager.saveOutputFiles( + { request, stats, timings }, + outputFiles, + compileDir, + outputDir, + (err, newOutputFiles) => { + if (err) { + const { project_id: projectId, user_id: userId } = + request + logger.err( + { projectId, userId, err }, + 'failed to save output files' + ) + } + + const outputStage = timer.done() + timings.sync = syncStage + timings.output = outputStage + + // Emit e2e compile time. + timings.compileE2E = timerE2E.done() + + if (stats['pdf-size']) { + emitPdfStats(stats, timings) + } + + callback(null, newOutputFiles, stats, timings) + } + ) + } + ) + } + ) + } + ) + } + ) }, stopCompile(project_id, user_id, callback) { @@ -377,13 +379,13 @@ module.exports = CompileManager = { '-f', '--', compileDir, - outputDir + outputDir, ]) proc.on('error', callback) let stderr = '' - proc.stderr.setEncoding('utf8').on('data', (chunk) => (stderr += chunk)) + proc.stderr.setEncoding('utf8').on('data', chunk => (stderr += chunk)) return proc.on('close', function (code) { if (code === 0) { @@ -406,7 +408,7 @@ module.exports = CompileManager = { if (err != null) { return callback(err) } - const allDirs = Array.from(files).map((file) => Path.join(root, file)) + const allDirs = Array.from(files).map(file => Path.join(root, file)) return callback(null, allDirs) }) }, @@ -575,7 +577,7 @@ module.exports = CompileManager = { const timeout = 60 * 1000 // increased to allow for large projects const compileName = getCompileName(project_id, user_id) const compileGroup = 'synctex' - CompileManager._checkFileExists(directory, 'output.synctex.gz', (error) => { + CompileManager._checkFileExists(directory, 'output.synctex.gz', error => { if (error) { return callback(error) } @@ -614,7 +616,7 @@ module.exports = CompileManager = { h: parseFloat(h), v: parseFloat(v), height: parseFloat(height), - width: parseFloat(width) + width: parseFloat(width), }) } } @@ -631,7 +633,7 @@ module.exports = CompileManager = { results.push({ file, line: parseInt(line, 10), - column: parseInt(column, 10) + column: parseInt(column, 10), }) } } @@ -649,7 +651,7 @@ module.exports = CompileManager = { '-nocol', '-inc', file_path, - `-out=${file_path}.wc` + `-out=${file_path}.wc`, ] const compileDir = getCompileDir(project_id, user_id) const timeout = 60 * 1000 @@ -711,7 +713,7 @@ module.exports = CompileManager = { mathInline: 0, mathDisplay: 0, errors: 0, - messages: '' + messages: '', } for (const line of Array.from(output.split('\n'))) { const [data, info] = Array.from(line.split(':')) @@ -749,7 +751,7 @@ module.exports = CompileManager = { } } return results - } + }, } function __guard__(value, transform) { diff --git a/services/clsi/app/js/ContentCacheManager.js b/services/clsi/app/js/ContentCacheManager.js index 7dfc40f0b7..2036057064 100644 --- a/services/clsi/app/js/ContentCacheManager.js +++ b/services/clsi/app/js/ContentCacheManager.js @@ -76,14 +76,14 @@ async function update(contentDir, filePath, size, compileTime) { if (bytesRead !== object.size) { throw new OError('could not read full chunk', { object, - bytesRead + bytesRead, }) } const idxObj = buffer.indexOf('obj') if (idxObj > 100) { throw new OError('objectId is too large', { object, - idxObj + idxObj, }) } const objectIdRaw = buffer.subarray(0, idxObj) @@ -95,7 +95,7 @@ async function update(contentDir, filePath, size, compileTime) { objectId: objectIdRaw.toString(), start: object.offset + objectIdRaw.byteLength, end: object.endOffset, - hash + hash, } ranges.push(range) @@ -168,7 +168,7 @@ class HashFileTracker { const statePath = getStatePath(this.contentDir) const blob = JSON.stringify({ hashAge: Array.from(this.hashAge.entries()), - hashSize: Array.from(this.hashSize.entries()) + hashSize: Array.from(this.hashSize.entries()), }) const atomicWrite = statePath + '~' try { @@ -198,7 +198,7 @@ class HashFileTracker { return reclaimedSpace } - await promiseMapWithLimit(10, hashes, async (hash) => { + await promiseMapWithLimit(10, hashes, async hash => { await fs.promises.unlink(Path.join(this.contentDir, hash)) this.hashAge.delete(hash) reclaimedSpace += this.hashSize.get(hash) @@ -251,7 +251,7 @@ function getDeadlineChecker(compileTime) { throw new TimedOutError(stage, { completedStages, lastStage: lastStage.stage, - diffToLastStage: now - lastStage.now + diffToLastStage: now - lastStage.now, }) } completedStages++ @@ -261,13 +261,13 @@ function getDeadlineChecker(compileTime) { function promiseMapWithLimit(concurrency, array, fn) { const limit = pLimit(concurrency) - return Promise.all(array.map((x) => limit(() => fn(x)))) + return Promise.all(array.map(x => limit(() => fn(x)))) } module.exports = { HASH_REGEX: /^[0-9a-f]{64}$/, update: callbackify(update), promises: { - update - } + update, + }, } diff --git a/services/clsi/app/js/ContentCacheMetrics.js b/services/clsi/app/js/ContentCacheMetrics.js index 6b7a33de8a..3550de70e8 100644 --- a/services/clsi/app/js/ContentCacheMetrics.js +++ b/services/clsi/app/js/ContentCacheMetrics.js @@ -4,13 +4,13 @@ const os = require('os') let CACHED_LOAD = { expires: -1, - load: [0, 0, 0] + load: [0, 0, 0], } function getSystemLoad() { if (CACHED_LOAD.expires < Date.now()) { CACHED_LOAD = { expires: Date.now() + 10 * 1000, - load: os.loadavg() + load: os.loadavg(), } } return CACHED_LOAD.load @@ -47,7 +47,7 @@ function emitPdfCachingStats(stats, timings) { { stats, timings, - load: getSystemLoad() + load: getSystemLoad(), }, 'slow pdf caching' ) @@ -111,5 +111,5 @@ function emitPdfCachingStats(stats, timings) { } module.exports = { - emitPdfStats + emitPdfStats, } diff --git a/services/clsi/app/js/ContentTypeMapper.js b/services/clsi/app/js/ContentTypeMapper.js index f690bf9df7..6301dce489 100644 --- a/services/clsi/app/js/ContentTypeMapper.js +++ b/services/clsi/app/js/ContentTypeMapper.js @@ -34,5 +34,5 @@ module.exports = ContentTypeMapper = { default: return 'application/octet-stream' } - } + }, } diff --git a/services/clsi/app/js/DockerLockManager.js b/services/clsi/app/js/DockerLockManager.js index 0ed098565d..d785ee46cb 100644 --- a/services/clsi/app/js/DockerLockManager.js +++ b/services/clsi/app/js/DockerLockManager.js @@ -109,5 +109,5 @@ module.exports = LockManager = { }) ) }) - } + }, } diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index 2d0810d14f..5de0586818 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -52,7 +52,7 @@ const DockerRunner = { const volumes = { [directory]: '/compile' } - command = command.map((arg) => + command = command.map(arg => arg.toString().replace('$COMPILE_DIR', '/compile') ) if (image == null) { @@ -96,7 +96,7 @@ const DockerRunner = { { err: error, projectId }, 'error running container so destroying and retrying' ) - DockerRunner.destroyContainer(name, null, true, (error) => { + DockerRunner.destroyContainer(name, null, true, error => { if (error != null) { return callback(error) } @@ -120,7 +120,7 @@ const DockerRunner = { kill(containerId, callback) { logger.log({ containerId }, 'sending kill signal to container') const container = dockerode.getContainer(containerId) - container.kill((error) => { + container.kill(error => { if ( error != null && error.message != null && @@ -250,12 +250,12 @@ const DockerRunner = { { Name: 'cpu', Soft: timeoutInSeconds + 5, - Hard: timeoutInSeconds + 10 - } + Hard: timeoutInSeconds + 10, + }, ], CapDrop: 'ALL', - SecurityOpt: ['no-new-privileges'] - } + SecurityOpt: ['no-new-privileges'], + }, } if (Settings.path != null && Settings.path.synctexBinHostPath != null) { @@ -303,12 +303,12 @@ const DockerRunner = { startContainer(options, volumes, attachStreamHandler, callback) { LockManager.runWithLock( options.name, - (releaseLock) => + releaseLock => // Check that volumes exist before starting the container. // When a container is started with volume pointing to a // non-existent directory then docker creates the directory but // with root ownership. - DockerRunner._checkVolumes(options, volumes, (err) => { + DockerRunner._checkVolumes(options, volumes, err => { if (err != null) { return releaseLock(err) } @@ -343,7 +343,7 @@ const DockerRunner = { }) const jobs = [] for (const vol in volumes) { - jobs.push((cb) => checkVolume(vol, cb)) + jobs.push(cb => checkVolume(vol, cb)) } async.series(jobs, callback) }, @@ -368,11 +368,11 @@ const DockerRunner = { DockerRunner.attachToContainer( options.name, attachStreamHandler, - (error) => { + error => { if (error != null) { return callback(error) } - container.start((error) => { + container.start(error => { if (error != null && error.statusCode !== 304) { callback(error) } else { @@ -430,14 +430,14 @@ const DockerRunner = { { containerId, length: this.data.length, - maxLen: MAX_OUTPUT + maxLen: MAX_OUTPUT, }, `${name} exceeds max size` ) this.data += `(...truncated at ${MAX_OUTPUT} chars...)` this.overflowed = true } - } + }, // kill container if too much output // docker.containers.kill(containerId, () ->) } @@ -448,7 +448,7 @@ const DockerRunner = { container.modem.demuxStream(stream, stdout, stderr) - stream.on('error', (err) => + stream.on('error', err => logger.error( { err, containerId }, 'error reading from container stream' @@ -470,7 +470,7 @@ const DockerRunner = { const timeoutId = setTimeout(() => { timedOut = true logger.log({ containerId }, 'timeout reached, killing container') - container.kill((err) => { + container.kill(err => { logger.warn({ err, containerId }, 'failed to kill container') }) }, timeout) @@ -507,7 +507,7 @@ const DockerRunner = { // supplied. LockManager.runWithLock( containerName, - (releaseLock) => + releaseLock => DockerRunner._destroyContainer( containerId || containerName, shouldForce, @@ -520,7 +520,7 @@ const DockerRunner = { _destroyContainer(containerId, shouldForce, callback) { logger.log({ containerId }, 'destroying docker container') const container = dockerode.getContainer(containerId) - container.remove({ force: shouldForce === true, v: true }, (error) => { + container.remove({ force: shouldForce === true, v: true }, error => { if (error != null && error.statusCode === 404) { logger.warn( { err: error, containerId }, @@ -567,7 +567,7 @@ const DockerRunner = { // strip the / prefix // the LockManager uses the plain container name const plainName = name.slice(1) - jobs.push((cb) => + jobs.push(cb => DockerRunner.destroyContainer(plainName, id, false, () => cb()) ) } @@ -592,7 +592,7 @@ const DockerRunner = { containerMonitorTimeout = setTimeout(() => { containerMonitorInterval = setInterval( () => - DockerRunner.destroyOldContainers((err) => { + DockerRunner.destroyOldContainers(err => { if (err) { logger.error({ err }, 'failed to destroy old containers') } @@ -611,7 +611,7 @@ const DockerRunner = { clearInterval(containerMonitorInterval) containerMonitorInterval = undefined } - } + }, } DockerRunner.startContainerMonitor() diff --git a/services/clsi/app/js/DraftModeManager.js b/services/clsi/app/js/DraftModeManager.js index 0bdd40f047..9be65e7afd 100644 --- a/services/clsi/app/js/DraftModeManager.js +++ b/services/clsi/app/js/DraftModeManager.js @@ -37,7 +37,7 @@ module.exports = DraftModeManager = { { content: content.slice(0, 1024), // \documentclass is normally v near the top modified_content: modified_content.slice(0, 1024), - filename + filename, }, 'injected draft class' ) @@ -53,5 +53,5 @@ module.exports = DraftModeManager = { // Without existing options .replace(/\\documentclass\{/g, '\\documentclass[draft]{') ) - } + }, } diff --git a/services/clsi/app/js/Errors.js b/services/clsi/app/js/Errors.js index 0b16803414..6b66c23421 100644 --- a/services/clsi/app/js/Errors.js +++ b/services/clsi/app/js/Errors.js @@ -37,5 +37,5 @@ module.exports = Errors = { TimedOutError, NotFoundError, FilesOutOfSyncError, - AlreadyCompilingError + AlreadyCompilingError, } diff --git a/services/clsi/app/js/LatexRunner.js b/services/clsi/app/js/LatexRunner.js index 7cd67a0396..7c288cef08 100644 --- a/services/clsi/app/js/LatexRunner.js +++ b/services/clsi/app/js/LatexRunner.js @@ -26,7 +26,7 @@ const ProcessTable = {} // table of currently running jobs (pids or docker conta const TIME_V_METRICS = Object.entries({ 'cpu-percent': /Percent of CPU this job got: (\d+)/m, 'cpu-time': /User time.*: (\d+.\d+)/m, - 'sys-time': /System time.*: (\d+.\d+)/m + 'sys-time': /System time.*: (\d+.\d+)/m, }) module.exports = LatexRunner = { @@ -43,7 +43,7 @@ module.exports = LatexRunner = { image, environment, flags, - compileGroup + compileGroup, } = options if (!compiler) { compiler = 'pdflatex' @@ -60,7 +60,7 @@ module.exports = LatexRunner = { mainFile, environment, flags, - compileGroup + compileGroup, }, 'starting compile' ) @@ -102,13 +102,13 @@ module.exports = LatexRunner = { } const runs = __guard__( - __guard__(output != null ? output.stderr : undefined, (x1) => + __guard__(output != null ? output.stderr : undefined, x1 => x1.match(/^Run number \d+ of .*latex/gm) ), - (x) => x.length + x => x.length ) || 0 const failed = - __guard__(output != null ? output.stdout : undefined, (x2) => + __guard__(output != null ? output.stdout : undefined, x2 => x2.match(/^Latexmk: Errors/m) ) != null ? 1 @@ -147,7 +147,7 @@ module.exports = LatexRunner = { // internal method for writing non-empty log files function _writeFile(file, content, cb) { if (content && content.length > 0) { - fs.writeFile(file, content, (err) => { + fs.writeFile(file, content, err => { if (err) { logger.error({ project_id, file }, 'error writing log file') // don't fail on error } @@ -188,7 +188,7 @@ module.exports = LatexRunner = { '-auxdir=$COMPILE_DIR', '-outdir=$COMPILE_DIR', '-synctex=1', - '-interaction=batchmode' + '-interaction=batchmode', ] if (flags) { args = args.concat(flags) @@ -196,7 +196,7 @@ module.exports = LatexRunner = { return ( __guard__( Settings != null ? Settings.clsi : undefined, - (x) => x.latexmkCommandPrefix + x => x.latexmkCommandPrefix ) || [] ).concat(args) }, @@ -204,30 +204,30 @@ module.exports = LatexRunner = { _pdflatexCommand(mainFile, flags) { return LatexRunner._latexmkBaseCommand(flags).concat([ '-pdf', - Path.join('$COMPILE_DIR', mainFile) + Path.join('$COMPILE_DIR', mainFile), ]) }, _latexCommand(mainFile, flags) { return LatexRunner._latexmkBaseCommand(flags).concat([ '-pdfdvi', - Path.join('$COMPILE_DIR', mainFile) + Path.join('$COMPILE_DIR', mainFile), ]) }, _xelatexCommand(mainFile, flags) { return LatexRunner._latexmkBaseCommand(flags).concat([ '-xelatex', - Path.join('$COMPILE_DIR', mainFile) + Path.join('$COMPILE_DIR', mainFile), ]) }, _lualatexCommand(mainFile, flags) { return LatexRunner._latexmkBaseCommand(flags).concat([ '-lualatex', - Path.join('$COMPILE_DIR', mainFile) + Path.join('$COMPILE_DIR', mainFile), ]) - } + }, } function __guard__(value, transform) { diff --git a/services/clsi/app/js/LocalCommandRunner.js b/services/clsi/app/js/LocalCommandRunner.js index d5fd3090a7..1e4236a579 100644 --- a/services/clsi/app/js/LocalCommandRunner.js +++ b/services/clsi/app/js/LocalCommandRunner.js @@ -37,7 +37,7 @@ module.exports = CommandRunner = { } else { callback = _.once(callback) } - command = Array.from(command).map((arg) => + command = Array.from(command).map(arg => arg.toString().replace('$COMPILE_DIR', directory) ) logger.log({ project_id, command, directory }, 'running command') @@ -58,7 +58,7 @@ module.exports = CommandRunner = { const proc = spawn(command[0], command.slice(1), { cwd: directory, env }) let stdout = '' - proc.stdout.setEncoding('utf8').on('data', (data) => (stdout += data)) + proc.stdout.setEncoding('utf8').on('data', data => (stdout += data)) proc.on('error', function (err) { logger.err( @@ -99,5 +99,5 @@ module.exports = CommandRunner = { return callback(err) } return callback() - } + }, } diff --git a/services/clsi/app/js/LockManager.js b/services/clsi/app/js/LockManager.js index a3bdf1bcfc..2dc09b336c 100644 --- a/services/clsi/app/js/LockManager.js +++ b/services/clsi/app/js/LockManager.js @@ -30,7 +30,7 @@ module.exports = LockManager = { const lockOpts = { wait: this.MAX_LOCK_WAIT_TIME, pollPeriod: this.LOCK_TEST_INTERVAL, - stale: this.LOCK_STALE + stale: this.LOCK_STALE, } return Lockfile.lock(path, lockOpts, function (error) { if ((error != null ? error.code : undefined) === 'EEXIST') { @@ -48,7 +48,7 @@ module.exports = LockManager = { statDir, statDirErr, readdirErr, - readdirDir + readdirDir, }, 'unable to get lock' ) @@ -68,5 +68,5 @@ module.exports = LockManager = { ) } }) - } + }, } diff --git a/services/clsi/app/js/OutputCacheManager.js b/services/clsi/app/js/OutputCacheManager.js index 682b85aa1a..af85f49661 100644 --- a/services/clsi/app/js/OutputCacheManager.js +++ b/services/clsi/app/js/OutputCacheManager.js @@ -180,38 +180,42 @@ module.exports = OutputCacheManager = { const newFile = _.clone(file) const [src, dst] = Array.from([ Path.join(compileDir, file.path), - Path.join(cacheDir, file.path) + Path.join(cacheDir, file.path), ]) - return OutputCacheManager._checkFileIsSafe(src, function ( - err, - isSafe - ) { - if (err != null) { - return cb(err) - } - if (!isSafe) { - return cb() - } - return OutputCacheManager._checkIfShouldCopy(src, function ( - err, - shouldCopy - ) { + return OutputCacheManager._checkFileIsSafe( + src, + function (err, isSafe) { if (err != null) { return cb(err) } - if (!shouldCopy) { + if (!isSafe) { return cb() } - return OutputCacheManager._copyFile(src, dst, function (err) { - if (err != null) { - return cb(err) + return OutputCacheManager._checkIfShouldCopy( + src, + function (err, shouldCopy) { + if (err != null) { + return cb(err) + } + if (!shouldCopy) { + return cb() + } + return OutputCacheManager._copyFile( + src, + dst, + function (err) { + if (err != null) { + return cb(err) + } + newFile.build = buildId // attach a build id if we cached the file + results.push(newFile) + return cb() + } + ) } - newFile.build = buildId // attach a build id if we cached the file - results.push(newFile) - return cb() - }) - }) - }) + ) + } + ) }, function (err) { if (err != null) { @@ -232,7 +236,7 @@ module.exports = OutputCacheManager = { // let file expiry run in the background, expire all previous files if per-user return OutputCacheManager.expireOutputFiles(cacheRoot, { keep: buildId, - limit: perUser ? 1 : null + limit: perUser ? 1 : null, }) } } @@ -242,7 +246,7 @@ module.exports = OutputCacheManager = { }, collectOutputPdfSize(outputFiles, outputDir, stats, callback) { - const outputFile = outputFiles.find((x) => x.path === 'output.pdf') + const outputFile = outputFiles.find(x => x.path === 'output.pdf') if (!outputFile) return callback(null, outputFiles) const outputFilePath = Path.join( outputDir, @@ -269,7 +273,7 @@ module.exports = OutputCacheManager = { OutputCacheManager.ensureContentDir(cacheRoot, function (err, contentDir) { if (err) return callback(err, outputFiles) - const outputFile = outputFiles.find((x) => x.path === 'output.pdf') + const outputFile = outputFiles.find(x => x.path === 'output.pdf') if (outputFile) { // possibly we should copy the file from the build dir here const outputFilePath = Path.join( @@ -331,7 +335,7 @@ module.exports = OutputCacheManager = { } fs.readdir(contentRoot, function (err, results) { const dirs = results.sort() - const contentId = dirs.find((dir) => + const contentId = dirs.find(dir => OutputCacheManager.BUILD_REGEX.test(dir) ) if (contentId) { @@ -374,31 +378,31 @@ module.exports = OutputCacheManager = { function (file, cb) { const [src, dst] = Array.from([ Path.join(compileDir, file.path), - Path.join(archiveDir, file.path) + Path.join(archiveDir, file.path), ]) - return OutputCacheManager._checkFileIsSafe(src, function ( - err, - isSafe - ) { - if (err != null) { - return cb(err) - } - if (!isSafe) { - return cb() - } - return OutputCacheManager._checkIfShouldArchive(src, function ( - err, - shouldArchive - ) { + return OutputCacheManager._checkFileIsSafe( + src, + function (err, isSafe) { if (err != null) { return cb(err) } - if (!shouldArchive) { + if (!isSafe) { return cb() } - return OutputCacheManager._copyFile(src, dst, cb) - }) - }) + return OutputCacheManager._checkIfShouldArchive( + src, + function (err, shouldArchive) { + if (err != null) { + return cb(err) + } + if (!shouldArchive) { + return cb() + } + return OutputCacheManager._copyFile(src, dst, cb) + } + ) + } + ) }, callback ) @@ -440,7 +444,7 @@ module.exports = OutputCacheManager = { // we can get the build time from the first part of the directory name DDDD-RRRR // DDDD is date and RRRR is random bytes const dirTime = parseInt( - __guard__(dir.split('-'), (x) => x[0]), + __guard__(dir.split('-'), x => x[0]), 16 ) const age = currentTime - dirTime @@ -549,7 +553,7 @@ module.exports = OutputCacheManager = { return callback(null, true) } return callback(null, false) - } + }, } function __guard__(value, transform) { diff --git a/services/clsi/app/js/OutputFileFinder.js b/services/clsi/app/js/OutputFileFinder.js index d9d2499699..9088215d1a 100644 --- a/services/clsi/app/js/OutputFileFinder.js +++ b/services/clsi/app/js/OutputFileFinder.js @@ -6,9 +6,7 @@ const logger = require('logger-sharelatex') module.exports = OutputFileFinder = { findOutputFiles(resources, directory, callback) { - const incomingResources = new Set( - resources.map((resource) => resource.path) - ) + const incomingResources = new Set(resources.map(resource => resource.path)) OutputFileFinder._getAllFiles(directory, function (error, allFiles) { if (allFiles == null) { @@ -23,7 +21,7 @@ module.exports = OutputFileFinder = { if (!incomingResources.has(file)) { outputFiles.push({ path: file, - type: Path.extname(file).replace(/^\./, '') || undefined + type: Path.extname(file).replace(/^\./, '') || undefined, }) } } @@ -42,7 +40,7 @@ module.exports = OutputFileFinder = { '.archive', '-o', '-name', - '.project-*' + '.project-*', ] const args = [ directory, @@ -53,13 +51,13 @@ module.exports = OutputFileFinder = { '-o', '-type', 'f', - '-print' + '-print', ] logger.log({ args }, 'running find command') const proc = spawn('find', args) let stdout = '' - proc.stdout.setEncoding('utf8').on('data', (chunk) => (stdout += chunk)) + proc.stdout.setEncoding('utf8').on('data', chunk => (stdout += chunk)) proc.on('error', callback) proc.on('close', function (code) { if (code !== 0) { @@ -76,5 +74,5 @@ module.exports = OutputFileFinder = { }) callback(null, fileList) }) - } + }, } diff --git a/services/clsi/app/js/OutputFileOptimiser.js b/services/clsi/app/js/OutputFileOptimiser.js index 0b835a42c3..979697b07c 100644 --- a/services/clsi/app/js/OutputFileOptimiser.js +++ b/services/clsi/app/js/OutputFileOptimiser.js @@ -29,15 +29,15 @@ module.exports = OutputFileOptimiser = { callback = function (error) {} } if (src.match(/\/output\.pdf$/)) { - return OutputFileOptimiser.checkIfPDFIsOptimised(src, function ( - err, - isOptimised - ) { - if (err != null || isOptimised) { - return callback(null) + return OutputFileOptimiser.checkIfPDFIsOptimised( + src, + function (err, isOptimised) { + if (err != null || isOptimised) { + return callback(null) + } + return OutputFileOptimiser.optimisePDF(src, dst, callback) } - return OutputFileOptimiser.optimisePDF(src, dst, callback) - }) + ) } else { return callback(null) } @@ -77,7 +77,7 @@ module.exports = OutputFileOptimiser = { const timer = new Metrics.Timer('qpdf') const proc = spawn('qpdf', args) let stdout = '' - proc.stdout.setEncoding('utf8').on('data', (chunk) => (stdout += chunk)) + proc.stdout.setEncoding('utf8').on('data', chunk => (stdout += chunk)) callback = _.once(callback) // avoid double call back for error and close event proc.on('error', function (err) { logger.warn({ err, args }, 'qpdf failed') @@ -99,5 +99,5 @@ module.exports = OutputFileOptimiser = { return callback(null) }) }) - } // ignore the error + }, // ignore the error } diff --git a/services/clsi/app/js/ProjectPersistenceManager.js b/services/clsi/app/js/ProjectPersistenceManager.js index 814f7e9916..c4abb69b6e 100644 --- a/services/clsi/app/js/ProjectPersistenceManager.js +++ b/services/clsi/app/js/ProjectPersistenceManager.js @@ -28,7 +28,7 @@ async function refreshExpiryTimeout() { const paths = [ Settings.path.compilesDir, Settings.path.outputDir, - Settings.path.clsiCacheDir + Settings.path.clsiCacheDir, ] for (const path of paths) { try { @@ -40,7 +40,7 @@ async function refreshExpiryTimeout() { logger.warn( { stats, - newExpiryTimeoutInDays: (lowerExpiry / oneDay).toFixed(2) + newExpiryTimeoutInDays: (lowerExpiry / oneDay).toFixed(2), }, 'disk running low on space, modifying EXPIRY_TIMEOUT' ) @@ -57,7 +57,7 @@ module.exports = ProjectPersistenceManager = { EXPIRY_TIMEOUT: Settings.project_cache_length_ms || oneDay * 2.5, promises: { - refreshExpiryTimeout + refreshExpiryTimeout, }, refreshExpiryTimeout: callbackify(refreshExpiryTimeout), @@ -66,7 +66,7 @@ module.exports = ProjectPersistenceManager = { callback = function (error) {} } const timer = new Metrics.Timer('db-bump-last-accessed') - const job = (cb) => + const job = cb => db.Project.findOrCreate({ where: { project_id } }) .spread((project, created) => project @@ -75,7 +75,7 @@ module.exports = ProjectPersistenceManager = { .error(cb) ) .error(cb) - dbQueue.queue.push(job, (error) => { + dbQueue.queue.push(job, error => { timer.done() callback(error) }) @@ -93,16 +93,19 @@ module.exports = ProjectPersistenceManager = { return callback(error) } logger.log({ project_ids }, 'clearing expired projects') - const jobs = Array.from(project_ids || []).map((project_id) => - ((project_id) => (callback) => - ProjectPersistenceManager.clearProjectFromCache(project_id, function ( - err - ) { - if (err != null) { - logger.error({ err, project_id }, 'error clearing project') - } - return callback() - }))(project_id) + const jobs = Array.from(project_ids || []).map(project_id => + ( + project_id => callback => + ProjectPersistenceManager.clearProjectFromCache( + project_id, + function (err) { + if (err != null) { + logger.error({ err, project_id }, 'error clearing project') + } + return callback() + } + ) + )(project_id) ) return async.series(jobs, function (error) { if (error != null) { @@ -110,7 +113,7 @@ module.exports = ProjectPersistenceManager = { } return CompileManager.clearExpiredProjects( ProjectPersistenceManager.EXPIRY_TIMEOUT, - (error) => callback() + error => callback() ) }) }) @@ -167,7 +170,7 @@ module.exports = ProjectPersistenceManager = { callback = function (error) {} } logger.log({ project_id }, 'clearing project from database') - const job = (cb) => + const job = cb => db.Project.destroy({ where: { project_id } }) .then(() => cb()) .error(cb) @@ -185,17 +188,17 @@ module.exports = ProjectPersistenceManager = { const q = {} q[db.op.lt] = keepProjectsFrom return db.Project.findAll({ where: { lastAccessed: q } }) - .then((projects) => + .then(projects => cb( null, - projects.map((project) => project.project_id) + projects.map(project => project.project_id) ) ) .error(cb) } return dbQueue.queue.push(job, callback) - } + }, } logger.log( diff --git a/services/clsi/app/js/RequestParser.js b/services/clsi/app/js/RequestParser.js index 702cf5593c..cb097f64f4 100644 --- a/services/clsi/app/js/RequestParser.js +++ b/services/clsi/app/js/RequestParser.js @@ -47,7 +47,7 @@ module.exports = RequestParser = { { validValues: this.VALID_COMPILERS, default: 'pdflatex', - type: 'string' + type: 'string', } ) response.enablePdfCaching = this._parseAttribute( @@ -55,7 +55,7 @@ module.exports = RequestParser = { compile.options.enablePdfCaching, { default: false, - type: 'boolean' + type: 'boolean', } ) response.timeout = this._parseAttribute( @@ -63,7 +63,7 @@ module.exports = RequestParser = { compile.options.timeout, { default: RequestParser.MAX_TIMEOUT, - type: 'number' + type: 'number', } ) response.imageName = this._parseAttribute( @@ -74,19 +74,19 @@ module.exports = RequestParser = { validValues: settings.clsi && settings.clsi.docker && - settings.clsi.docker.allowedImages + settings.clsi.docker.allowedImages, } ) response.draft = this._parseAttribute('draft', compile.options.draft, { default: false, - type: 'boolean' + type: 'boolean', }) response.check = this._parseAttribute('check', compile.options.check, { - type: 'string' + type: 'string', }) response.flags = this._parseAttribute('flags', compile.options.flags, { default: [], - type: 'object' + type: 'object', }) if (settings.allowedCompileGroups) { response.compileGroup = this._parseAttribute( @@ -95,7 +95,7 @@ module.exports = RequestParser = { { validValues: settings.allowedCompileGroups, default: '', - type: 'string' + type: 'string', } ) } @@ -107,7 +107,7 @@ module.exports = RequestParser = { compile.options.syncType, { validValues: ['full', 'incremental'], - type: 'string' + type: 'string', } ) @@ -144,13 +144,12 @@ module.exports = RequestParser = { compile.rootResourcePath, { default: 'main.tex', - type: 'string' + type: 'string', } ) const originalRootResourcePath = rootResourcePath - const sanitizedRootResourcePath = RequestParser._sanitizePath( - rootResourcePath - ) + const sanitizedRootResourcePath = + RequestParser._sanitizePath(rootResourcePath) response.rootResourcePath = RequestParser._checkPath( sanitizedRootResourcePath ) @@ -195,7 +194,7 @@ module.exports = RequestParser = { path: resource.path, modified, url: resource.url, - content: resource.content + content: resource.content, } }, @@ -237,5 +236,5 @@ module.exports = RequestParser = { } } return path - } + }, } diff --git a/services/clsi/app/js/ResourceStateManager.js b/services/clsi/app/js/ResourceStateManager.js index e36f19ed67..7ae3557d51 100644 --- a/services/clsi/app/js/ResourceStateManager.js +++ b/services/clsi/app/js/ResourceStateManager.js @@ -36,7 +36,7 @@ module.exports = { }) } else { logger.log({ state, basePath }, 'writing sync state') - const resourceList = resources.map((resource) => resource.path) + const resourceList = resources.map(resource => resource.path) fs.writeFile( stateFile, [...resourceList, `stateHash:${state}`].join('\n'), @@ -48,43 +48,46 @@ module.exports = { checkProjectStateMatches(state, basePath, callback) { const stateFile = Path.join(basePath, this.SYNC_STATE_FILE) const size = this.SYNC_STATE_MAX_SIZE - SafeReader.readFile(stateFile, size, 'utf8', function ( - err, - result, - bytesRead - ) { - if (err) { - return callback(err) - } - if (bytesRead === size) { - logger.error( - { file: stateFile, size, bytesRead }, - 'project state file truncated' + SafeReader.readFile( + stateFile, + size, + 'utf8', + function (err, result, bytesRead) { + if (err) { + return callback(err) + } + if (bytesRead === size) { + logger.error( + { file: stateFile, size, bytesRead }, + 'project state file truncated' + ) + } + const array = result ? result.toString().split('\n') : [] + const adjustedLength = Math.max(array.length, 1) + const resourceList = array.slice(0, adjustedLength - 1) + const oldState = array[adjustedLength - 1] + const newState = `stateHash:${state}` + logger.log( + { state, oldState, basePath, stateMatches: newState === oldState }, + 'checking sync state' ) + if (newState !== oldState) { + return callback( + new Errors.FilesOutOfSyncError( + 'invalid state for incremental update' + ) + ) + } else { + const resources = resourceList.map(path => ({ path })) + callback(null, resources) + } } - const array = result ? result.toString().split('\n') : [] - const adjustedLength = Math.max(array.length, 1) - const resourceList = array.slice(0, adjustedLength - 1) - const oldState = array[adjustedLength - 1] - const newState = `stateHash:${state}` - logger.log( - { state, oldState, basePath, stateMatches: newState === oldState }, - 'checking sync state' - ) - if (newState !== oldState) { - return callback( - new Errors.FilesOutOfSyncError('invalid state for incremental update') - ) - } else { - const resources = resourceList.map((path) => ({ path })) - callback(null, resources) - } - }) + ) }, checkResourceFiles(resources, allFiles, basePath, callback) { // check the paths are all relative to current directory - const containsRelativePath = (resource) => { + const containsRelativePath = resource => { const dirs = resource.path.split('/') return dirs.indexOf('..') !== -1 } @@ -94,8 +97,8 @@ module.exports = { // check if any of the input files are not present in list of files const seenFiles = new Set(allFiles) const missingFiles = resources - .map((resource) => resource.path) - .filter((path) => !seenFiles.has(path)) + .map(resource => resource.path) + .filter(path => !seenFiles.has(path)) if (missingFiles.length > 0) { logger.err( { missingFiles, basePath, allFiles, resources }, @@ -109,5 +112,5 @@ module.exports = { } else { callback() } - } + }, } diff --git a/services/clsi/app/js/ResourceWriter.js b/services/clsi/app/js/ResourceWriter.js index 25f1b979de..d7cdc5027f 100644 --- a/services/clsi/app/js/ResourceWriter.js +++ b/services/clsi/app/js/ResourceWriter.js @@ -109,13 +109,13 @@ module.exports = ResourceWriter = { if (callback == null) { callback = function (error) {} } - return this._createDirectory(basePath, (error) => { + return this._createDirectory(basePath, error => { if (error != null) { return callback(error) } - const jobs = Array.from(resources).map((resource) => - ((resource) => { - return (callback) => + const jobs = Array.from(resources).map(resource => + (resource => { + return callback => this._writeResourceToDisk(project_id, resource, basePath, callback) })(resource) ) @@ -127,17 +127,17 @@ module.exports = ResourceWriter = { if (callback == null) { callback = function (error) {} } - return this._createDirectory(basePath, (error) => { + return this._createDirectory(basePath, error => { if (error != null) { return callback(error) } - return this._removeExtraneousFiles(resources, basePath, (error) => { + return this._removeExtraneousFiles(resources, basePath, error => { if (error != null) { return callback(error) } - const jobs = Array.from(resources).map((resource) => - ((resource) => { - return (callback) => + const jobs = Array.from(resources).map(resource => + (resource => { + return callback => this._writeResourceToDisk( project_id, resource, @@ -179,86 +179,86 @@ module.exports = ResourceWriter = { return _callback(error, ...Array.from(result)) } - return OutputFileFinder.findOutputFiles(resources, basePath, function ( - error, - outputFiles, - allFiles - ) { - if (error != null) { - return callback(error) - } - - const jobs = [] - for (const file of Array.from(outputFiles || [])) { - ;(function (file) { - const { path } = file - let should_delete = true - if ( - path.match(/^output\./) || - path.match(/\.aux$/) || - path.match(/^cache\//) - ) { - // knitr cache - should_delete = false - } - if (path.match(/^output-.*/)) { - // Tikz cached figures (default case) - should_delete = false - } - if (path.match(/\.(pdf|dpth|md5)$/)) { - // Tikz cached figures (by extension) - should_delete = false - } - if ( - path.match(/\.(pygtex|pygstyle)$/) || - path.match(/(^|\/)_minted-[^\/]+\//) - ) { - // minted files/directory - should_delete = false - } - if ( - path.match(/\.md\.tex$/) || - path.match(/(^|\/)_markdown_[^\/]+\//) - ) { - // markdown files/directory - should_delete = false - } - if (path.match(/-eps-converted-to\.pdf$/)) { - // Epstopdf generated files - should_delete = false - } - if ( - path === 'output.pdf' || - path === 'output.dvi' || - path === 'output.log' || - path === 'output.xdv' || - path === 'output.stdout' || - path === 'output.stderr' - ) { - should_delete = true - } - if (path === 'output.tex') { - // created by TikzManager if present in output files - should_delete = true - } - if (should_delete) { - return jobs.push((callback) => - ResourceWriter._deleteFileIfNotDirectory( - Path.join(basePath, path), - callback - ) - ) - } - })(file) - } - - return async.series(jobs, function (error) { + return OutputFileFinder.findOutputFiles( + resources, + basePath, + function (error, outputFiles, allFiles) { if (error != null) { return callback(error) } - return callback(null, outputFiles, allFiles) - }) - }) + + const jobs = [] + for (const file of Array.from(outputFiles || [])) { + ;(function (file) { + const { path } = file + let should_delete = true + if ( + path.match(/^output\./) || + path.match(/\.aux$/) || + path.match(/^cache\//) + ) { + // knitr cache + should_delete = false + } + if (path.match(/^output-.*/)) { + // Tikz cached figures (default case) + should_delete = false + } + if (path.match(/\.(pdf|dpth|md5)$/)) { + // Tikz cached figures (by extension) + should_delete = false + } + if ( + path.match(/\.(pygtex|pygstyle)$/) || + path.match(/(^|\/)_minted-[^\/]+\//) + ) { + // minted files/directory + should_delete = false + } + if ( + path.match(/\.md\.tex$/) || + path.match(/(^|\/)_markdown_[^\/]+\//) + ) { + // markdown files/directory + should_delete = false + } + if (path.match(/-eps-converted-to\.pdf$/)) { + // Epstopdf generated files + should_delete = false + } + if ( + path === 'output.pdf' || + path === 'output.dvi' || + path === 'output.log' || + path === 'output.xdv' || + path === 'output.stdout' || + path === 'output.stderr' + ) { + should_delete = true + } + if (path === 'output.tex') { + // created by TikzManager if present in output files + should_delete = true + } + if (should_delete) { + return jobs.push(callback => + ResourceWriter._deleteFileIfNotDirectory( + Path.join(basePath, path), + callback + ) + ) + } + })(file) + } + + return async.series(jobs, function (error) { + if (error != null) { + return callback(error) + } + return callback(null, outputFiles, allFiles) + }) + } + ) }, _deleteFileIfNotDirectory(path, callback) { @@ -296,48 +296,51 @@ module.exports = ResourceWriter = { if (callback == null) { callback = function (error) {} } - return ResourceWriter.checkPath(basePath, resource.path, function ( - error, - path - ) { - if (error != null) { - return callback(error) - } - return fs.mkdir(Path.dirname(path), { recursive: true }, function ( - error - ) { + return ResourceWriter.checkPath( + basePath, + resource.path, + function (error, path) { if (error != null) { return callback(error) } - // TODO: Don't overwrite file if it hasn't been modified - if (resource.url != null) { - return UrlCache.downloadUrlToFile( - project_id, - resource.url, - path, - resource.modified, - function (err) { - if (err != null) { - logger.err( - { - err, - project_id, - path, - resource_url: resource.url, - modified: resource.modified - }, - 'error downloading file for resources' - ) - Metrics.inc('download-failed') - } - return callback() + return fs.mkdir( + Path.dirname(path), + { recursive: true }, + function (error) { + if (error != null) { + return callback(error) } - ) // try and continue compiling even if http resource can not be downloaded at this time - } else { - fs.writeFile(path, resource.content, callback) - } - }) - }) + // TODO: Don't overwrite file if it hasn't been modified + if (resource.url != null) { + return UrlCache.downloadUrlToFile( + project_id, + resource.url, + path, + resource.modified, + function (err) { + if (err != null) { + logger.err( + { + err, + project_id, + path, + resource_url: resource.url, + modified: resource.modified, + }, + 'error downloading file for resources' + ) + Metrics.inc('download-failed') + } + return callback() + } + ) // try and continue compiling even if http resource can not be downloaded at this time + } else { + fs.writeFile(path, resource.content, callback) + } + } + ) + } + ) }, checkPath(basePath, resourcePath, callback) { @@ -347,5 +350,5 @@ module.exports = ResourceWriter = { } else { return callback(null, path) } - } + }, } diff --git a/services/clsi/app/js/SafeReader.js b/services/clsi/app/js/SafeReader.js index f3188791b1..760a7725b9 100644 --- a/services/clsi/app/js/SafeReader.js +++ b/services/clsi/app/js/SafeReader.js @@ -44,17 +44,20 @@ module.exports = SafeReader = { return callback(null, ...Array.from(result)) }) const buff = Buffer.alloc(size) // fills with zeroes by default - return fs.read(fd, buff, 0, buff.length, 0, function ( - err, - bytesRead, - buffer - ) { - if (err != null) { - return callbackWithClose(err) + return fs.read( + fd, + buff, + 0, + buff.length, + 0, + function (err, bytesRead, buffer) { + if (err != null) { + return callbackWithClose(err) + } + const result = buffer.toString(encoding, 0, bytesRead) + return callbackWithClose(null, result, bytesRead) } - const result = buffer.toString(encoding, 0, bytesRead) - return callbackWithClose(null, result, bytesRead) - }) + ) }) - } + }, } diff --git a/services/clsi/app/js/StaticServerForbidSymlinks.js b/services/clsi/app/js/StaticServerForbidSymlinks.js index bcfc5015b3..f810d2b8ab 100644 --- a/services/clsi/app/js/StaticServerForbidSymlinks.js +++ b/services/clsi/app/js/StaticServerForbidSymlinks.js @@ -26,7 +26,7 @@ module.exports = ForbidSymlinks = function (staticFn, root, options) { const basePath = Path.resolve(root) return function (req, res, next) { let file, project_id, result - const path = __guard__(url.parse(req.url), (x) => x.pathname) + const path = __guard__(url.parse(req.url), x => x.pathname) // check that the path is of the form /project_id_or_name/path/to/file.log if ((result = path.match(/^\/?([a-zA-Z0-9_-]+)\/(.*)/))) { project_id = result[1] @@ -63,7 +63,7 @@ module.exports = ForbidSymlinks = function (staticFn, root, options) { requestedFsPath, realFsPath, path: req.params[0], - project_id: req.params.project_id + project_id: req.params.project_id, }, 'error checking file access' ) @@ -75,7 +75,7 @@ module.exports = ForbidSymlinks = function (staticFn, root, options) { requestedFsPath, realFsPath, path: req.params[0], - project_id: req.params.project_id + project_id: req.params.project_id, }, 'trying to access a different file (symlink), aborting' ) diff --git a/services/clsi/app/js/TikzManager.js b/services/clsi/app/js/TikzManager.js index 0e39897d60..ac62ddac5a 100644 --- a/services/clsi/app/js/TikzManager.js +++ b/services/clsi/app/js/TikzManager.js @@ -35,63 +35,67 @@ module.exports = TikzManager = { } } // if there's no output.tex, see if we are using tikz/pgf or pstool in the main file - return ResourceWriter.checkPath(compileDir, mainFile, function ( - error, - path - ) { - if (error != null) { - return callback(error) - } - return SafeReader.readFile(path, 65536, 'utf8', function ( - error, - content - ) { + return ResourceWriter.checkPath( + compileDir, + mainFile, + function (error, path) { if (error != null) { return callback(error) } - const usesTikzExternalize = - (content != null - ? content.indexOf('\\tikzexternalize') - : undefined) >= 0 - const usesPsTool = - (content != null ? content.indexOf('{pstool}') : undefined) >= 0 - logger.log( - { compileDir, mainFile, usesTikzExternalize, usesPsTool }, - 'checked for packages needing main file as output.tex' + return SafeReader.readFile( + path, + 65536, + 'utf8', + function (error, content) { + if (error != null) { + return callback(error) + } + const usesTikzExternalize = + (content != null + ? content.indexOf('\\tikzexternalize') + : undefined) >= 0 + const usesPsTool = + (content != null ? content.indexOf('{pstool}') : undefined) >= 0 + logger.log( + { compileDir, mainFile, usesTikzExternalize, usesPsTool }, + 'checked for packages needing main file as output.tex' + ) + const needsMainFile = usesTikzExternalize || usesPsTool + return callback(null, needsMainFile) + } ) - const needsMainFile = usesTikzExternalize || usesPsTool - return callback(null, needsMainFile) - }) - }) + } + ) }, injectOutputFile(compileDir, mainFile, callback) { if (callback == null) { callback = function (error) {} } - return ResourceWriter.checkPath(compileDir, mainFile, function ( - error, - path - ) { - if (error != null) { - return callback(error) - } - return fs.readFile(path, 'utf8', function (error, content) { + return ResourceWriter.checkPath( + compileDir, + mainFile, + function (error, path) { if (error != null) { return callback(error) } - logger.log( - { compileDir, mainFile }, - 'copied file to output.tex as project uses packages which require it' - ) - // use wx flag to ensure that output file does not already exist - return fs.writeFile( - Path.join(compileDir, 'output.tex'), - content, - { flag: 'wx' }, - callback - ) - }) - }) - } + return fs.readFile(path, 'utf8', function (error, content) { + if (error != null) { + return callback(error) + } + logger.log( + { compileDir, mainFile }, + 'copied file to output.tex as project uses packages which require it' + ) + // use wx flag to ensure that output file does not already exist + return fs.writeFile( + Path.join(compileDir, 'output.tex'), + content, + { flag: 'wx' }, + callback + ) + }) + } + ) + }, } diff --git a/services/clsi/app/js/UrlCache.js b/services/clsi/app/js/UrlCache.js index b6378a5504..70535b6155 100644 --- a/services/clsi/app/js/UrlCache.js +++ b/services/clsi/app/js/UrlCache.js @@ -65,17 +65,19 @@ module.exports = UrlCache = { if (error != null) { return callback(error) } - const jobs = Array.from(urls || []).map((url) => - ((url) => (callback) => - UrlCache._clearUrlFromCache(project_id, url, function (error) { - if (error != null) { - logger.error( - { err: error, project_id, url }, - 'error clearing project URL' - ) - } - return callback() - }))(url) + const jobs = Array.from(urls || []).map(url => + ( + url => callback => + UrlCache._clearUrlFromCache(project_id, url, function (error) { + if (error != null) { + logger.error( + { err: error, project_id, url }, + 'error clearing project URL' + ) + } + return callback() + }) + )(url) ) return async.series(jobs, callback) }) @@ -103,7 +105,7 @@ module.exports = UrlCache = { return UrlFetcher.pipeUrlToFileWithRetry( url, UrlCache._cacheFilePathForUrl(project_id, url), - (error) => { + error => { if (error != null) { return callback(error) } @@ -111,7 +113,7 @@ module.exports = UrlCache = { project_id, url, lastModified, - (error) => { + error => { if (error != null) { return callback(error) } @@ -138,23 +140,24 @@ module.exports = UrlCache = { if (lastModified == null) { return callback(null, true) } - return UrlCache._findUrlDetails(project_id, url, function ( - error, - urlDetails - ) { - if (error != null) { - return callback(error) + return UrlCache._findUrlDetails( + project_id, + url, + function (error, urlDetails) { + if (error != null) { + return callback(error) + } + if ( + urlDetails == null || + urlDetails.lastModified == null || + urlDetails.lastModified.getTime() < lastModified.getTime() + ) { + return callback(null, true) + } else { + return callback(null, false) + } } - if ( - urlDetails == null || - urlDetails.lastModified == null || - urlDetails.lastModified.getTime() < lastModified.getTime() - ) { - return callback(null, true) - } else { - return callback(null, false) - } - }) + ) }, _cacheFileNameForUrl(project_id, url) { @@ -176,14 +179,16 @@ module.exports = UrlCache = { if (error != null) { return callback(error) } - return UrlCache._deleteUrlCacheFromDisk(project_id, url, function ( - error - ) { - if (error != null) { - return callback(error) + return UrlCache._deleteUrlCacheFromDisk( + project_id, + url, + function (error) { + if (error != null) { + return callback(error) + } + return callback(null) } - return callback(null) - }) + ) }) }, @@ -191,16 +196,17 @@ module.exports = UrlCache = { if (callback == null) { callback = function (error) {} } - return fs.unlink(UrlCache._cacheFilePathForUrl(project_id, url), function ( - error - ) { - if (error != null && error.code !== 'ENOENT') { - // no error if the file isn't present - return callback(error) - } else { - return callback() + return fs.unlink( + UrlCache._cacheFilePathForUrl(project_id, url), + function (error) { + if (error != null && error.code !== 'ENOENT') { + // no error if the file isn't present + return callback(error) + } else { + return callback() + } } - }) + ) }, _findUrlDetails(project_id, url, callback) { @@ -208,9 +214,9 @@ module.exports = UrlCache = { callback = function (error, urlDetails) {} } const timer = new Metrics.Timer('db-find-url-details') - const job = (cb) => + const job = cb => db.UrlCache.findOne({ where: { url, project_id } }) - .then((urlDetails) => cb(null, urlDetails)) + .then(urlDetails => cb(null, urlDetails)) .error(cb) dbQueue.queue.push(job, (error, urlDetails) => { timer.done() @@ -223,7 +229,7 @@ module.exports = UrlCache = { callback = function (error) {} } const timer = new Metrics.Timer('db-update-or-create-url-details') - const job = (cb) => + const job = cb => db.UrlCache.findOrCreate({ where: { url, project_id } }) .spread((urlDetails, created) => urlDetails @@ -232,7 +238,7 @@ module.exports = UrlCache = { .error(cb) ) .error(cb) - dbQueue.queue.push(job, (error) => { + dbQueue.queue.push(job, error => { timer.done() callback(error) }) @@ -243,11 +249,11 @@ module.exports = UrlCache = { callback = function (error) {} } const timer = new Metrics.Timer('db-clear-url-details') - const job = (cb) => + const job = cb => db.UrlCache.destroy({ where: { url, project_id } }) .then(() => cb(null)) .error(cb) - dbQueue.queue.push(job, (error) => { + dbQueue.queue.push(job, error => { timer.done() callback(error) }) @@ -258,12 +264,12 @@ module.exports = UrlCache = { callback = function (error, urls) {} } const timer = new Metrics.Timer('db-find-urls-in-project') - const job = (cb) => + const job = cb => db.UrlCache.findAll({ where: { project_id } }) - .then((urlEntries) => + .then(urlEntries => cb( null, - urlEntries.map((entry) => entry.url) + urlEntries.map(entry => entry.url) ) ) .error(cb) @@ -271,5 +277,5 @@ module.exports = UrlCache = { timer.done() callback(err, urls) }) - } + }, } diff --git a/services/clsi/app/js/UrlFetcher.js b/services/clsi/app/js/UrlFetcher.js index 28155b94be..4d3c3d5ebe 100644 --- a/services/clsi/app/js/UrlFetcher.js +++ b/services/clsi/app/js/UrlFetcher.js @@ -127,5 +127,5 @@ module.exports = UrlFetcher = { ) } }) - } + }, } diff --git a/services/clsi/app/js/db.js b/services/clsi/app/js/db.js index 135f3a52d4..856f1da08b 100644 --- a/services/clsi/app/js/db.js +++ b/services/clsi/app/js/db.js @@ -37,10 +37,10 @@ module.exports = { { url: Sequelize.STRING, project_id: Sequelize.STRING, - lastModified: Sequelize.DATE + lastModified: Sequelize.DATE, }, { - indexes: [{ fields: ['url', 'project_id'] }, { fields: ['project_id'] }] + indexes: [{ fields: ['url', 'project_id'] }, { fields: ['project_id'] }], } ), @@ -48,10 +48,10 @@ module.exports = { 'Project', { project_id: { type: Sequelize.STRING, primaryKey: true }, - lastAccessed: Sequelize.DATE + lastAccessed: Sequelize.DATE, }, { - indexes: [{ fields: ['lastAccessed'] }] + indexes: [{ fields: ['lastAccessed'] }], } ), @@ -62,6 +62,6 @@ module.exports = { return sequelize .sync() .then(() => logger.log('db sync complete')) - .catch((err) => console.log(err, 'error syncing')) - } + .catch(err => console.log(err, 'error syncing')) + }, } diff --git a/services/clsi/app/lib/pdfjs/FSPdfManager.js b/services/clsi/app/lib/pdfjs/FSPdfManager.js index 8fb1606bf0..692576b2ce 100644 --- a/services/clsi/app/lib/pdfjs/FSPdfManager.js +++ b/services/clsi/app/lib/pdfjs/FSPdfManager.js @@ -39,5 +39,5 @@ class FSPdfManager extends LocalPdfManager { } module.exports = { - FSPdfManager + FSPdfManager, } diff --git a/services/clsi/app/lib/pdfjs/FSStream.js b/services/clsi/app/lib/pdfjs/FSStream.js index 748d74362a..e3e3ac0243 100644 --- a/services/clsi/app/lib/pdfjs/FSStream.js +++ b/services/clsi/app/lib/pdfjs/FSStream.js @@ -34,14 +34,14 @@ class FSStream extends Stream { const result = { begin: begin, end: end, - buffer: Buffer.alloc(end - begin, 0) + buffer: Buffer.alloc(end - begin, 0), } this.cachedBytes.push(result) return this.fh.read(result.buffer, 0, end - begin, begin) } _ensureGetPos(pos) { - const found = this.cachedBytes.find((x) => { + const found = this.cachedBytes.find(x => { return x.begin <= pos && pos < x.end }) if (!found) { @@ -52,7 +52,7 @@ class FSStream extends Stream { _ensureGetRange(begin, end) { end = Math.min(end, this.length) // BG: handle overflow case - const found = this.cachedBytes.find((x) => { + const found = this.cachedBytes.find(x => { return x.begin <= begin && end <= x.end }) if (!found) { diff --git a/services/clsi/app/lib/pdfjs/parseXrefTable.js b/services/clsi/app/lib/pdfjs/parseXrefTable.js index de7a386f48..1ecfd2397d 100644 --- a/services/clsi/app/lib/pdfjs/parseXrefTable.js +++ b/services/clsi/app/lib/pdfjs/parseXrefTable.js @@ -23,5 +23,5 @@ async function parseXrefTable(path, size, checkDeadline) { } module.exports = { - parseXrefTable + parseXrefTable, } diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index b53f871b6d..352c045d27 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -12,12 +12,12 @@ module.exports = { process.env.SQLITE_PATH || Path.resolve(__dirname, '../db/db.sqlite'), pool: { max: 1, - min: 1 + min: 1, }, retry: { - max: 10 - } - } + max: 10, + }, + }, }, compileSizeLimit: process.env.COMPILE_SIZE_LIMIT || '7mb', @@ -33,30 +33,30 @@ module.exports = { clsiCacheDir: Path.resolve(__dirname, '../cache'), synctexBaseDir(projectId) { return Path.join(this.compilesDir, projectId) - } + }, }, internal: { clsi: { port: 3013, - host: process.env.LISTEN_ADDRESS || 'localhost' + host: process.env.LISTEN_ADDRESS || 'localhost', }, load_balancer_agent: { report_load: true, load_port: 3048, - local_port: 3049 - } + local_port: 3049, + }, }, apis: { clsi: { - url: `http://${process.env.CLSI_HOST || 'localhost'}:3013` + url: `http://${process.env.CLSI_HOST || 'localhost'}:3013`, }, clsiPerf: { host: `${process.env.CLSI_PERF_HOST || 'localhost'}:${ process.env.CLSI_PERF_PORT || '3043' - }` - } + }`, + }, }, smokeTest: process.env.SMOKE_TEST || false, @@ -67,7 +67,7 @@ module.exports = { texliveImageNameOveride: process.env.TEX_LIVE_IMAGE_NAME_OVERRIDE, texliveOpenoutAny: process.env.TEXLIVE_OPENOUT_ANY, sentry: { - dsn: process.env.SENTRY_DSN + dsn: process.env.SENTRY_DSN, }, enablePdfCaching: process.env.ENABLE_PDF_CACHING === 'true', @@ -75,14 +75,13 @@ module.exports = { pdfCachingMinChunkSize: parseInt(process.env.PDF_CACHING_MIN_CHUNK_SIZE, 10) || 1024, pdfCachingMaxProcessingTime: - parseInt(process.env.PDF_CACHING_MAX_PROCESSING_TIME, 10) || 10 * 1000 + parseInt(process.env.PDF_CACHING_MAX_PROCESSING_TIME, 10) || 10 * 1000, } if (process.env.ALLOWED_COMPILE_GROUPS) { try { - module.exports.allowedCompileGroups = process.env.ALLOWED_COMPILE_GROUPS.split( - ' ' - ) + module.exports.allowedCompileGroups = + process.env.ALLOWED_COMPILE_GROUPS.split(' ') } catch (error) { console.error(error, 'could not apply allowed compile group setting') process.exit(1) @@ -98,14 +97,14 @@ if (process.env.DOCKER_RUNNER) { image: process.env.TEXLIVE_IMAGE || 'quay.io/sharelatex/texlive-full:2017.1', env: { - HOME: '/tmp' + HOME: '/tmp', }, socketPath: '/var/run/docker.sock', - user: process.env.TEXLIVE_IMAGE_USER || 'tex' + user: process.env.TEXLIVE_IMAGE_USER || 'tex', }, optimiseInDocker: true, expireProjectAfterIdleMs: 24 * 60 * 60 * 1000, - checkProjectsIntervalMs: 10 * 60 * 1000 + checkProjectsIntervalMs: 10 * 60 * 1000, } try { @@ -120,7 +119,7 @@ if (process.env.DOCKER_RUNNER) { // Automatically clean up wordcount and synctex containers const defaultCompileGroupConfig = { wordcount: { 'HostConfig.AutoRemove': true }, - synctex: { 'HostConfig.AutoRemove': true } + synctex: { 'HostConfig.AutoRemove': true }, } module.exports.clsi.docker.compileGroupConfig = Object.assign( defaultCompileGroupConfig, @@ -146,9 +145,8 @@ if (process.env.DOCKER_RUNNER) { if (process.env.ALLOWED_IMAGES) { try { - module.exports.clsi.docker.allowedImages = process.env.ALLOWED_IMAGES.split( - ' ' - ) + module.exports.clsi.docker.allowedImages = + process.env.ALLOWED_IMAGES.split(' ') } catch (error) { console.error(error, 'could not apply allowed images setting') process.exit(1) diff --git a/services/clsi/test/acceptance/js/AllowedImageNamesTests.js b/services/clsi/test/acceptance/js/AllowedImageNamesTests.js index 06304a808d..e33ccb46e4 100644 --- a/services/clsi/test/acceptance/js/AllowedImageNamesTests.js +++ b/services/clsi/test/acceptance/js/AllowedImageNamesTests.js @@ -7,7 +7,7 @@ describe('AllowedImageNames', function () { this.project_id = Client.randomId() this.request = { options: { - imageName: undefined + imageName: undefined, }, resources: [ { @@ -17,9 +17,9 @@ describe('AllowedImageNames', function () { \\begin{document} Hello world \\end{document}\ -` - } - ] +`, + }, + ], } ClsiApp.ensureRunning(done) }) @@ -101,8 +101,8 @@ Hello world expect(error).to.not.exist expect(result).to.deep.equal({ pdf: [ - { page: 1, h: 133.77, v: 134.76, height: 6.92, width: 343.71 } - ] + { page: 1, h: 133.77, v: 134.76, height: 6.92, width: 343.71 }, + ], }) done() } @@ -139,7 +139,7 @@ Hello world (error, result) => { expect(error).to.not.exist expect(result).to.deep.equal({ - code: [{ file: 'main.tex', line: 3, column: -1 }] + code: [{ file: 'main.tex', line: 3, column: -1 }], }) done() } diff --git a/services/clsi/test/acceptance/js/BrokenLatexFileTests.js b/services/clsi/test/acceptance/js/BrokenLatexFileTests.js index 34d3b37c92..71e9956c0d 100644 --- a/services/clsi/test/acceptance/js/BrokenLatexFileTests.js +++ b/services/clsi/test/acceptance/js/BrokenLatexFileTests.js @@ -23,9 +23,9 @@ describe('Broken LaTeX file', function () { \\begin{documen % :( Broken \\end{documen % :(\ -` - } - ] +`, + }, + ], } this.correct_request = { resources: [ @@ -36,9 +36,9 @@ Broken \\begin{document} Hello world \\end{document}\ -` - } - ] +`, + }, + ], } return ClsiApp.ensureRunning(done) }) diff --git a/services/clsi/test/acceptance/js/DeleteOldFilesTest.js b/services/clsi/test/acceptance/js/DeleteOldFilesTest.js index 064f6be67d..09eea1a948 100644 --- a/services/clsi/test/acceptance/js/DeleteOldFilesTest.js +++ b/services/clsi/test/acceptance/js/DeleteOldFilesTest.js @@ -23,9 +23,9 @@ describe('Deleting Old Files', function () { \\begin{document} Hello world \\end{document}\ -` - } - ] +`, + }, + ], } return ClsiApp.ensureRunning(done) }) diff --git a/services/clsi/test/acceptance/js/ExampleDocumentTests.js b/services/clsi/test/acceptance/js/ExampleDocumentTests.js index 92bab4b6cc..84758c4d65 100644 --- a/services/clsi/test/acceptance/js/ExampleDocumentTests.js +++ b/services/clsi/test/acceptance/js/ExampleDocumentTests.js @@ -23,7 +23,7 @@ const ChildProcess = require('child_process') const ClsiApp = require('./helpers/ClsiApp') const logger = require('logger-sharelatex') const Path = require('path') -const fixturePath = (path) => { +const fixturePath = path => { if (path.slice(0, 3) === 'tmp') { return '/tmp/clsi_acceptance_tests' + path.slice(3) } @@ -49,8 +49,8 @@ const convertToPng = function (pdfPath, pngPath, callback) { console.log(command) const convert = ChildProcess.exec(command) const stdout = '' - convert.stdout.on('data', (chunk) => console.log('STDOUT', chunk.toString())) - convert.stderr.on('data', (chunk) => console.log('STDERR', chunk.toString())) + convert.stdout.on('data', chunk => console.log('STDOUT', chunk.toString())) + convert.stderr.on('data', chunk => console.log('STDERR', chunk.toString())) return convert.on('exit', () => callback()) } @@ -65,11 +65,11 @@ const compare = function (originalPath, generatedPath, callback) { )} ${diff_file}` ) let stderr = '' - proc.stderr.on('data', (chunk) => (stderr += chunk)) + proc.stderr.on('data', chunk => (stderr += chunk)) return proc.on('exit', () => { if (stderr.trim() === '0 (0)') { // remove output diff if test matches expected image - fs.unlink(diff_file, (err) => { + fs.unlink(diff_file, err => { if (err) { throw err } @@ -88,8 +88,8 @@ const checkPdfInfo = function (pdfPath, callback) { } const proc = ChildProcess.exec(`pdfinfo ${fixturePath(pdfPath)}`) let stdout = '' - proc.stdout.on('data', (chunk) => (stdout += chunk)) - proc.stderr.on('data', (chunk) => console.log('STDERR', chunk.toString())) + proc.stdout.on('data', chunk => (stdout += chunk)) + proc.stderr.on('data', chunk => console.log('STDERR', chunk.toString())) return proc.on('exit', () => { if (stdout.match(/Optimized:\s+yes/)) { return callback(null, true) @@ -135,14 +135,14 @@ const comparePdf = function (project_id, example_dir, callback) { return convertToPng( `tmp/${project_id}.pdf`, `tmp/${project_id}-generated.png`, - (error) => { + error => { if (error != null) { throw error } return convertToPng( `examples/${example_dir}/output.pdf`, `tmp/${project_id}-source.png`, - (error) => { + error => { if (error != null) { throw error } @@ -162,7 +162,7 @@ const comparePdf = function (project_id, example_dir, callback) { } ) } else { - return compareMultiplePages(project_id, (error) => { + return compareMultiplePages(project_id, error => { if (error != null) { throw error } @@ -216,82 +216,71 @@ describe('Example Documents', function () { fsExtra.remove(fixturePath('tmp'), done) }) - return Array.from(fs.readdirSync(fixturePath('examples'))).map( - (example_dir) => - ((example_dir) => - describe(example_dir, function () { - before(function () { - return (this.project_id = Client.randomId() + '_' + example_dir) - }) + return Array.from(fs.readdirSync(fixturePath('examples'))).map(example_dir => + (example_dir => + describe(example_dir, function () { + before(function () { + return (this.project_id = Client.randomId() + '_' + example_dir) + }) - it('should generate the correct pdf', function (done) { - this.timeout(MOCHA_LATEX_TIMEOUT) - return Client.compileDirectory( - this.project_id, - fixturePath('examples'), - example_dir, - 4242, - (error, res, body) => { - if ( - error || - __guard__( - body != null ? body.compile : undefined, - (x) => x.status - ) === 'failure' - ) { - console.log( - 'DEBUG: error', - error, - 'body', - JSON.stringify(body) - ) - return done(new Error('Compile failed')) - } - const pdf = Client.getOutputFile(body, 'pdf') - return downloadAndComparePdf( - this.project_id, - example_dir, - pdf.url, - done - ) + it('should generate the correct pdf', function (done) { + this.timeout(MOCHA_LATEX_TIMEOUT) + return Client.compileDirectory( + this.project_id, + fixturePath('examples'), + example_dir, + 4242, + (error, res, body) => { + if ( + error || + __guard__( + body != null ? body.compile : undefined, + x => x.status + ) === 'failure' + ) { + console.log('DEBUG: error', error, 'body', JSON.stringify(body)) + return done(new Error('Compile failed')) } - ) - }) + const pdf = Client.getOutputFile(body, 'pdf') + return downloadAndComparePdf( + this.project_id, + example_dir, + pdf.url, + done + ) + } + ) + }) - return it('should generate the correct pdf on the second run as well', function (done) { - this.timeout(MOCHA_LATEX_TIMEOUT) - return Client.compileDirectory( - this.project_id, - fixturePath('examples'), - example_dir, - 4242, - (error, res, body) => { - if ( - error || - __guard__( - body != null ? body.compile : undefined, - (x) => x.status - ) === 'failure' - ) { - console.log( - 'DEBUG: error', - error, - 'body', - JSON.stringify(body) - ) - return done(new Error('Compile failed')) - } - const pdf = Client.getOutputFile(body, 'pdf') - return downloadAndComparePdf( - this.project_id, - example_dir, - pdf.url, - done - ) + return it('should generate the correct pdf on the second run as well', function (done) { + this.timeout(MOCHA_LATEX_TIMEOUT) + return Client.compileDirectory( + this.project_id, + fixturePath('examples'), + example_dir, + 4242, + (error, res, body) => { + if ( + error || + __guard__( + body != null ? body.compile : undefined, + x => x.status + ) === 'failure' + ) { + console.log('DEBUG: error', error, 'body', JSON.stringify(body)) + return done(new Error('Compile failed')) } - ) - }) - }))(example_dir) + const pdf = Client.getOutputFile(body, 'pdf') + return downloadAndComparePdf( + this.project_id, + example_dir, + pdf.url, + done + ) + } + ) + }) + }))(example_dir) ) }) diff --git a/services/clsi/test/acceptance/js/SimpleLatexFileTests.js b/services/clsi/test/acceptance/js/SimpleLatexFileTests.js index 7377afdaf1..b2152f39ed 100644 --- a/services/clsi/test/acceptance/js/SimpleLatexFileTests.js +++ b/services/clsi/test/acceptance/js/SimpleLatexFileTests.js @@ -24,9 +24,9 @@ describe('Simple LaTeX file', function () { \\begin{document} Hello world \\end{document}\ -` - } - ] +`, + }, + ], } return ClsiApp.ensureRunning(() => { return Client.compile( diff --git a/services/clsi/test/acceptance/js/Stats.js b/services/clsi/test/acceptance/js/Stats.js index d96a8fcbce..4f071abe5f 100644 --- a/services/clsi/test/acceptance/js/Stats.js +++ b/services/clsi/test/acceptance/js/Stats.js @@ -3,7 +3,7 @@ const Settings = require('@overleaf/settings') after(function (done) { request( { - url: `${Settings.apis.clsi.url}/metrics` + url: `${Settings.apis.clsi.url}/metrics`, }, (err, response, body) => { if (err) return done(err) diff --git a/services/clsi/test/acceptance/js/SynctexTests.js b/services/clsi/test/acceptance/js/SynctexTests.js index 206ada45d1..3bed29aac7 100644 --- a/services/clsi/test/acceptance/js/SynctexTests.js +++ b/services/clsi/test/acceptance/js/SynctexTests.js @@ -27,9 +27,9 @@ Hello world resources: [ { path: 'main.tex', - content - } - ] + content, + }, + ], } this.project_id = Client.randomId() return ClsiApp.ensureRunning(() => { @@ -59,8 +59,8 @@ Hello world } expect(pdfPositions).to.deep.equal({ pdf: [ - { page: 1, h: 133.77, v: 134.76, height: 6.92, width: 343.71 } - ] + { page: 1, h: 133.77, v: 134.76, height: 6.92, width: 343.71 }, + ], }) return done() } @@ -80,7 +80,7 @@ Hello world throw error } expect(codePositions).to.deep.equal({ - code: [{ file: 'main.tex', line: 3, column: -1 }] + code: [{ file: 'main.tex', line: 3, column: -1 }], }) return done() } @@ -132,9 +132,9 @@ Hello world resources: [ { path: 'main.tex', - content - } - ] + content, + }, + ], } Client.compile( this.broken_project_id, diff --git a/services/clsi/test/acceptance/js/TimeoutTests.js b/services/clsi/test/acceptance/js/TimeoutTests.js index 0d359bec0a..bca8ae71d2 100644 --- a/services/clsi/test/acceptance/js/TimeoutTests.js +++ b/services/clsi/test/acceptance/js/TimeoutTests.js @@ -16,7 +16,7 @@ describe('Timed out compile', function () { before(function (done) { this.request = { options: { - timeout: 10 + timeout: 10, }, // seconds resources: [ { @@ -27,9 +27,9 @@ describe('Timed out compile', function () { \\def\\x{Hello!\\par\\x} \\x \\end{document}\ -` - } - ] +`, + }, + ], } this.project_id = Client.randomId() return ClsiApp.ensureRunning(() => { @@ -55,7 +55,7 @@ describe('Timed out compile', function () { }) return it('should return the log output file name', function () { - const outputFilePaths = this.body.compile.outputFiles.map((x) => x.path) + const outputFilePaths = this.body.compile.outputFiles.map(x => x.path) return outputFilePaths.should.include('output.log') }) }) diff --git a/services/clsi/test/acceptance/js/UrlCachingTests.js b/services/clsi/test/acceptance/js/UrlCachingTests.js index 9528efba95..05a8b26e6f 100644 --- a/services/clsi/test/acceptance/js/UrlCachingTests.js +++ b/services/clsi/test/acceptance/js/UrlCachingTests.js @@ -35,7 +35,7 @@ const Server = { randomId() { return Math.random().toString(16).slice(2) - } + }, } Server.run() @@ -55,13 +55,13 @@ describe('Url Caching', function () { \\begin{document} \\includegraphics{lion.png} \\end{document}\ -` +`, }, { path: 'lion.png', - url: `http://${host}:31415/${this.file}` - } - ] + url: `http://${host}:31415/${this.file}`, + }, + ], } sinon.spy(Server, 'getFile') @@ -102,14 +102,14 @@ describe('Url Caching', function () { \\begin{document} \\includegraphics{lion.png} \\end{document}\ -` +`, }, (this.image_resource = { path: 'lion.png', url: `http://${host}:31415/${this.file}`, - modified: Date.now() - }) - ] + modified: Date.now(), + }), + ], } return Client.compile( @@ -157,14 +157,14 @@ describe('Url Caching', function () { \\begin{document} \\includegraphics{lion.png} \\end{document}\ -` +`, }, (this.image_resource = { path: 'lion.png', url: `http://${host}:31415/${this.file}`, - modified: (this.last_modified = Date.now()) - }) - ] + modified: (this.last_modified = Date.now()), + }), + ], } return Client.compile( @@ -213,14 +213,14 @@ describe('Url Caching', function () { \\begin{document} \\includegraphics{lion.png} \\end{document}\ -` +`, }, (this.image_resource = { path: 'lion.png', url: `http://${host}:31415/${this.file}`, - modified: (this.last_modified = Date.now()) - }) - ] + modified: (this.last_modified = Date.now()), + }), + ], } return Client.compile( @@ -269,14 +269,14 @@ describe('Url Caching', function () { \\begin{document} \\includegraphics{lion.png} \\end{document}\ -` +`, }, (this.image_resource = { path: 'lion.png', url: `http://${host}:31415/${this.file}`, - modified: (this.last_modified = Date.now()) - }) - ] + modified: (this.last_modified = Date.now()), + }), + ], } return Client.compile( @@ -325,17 +325,17 @@ describe('Url Caching', function () { \\begin{document} \\includegraphics{lion.png} \\end{document}\ -` +`, }, (this.image_resource = { path: 'lion.png', url: `http://${host}:31415/${this.file}`, - modified: (this.last_modified = Date.now()) - }) - ] + modified: (this.last_modified = Date.now()), + }), + ], } - return Client.compile(this.project_id, this.request, (error) => { + return Client.compile(this.project_id, this.request, error => { if (error != null) { throw error } diff --git a/services/clsi/test/acceptance/js/WordcountTests.js b/services/clsi/test/acceptance/js/WordcountTests.js index 837eb47a2a..d3fa7d2b94 100644 --- a/services/clsi/test/acceptance/js/WordcountTests.js +++ b/services/clsi/test/acceptance/js/WordcountTests.js @@ -25,9 +25,9 @@ describe('Syncing', function () { content: fs.readFileSync( path.join(__dirname, '../fixtures/naugty_strings.txt'), 'utf-8' - ) - } - ] + ), + }, + ], } this.project_id = Client.randomId() return ClsiApp.ensureRunning(() => { @@ -61,8 +61,8 @@ describe('Syncing', function () { mathInline: 6, mathDisplay: 0, errors: 0, - messages: '' - } + messages: '', + }, }) return done() }) diff --git a/services/clsi/test/acceptance/js/helpers/Client.js b/services/clsi/test/acceptance/js/helpers/Client.js index 1da6601efc..af8e0e30fa 100644 --- a/services/clsi/test/acceptance/js/helpers/Client.js +++ b/services/clsi/test/acceptance/js/helpers/Client.js @@ -38,8 +38,8 @@ module.exports = Client = { { url: `${this.host}/project/${project_id}/compile`, json: { - compile: data - } + compile: data, + }, }, callback ) @@ -66,7 +66,7 @@ module.exports = Client = { const app = express() app.use(express.static(directory)) console.log('starting test server on', port, host) - return app.listen(port, host).on('error', (error) => { + return app.listen(port, host).on('error', error => { console.error('error starting server:', error.message) return process.exit(1) }) @@ -87,9 +87,9 @@ module.exports = Client = { imageName, file, line, - column + column, }, - json: true + json: true, }, (error, response, body) => { if (error != null) { @@ -118,9 +118,9 @@ module.exports = Client = { imageName, page, h, - v + v, }, - json: true + json: true, }, (error, response, body) => { if (error != null) { @@ -148,7 +148,7 @@ module.exports = Client = { entities = entities.concat( fs .readdirSync(`${baseDirectory}/${directory}/${entity}`) - .map((subEntity) => { + .map(subEntity => { if (subEntity === 'main.tex') { rootResourcePath = `${entity}/${subEntity}` } @@ -167,14 +167,14 @@ module.exports = Client = { 'Rtex', 'ist', 'md', - 'Rmd' + 'Rmd', ].indexOf(extension) > -1 ) { resources.push({ path: entity, content: fs .readFileSync(`${baseDirectory}/${directory}/${entity}`) - .toString() + .toString(), }) } else if ( ['eps', 'ttf', 'png', 'jpg', 'pdf', 'jpeg'].indexOf(extension) > -1 @@ -182,7 +182,7 @@ module.exports = Client = { resources.push({ path: entity, url: `http://${host}:${serverPort}/${directory}/${entity}`, - modified: stat.mtime + modified: stat.mtime, }) } } @@ -193,7 +193,7 @@ module.exports = Client = { (error, body) => { const req = { resources, - rootResourcePath + rootResourcePath, } if (error == null) { @@ -220,8 +220,8 @@ module.exports = Client = { url: `${this.host}/project/${project_id}/wordcount`, qs: { image, - file - } + file, + }, }, (error, response, body) => { if (error != null) { @@ -233,5 +233,5 @@ module.exports = Client = { return callback(null, JSON.parse(body)) } ) - } + }, } diff --git a/services/clsi/test/acceptance/js/helpers/ClsiApp.js b/services/clsi/test/acceptance/js/helpers/ClsiApp.js index 343c3c7d95..8dc946ff04 100644 --- a/services/clsi/test/acceptance/js/helpers/ClsiApp.js +++ b/services/clsi/test/acceptance/js/helpers/ClsiApp.js @@ -35,10 +35,10 @@ module.exports = { return app.listen( __guard__( Settings.internal != null ? Settings.internal.clsi : undefined, - (x) => x.port + x => x.port ), 'localhost', - (error) => { + error => { if (error != null) { throw error } @@ -55,7 +55,7 @@ module.exports = { } ) } - } + }, } function __guard__(value, transform) { return typeof value !== 'undefined' && value !== null diff --git a/services/clsi/test/acceptance/scripts/settings.test.js b/services/clsi/test/acceptance/scripts/settings.test.js index f9446c314f..877ad34641 100644 --- a/services/clsi/test/acceptance/scripts/settings.test.js +++ b/services/clsi/test/acceptance/scripts/settings.test.js @@ -9,8 +9,8 @@ module.exports = { username: 'clsi', password: null, dialect: 'sqlite', - storage: Path.resolve('db.sqlite') - } + storage: Path.resolve('db.sqlite'), + }, }, path: { @@ -22,7 +22,7 @@ module.exports = { synctexBaseDir() { return '/compile' }, - sandboxedCompilesHostDir: process.env.SANDBOXED_COMPILES_HOST_DIR + sandboxedCompilesHostDir: process.env.SANDBOXED_COMPILES_HOST_DIR, }, clsi: { @@ -33,32 +33,31 @@ module.exports = { docker: { image: process.env.TEXLIVE_IMAGE || 'texlive-full:2017.1-opt', env: { - PATH: - '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2017/bin/x86_64-linux/', - HOME: '/tmp' + PATH: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2017/bin/x86_64-linux/', + HOME: '/tmp', }, modem: { - socketPath: false + socketPath: false, }, - user: process.env.SIBLING_CONTAINER_USER || '111' - } + user: process.env.SIBLING_CONTAINER_USER || '111', + }, }, internal: { clsi: { port: 3013, load_port: 3044, - host: 'localhost' - } + host: 'localhost', + }, }, apis: { clsi: { - url: 'http://localhost:3013' - } + url: 'http://localhost:3013', + }, }, smokeTest: false, project_cache_length_ms: 1000 * 60 * 60 * 24, - parallelFileDownloads: 1 + parallelFileDownloads: 1, } diff --git a/services/clsi/test/bench/hashbench.js b/services/clsi/test/bench/hashbench.js index bbd9a02855..787a4e2280 100644 --- a/services/clsi/test/bench/hashbench.js +++ b/services/clsi/test/bench/hashbench.js @@ -22,13 +22,13 @@ function test(hashType, filePath, callback) { return callback(err) } const t0 = process.hrtime.bigint() - ContentCacheManager.update(dir, filePath, (x) => { + ContentCacheManager.update(dir, filePath, x => { const t1 = process.hrtime.bigint() const cold = Number(t1 - t0) / 1e6 - ContentCacheManager.update(dir, filePath, (x) => { + ContentCacheManager.update(dir, filePath, x => { const t2 = process.hrtime.bigint() const warm = Number(t2 - t1) / 1e6 - fs.rmdir(dir, { recursive: true }, (err) => { + fs.rmdir(dir, { recursive: true }, err => { if (err) { return callback(err) } @@ -52,18 +52,18 @@ function test(hashType, filePath, callback) { }) } -var jobs = [] -files.forEach((file) => { - jobs.push((cb) => { +const jobs = [] +files.forEach(file => { + jobs.push(cb => { test('md5', file, cb) }) - jobs.push((cb) => { + jobs.push(cb => { test('sha1', file, cb) }) - jobs.push((cb) => { + jobs.push(cb => { test('hmac-sha1', file, cb) }) - jobs.push((cb) => { + jobs.push(cb => { test('sha256', file, cb) }) }) diff --git a/services/clsi/test/load/js/loadTest.js b/services/clsi/test/load/js/loadTest.js index 7e2ae5fa79..96f15a4cbe 100644 --- a/services/clsi/test/load/js/loadTest.js +++ b/services/clsi/test/load/js/loadTest.js @@ -17,7 +17,7 @@ const _ = require('lodash') const concurentCompiles = 5 const totalCompiles = 50 -const buildUrl = (path) => +const buildUrl = path => `http://${Settings.internal.clsi.host}:${Settings.internal.clsi.port}/${path}` const mainTexContent = fs.readFileSync('./bulk.tex', 'utf-8') @@ -51,11 +51,11 @@ const makeRequest = function (compileNumber, callback) { \\begin{document} ${bodyContent} \\end{document}\ -` - } - ] - } - } +`, + }, + ], + }, + }, }, (err, response, body) => { if (response.statusCode !== 200) { @@ -74,12 +74,13 @@ ${bodyContent} ) } -const jobs = _.map(__range__(1, totalCompiles, true), (i) => (cb) => - makeRequest(i, cb) +const jobs = _.map( + __range__(1, totalCompiles, true), + i => cb => makeRequest(i, cb) ) const startTime = new Date() -async.parallelLimit(jobs, concurentCompiles, (err) => { +async.parallelLimit(jobs, concurentCompiles, err => { if (err != null) { console.error(err) } diff --git a/services/clsi/test/setup.js b/services/clsi/test/setup.js index 87532f0a26..de87f662b5 100644 --- a/services/clsi/test/setup.js +++ b/services/clsi/test/setup.js @@ -12,8 +12,8 @@ SandboxedModule.configure({ info() {}, warn() {}, error() {}, - err() {} - } + err() {}, + }, }, - globals: { Buffer, console, process } + globals: { Buffer, console, process }, }) diff --git a/services/clsi/test/smoke/js/SmokeTests.js b/services/clsi/test/smoke/js/SmokeTests.js index 8dbdc0a348..0c207e660e 100644 --- a/services/clsi/test/smoke/js/SmokeTests.js +++ b/services/clsi/test/smoke/js/SmokeTests.js @@ -1,20 +1,20 @@ const request = require('request') const Settings = require('@overleaf/settings') -const buildUrl = (path) => +const buildUrl = path => `http://${Settings.internal.clsi.host}:${Settings.internal.clsi.port}/${path}` const url = buildUrl(`project/smoketest-${process.pid}/compile`) module.exports = { sendNewResult(res) { - this._run((error) => this._sendResponse(res, error)) + this._run(error => this._sendResponse(res, error)) }, sendLastResult(res) { this._sendResponse(res, this._lastError) }, triggerRun(cb) { - this._run((error) => { + this._run(error => { this._lastError = error cb(error) }) @@ -74,11 +74,11 @@ module.exports = { } \\end{tikzpicture} \\end{document}\ -` - } - ] - } - } +`, + }, + ], + }, + }, }, (error, response, body) => { if (error) return done(error) @@ -98,5 +98,5 @@ module.exports = { done() } ) - } + }, } diff --git a/services/clsi/test/unit/js/CompileControllerTests.js b/services/clsi/test/unit/js/CompileControllerTests.js index e8739379da..792c7adc7e 100644 --- a/services/clsi/test/unit/js/CompileControllerTests.js +++ b/services/clsi/test/unit/js/CompileControllerTests.js @@ -24,7 +24,7 @@ function tryImageNameValidation(method, imageNameField) { this.Settings.clsi = { docker: {} } this.Settings.clsi.docker.allowedImages = [ 'repo/image:tag1', - 'repo/image:tag2' + 'repo/image:tag2', ] this.res.send = sinon.stub() this.res.status = sinon.stub().returns({ send: this.res.send }) @@ -69,12 +69,12 @@ describe('CompileController', function () { '@overleaf/settings': (this.Settings = { apis: { clsi: { - url: 'http://clsi.example.com' - } - } + url: 'http://clsi.example.com', + }, + }, }), - './ProjectPersistenceManager': (this.ProjectPersistenceManager = {}) - } + './ProjectPersistenceManager': (this.ProjectPersistenceManager = {}), + }, }) this.Settings.externalUrl = 'http://www.example.com' this.req = {} @@ -85,28 +85,28 @@ describe('CompileController', function () { describe('compile', function () { beforeEach(function () { this.req.body = { - compile: 'mock-body' + compile: 'mock-body', } this.req.params = { project_id: (this.project_id = 'project-id-123') } this.request = { - compile: 'mock-parsed-request' + compile: 'mock-parsed-request', } this.request_with_project_id = { compile: this.request.compile, - project_id: this.project_id + project_id: this.project_id, } this.output_files = [ { path: 'output.pdf', type: 'pdf', size: 1337, - build: 1234 + build: 1234, }, { path: 'output.log', type: 'log', - build: 1234 - } + build: 1234, + }, ] this.RequestParser.parse = sinon .stub() @@ -155,13 +155,13 @@ describe('CompileController', function () { error: null, stats: this.stats, timings: this.timings, - outputFiles: this.output_files.map((file) => { + outputFiles: this.output_files.map(file => { return { url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`, - ...file + ...file, } - }) - } + }), + }, }) .should.equal(true) }) @@ -173,13 +173,13 @@ describe('CompileController', function () { { path: 'fake_output.pdf', type: 'pdf', - build: 1234 + build: 1234, }, { path: 'output.log', type: 'log', - build: 1234 - } + build: 1234, + }, ] this.CompileManager.doCompileWithLock = sinon .stub() @@ -196,13 +196,13 @@ describe('CompileController', function () { error: null, stats: this.stats, timings: this.timings, - outputFiles: this.output_files.map((file) => { + outputFiles: this.output_files.map(file => { return { url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`, - ...file + ...file, } - }) - } + }), + }, }) .should.equal(true) }) @@ -215,13 +215,13 @@ describe('CompileController', function () { path: 'output.pdf', type: 'pdf', size: 0, - build: 1234 + build: 1234, }, { path: 'output.log', type: 'log', - build: 1234 - } + build: 1234, + }, ] this.CompileManager.doCompileWithLock = sinon .stub() @@ -238,13 +238,13 @@ describe('CompileController', function () { error: null, stats: this.stats, timings: this.timings, - outputFiles: this.output_files.map((file) => { + outputFiles: this.output_files.map(file => { return { url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`, - ...file + ...file, } - }) - } + }), + }, }) .should.equal(true) }) @@ -268,8 +268,8 @@ describe('CompileController', function () { outputFiles: [], // JSON.stringify will omit these stats: undefined, - timings: undefined - } + timings: undefined, + }, }) .should.equal(true) }) @@ -295,8 +295,8 @@ describe('CompileController', function () { outputFiles: [], // JSON.stringify will omit these stats: undefined, - timings: undefined - } + timings: undefined, + }, }) .should.equal(true) }) @@ -320,8 +320,8 @@ describe('CompileController', function () { outputFiles: [], // JSON.stringify will omit these stats: undefined, - timings: undefined - } + timings: undefined, + }, }) .should.equal(true) }) @@ -338,7 +338,7 @@ describe('CompileController', function () { this.req.query = { file: this.file, line: this.line.toString(), - column: this.column.toString() + column: this.column.toString(), } this.res.json = sinon.stub() @@ -363,7 +363,7 @@ describe('CompileController', function () { it('should return the positions', function () { return this.res.json .calledWith({ - pdf: this.pdfPositions + pdf: this.pdfPositions, }) .should.equal(true) }) @@ -381,7 +381,7 @@ describe('CompileController', function () { this.req.query = { page: this.page.toString(), h: this.h.toString(), - v: this.v.toString() + v: this.v.toString(), } this.res.json = sinon.stub() @@ -400,7 +400,7 @@ describe('CompileController', function () { it('should return the positions', function () { return this.res.json .calledWith({ - code: this.codePositions + code: this.codePositions, }) .should.equal(true) }) @@ -415,7 +415,7 @@ describe('CompileController', function () { this.req.params = { project_id: this.project_id } this.req.query = { file: this.file, - image: (this.image = 'example.com/image') + image: (this.image = 'example.com/image'), } this.res.json = sinon.stub() @@ -435,7 +435,7 @@ describe('CompileController', function () { this.CompileController.wordcount(this.req, this.res, this.next) return this.res.json .calledWith({ - texcount: this.texcount + texcount: this.texcount, }) .should.equal(true) }) diff --git a/services/clsi/test/unit/js/CompileManagerTests.js b/services/clsi/test/unit/js/CompileManagerTests.js index 7f4046c2ee..dfedcf31ef 100644 --- a/services/clsi/test/unit/js/CompileManagerTests.js +++ b/services/clsi/test/unit/js/CompileManagerTests.js @@ -34,16 +34,16 @@ describe('CompileManager', function () { '@overleaf/settings': (this.Settings = { path: { compilesDir: '/compiles/dir', - outputDir: '/output/dir' + outputDir: '/output/dir', }, synctexBaseDir() { return '/compile' }, clsi: { docker: { - image: 'SOMEIMAGE' - } - } + image: 'SOMEIMAGE', + }, + }, }), child_process: (this.child_process = {}), @@ -52,8 +52,8 @@ describe('CompileManager', function () { './TikzManager': (this.TikzManager = {}), './LockManager': (this.LockManager = {}), fs: (this.fs = {}), - 'fs-extra': (this.fse = { ensureDir: sinon.stub().callsArg(1) }) - } + 'fs-extra': (this.fse = { ensureDir: sinon.stub().callsArg(1) }), + }, }) this.callback = sinon.stub() this.project_id = 'project-id-123' @@ -64,7 +64,7 @@ describe('CompileManager', function () { this.request = { resources: (this.resources = 'mock-resources'), project_id: this.project_id, - user_id: this.user_id + user_id: this.user_id, } this.output_files = ['foo', 'bar'] this.Settings.compileDir = 'compiles' @@ -132,24 +132,24 @@ describe('CompileManager', function () { this.output_files = [ { path: 'output.log', - type: 'log' + type: 'log', }, { path: 'output.pdf', - type: 'pdf' - } + type: 'pdf', + }, ] this.build_files = [ { path: 'output.log', type: 'log', - build: 1234 + build: 1234, }, { path: 'output.pdf', type: 'pdf', - build: 1234 - } + build: 1234, + }, ] this.request = { resources: (this.resources = 'mock-resources'), @@ -160,7 +160,7 @@ describe('CompileManager', function () { timeout: (this.timeout = 42000), imageName: (this.image = 'example.com/image'), flags: (this.flags = ['-file-line-error']), - compileGroup: (this.compileGroup = 'compile-group') + compileGroup: (this.compileGroup = 'compile-group'), } this.env = {} this.Settings.compileDir = 'compiles' @@ -201,7 +201,7 @@ describe('CompileManager', function () { image: this.image, flags: this.flags, environment: this.env, - compileGroup: this.compileGroup + compileGroup: this.compileGroup, }) .should.equal(true) }) @@ -254,9 +254,9 @@ describe('CompileManager', function () { environment: { CHKTEX_OPTIONS: '-nall -e9 -e10 -w15 -w16', CHKTEX_EXIT_ON_ERROR: 1, - CHKTEX_ULIMIT_OPTIONS: '-t 5 -v 64000' + CHKTEX_ULIMIT_OPTIONS: '-t 5 -v 64000', }, - compileGroup: this.compileGroup + compileGroup: this.compileGroup, }) .should.equal(true) }) @@ -279,7 +279,7 @@ describe('CompileManager', function () { image: this.image, flags: this.flags, environment: this.env, - compileGroup: this.compileGroup + compileGroup: this.compileGroup, }) .should.equal(true) }) @@ -293,7 +293,7 @@ describe('CompileManager', function () { this.fs.lstat = sinon.stub().callsArgWith(1, null, { isDirectory() { return true - } + }, }) this.proc = new EventEmitter() this.proc.stdout = new EventEmitter() @@ -315,7 +315,7 @@ describe('CompileManager', function () { '-f', '--', `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, - `${this.Settings.path.outputDir}/${this.project_id}-${this.user_id}` + `${this.Settings.path.outputDir}/${this.project_id}-${this.user_id}`, ]) .should.equal(true) }) @@ -331,7 +331,7 @@ describe('CompileManager', function () { this.fs.lstat = sinon.stub().callsArgWith(1, null, { isDirectory() { return true - } + }, }) this.proc = new EventEmitter() this.proc.stdout = new EventEmitter() @@ -354,7 +354,7 @@ describe('CompileManager', function () { '-f', '--', `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, - `${this.Settings.path.outputDir}/${this.project_id}-${this.user_id}` + `${this.Settings.path.outputDir}/${this.project_id}-${this.user_id}`, ]) .should.equal(true) }) @@ -380,7 +380,7 @@ describe('CompileManager', function () { this.column = 3 this.file_name = 'main.tex' this.child_process.execFile = sinon.stub() - return (this.Settings.path.synctexBaseDir = (project_id) => + return (this.Settings.path.synctexBaseDir = project_id => `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`) }) @@ -389,7 +389,7 @@ describe('CompileManager', function () { this.fs.stat = sinon.stub().callsArgWith(1, null, { isFile() { return true - } + }, }) this.stdout = `NODE\t${this.page}\t${this.h}\t${this.v}\t${this.width}\t${this.height}\n` this.CommandRunner.run = sinon @@ -419,7 +419,7 @@ describe('CompileManager', function () { synctex_path, file_path, this.line, - this.column + this.column, ], `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, this.Settings.clsi.docker.image, @@ -437,8 +437,8 @@ describe('CompileManager', function () { h: this.h, v: this.v, height: this.height, - width: this.width - } + width: this.width, + }, ]) .should.equal(true) }) @@ -470,7 +470,7 @@ describe('CompileManager', function () { synctex_path, file_path, this.line, - this.column + this.column, ], `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, customImageName, @@ -487,7 +487,7 @@ describe('CompileManager', function () { this.fs.stat = sinon.stub().callsArgWith(1, null, { isFile() { return true - } + }, }) this.stdout = `NODE\t${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/${this.file_name}\t${this.line}\t${this.column}\n` this.CommandRunner.run = sinon @@ -525,8 +525,8 @@ describe('CompileManager', function () { { file: this.file_name, line: this.line, - column: this.column - } + column: this.column, + }, ]) .should.equal(true) }) @@ -598,7 +598,7 @@ describe('CompileManager', function () { '-nocol', '-inc', this.file_path, - `-out=${this.file_path}.wc` + `-out=${this.file_path}.wc`, ] return this.CommandRunner.run @@ -625,7 +625,7 @@ describe('CompileManager', function () { mathInline: 0, mathDisplay: 0, errors: 0, - messages: '' + messages: '', }) .should.equal(true) }) diff --git a/services/clsi/test/unit/js/ContentCacheManagerTests.js b/services/clsi/test/unit/js/ContentCacheManagerTests.js index 6e8490babd..e59bd68d93 100644 --- a/services/clsi/test/unit/js/ContentCacheManagerTests.js +++ b/services/clsi/test/unit/js/ContentCacheManagerTests.js @@ -91,14 +91,14 @@ describe('ContentCacheManager', function () { objectId: OBJECT_ID_1, start: START_1, end: END_1, - hash: h1 + hash: h1, }, { objectId: OBJECT_ID_2, start: START_2, end: END_2, - hash: h2 - } + hash: h2, + }, ]) }) @@ -110,14 +110,14 @@ describe('ContentCacheManager', function () { JSON.stringify({ hashAge: [ [h1, 0], - [h2, 0] + [h2, 0], ], hashSize: [ [h1, RANGE_1.byteLength], - [h2, RANGE_2.byteLength] - ] + [h2, RANGE_2.byteLength], + ], }) - ) + ), }) }) @@ -144,8 +144,8 @@ describe('ContentCacheManager', function () { objectId: OBJECT_ID_1, start: START_1, end: END_1, - hash: h1 - } + hash: h1, + }, ]) }) @@ -157,14 +157,14 @@ describe('ContentCacheManager', function () { JSON.stringify({ hashAge: [ [h1, 0], - [h2, 1] + [h2, 1], ], hashSize: [ [h1, RANGE_1.byteLength], - [h2, RANGE_2.byteLength] - ] + [h2, RANGE_2.byteLength], + ], }) - ) + ), }) }) @@ -189,8 +189,8 @@ describe('ContentCacheManager', function () { objectId: OBJECT_ID_1, start: START_1, end: END_1, - hash: h1 - } + hash: h1, + }, ]) }) @@ -200,9 +200,9 @@ describe('ContentCacheManager', function () { [Path.join(contentDir, '.state.v0.json')]: Buffer.from( JSON.stringify({ hashAge: [[h1, 0]], - hashSize: [[h1, RANGE_1.byteLength]] + hashSize: [[h1, RANGE_1.byteLength]], }) - ) + ), }) }) diff --git a/services/clsi/test/unit/js/DockerLockManagerTests.js b/services/clsi/test/unit/js/DockerLockManagerTests.js index f06f1afa0e..5708faf292 100644 --- a/services/clsi/test/unit/js/DockerLockManagerTests.js +++ b/services/clsi/test/unit/js/DockerLockManagerTests.js @@ -20,8 +20,8 @@ describe('LockManager', function () { beforeEach(function () { return (this.LockManager = SandboxedModule.require(modulePath, { requires: { - '@overleaf/settings': (this.Settings = { clsi: { docker: {} } }) - } + '@overleaf/settings': (this.Settings = { clsi: { docker: {} } }), + }, })) }) @@ -31,7 +31,7 @@ describe('LockManager', function () { this.callback = sinon.stub() return this.LockManager.runWithLock( 'lock-one', - (releaseLock) => + releaseLock => setTimeout(() => releaseLock(null, 'hello', 'world'), 100), (err, ...args) => { @@ -54,7 +54,7 @@ describe('LockManager', function () { this.callback2 = sinon.stub() this.LockManager.runWithLock( 'lock-one', - (releaseLock) => + releaseLock => setTimeout(() => releaseLock(null, 'hello', 'world', 'one'), 100), (err, ...args) => { @@ -63,7 +63,7 @@ describe('LockManager', function () { ) return this.LockManager.runWithLock( 'lock-two', - (releaseLock) => + releaseLock => setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 200), (err, ...args) => { @@ -95,7 +95,7 @@ describe('LockManager', function () { this.callback2 = sinon.stub() this.LockManager.runWithLock( 'lock', - (releaseLock) => + releaseLock => setTimeout(() => releaseLock(null, 'hello', 'world', 'one'), 100), (err, ...args) => { @@ -104,7 +104,7 @@ describe('LockManager', function () { ) return this.LockManager.runWithLock( 'lock', - (releaseLock) => + releaseLock => setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 200), (err, ...args) => { @@ -149,7 +149,7 @@ describe('LockManager', function () { } this.LockManager.runWithLock( 'lock', - (releaseLock) => + releaseLock => setTimeout( () => releaseLock(null, 'hello', 'world', 'one'), 1100 @@ -162,7 +162,7 @@ describe('LockManager', function () { ) return this.LockManager.runWithLock( 'lock', - (releaseLock) => + releaseLock => setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 100), (err, ...args) => { @@ -206,7 +206,7 @@ describe('LockManager', function () { } this.LockManager.runWithLock( 'lock', - (releaseLock) => + releaseLock => setTimeout( () => releaseLock(null, 'hello', 'world', 'one'), 1500 @@ -219,7 +219,7 @@ describe('LockManager', function () { ) return this.LockManager.runWithLock( 'lock', - (releaseLock) => + releaseLock => setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 100), (err, ...args) => { diff --git a/services/clsi/test/unit/js/DockerRunnerTests.js b/services/clsi/test/unit/js/DockerRunnerTests.js index b47ed609a9..dee351ec2f 100644 --- a/services/clsi/test/unit/js/DockerRunnerTests.js +++ b/services/clsi/test/unit/js/DockerRunnerTests.js @@ -30,7 +30,7 @@ describe('DockerRunner', function () { requires: { '@overleaf/settings': (this.Settings = { clsi: { docker: {} }, - path: {} + path: {}, }), dockerode: (Docker = (function () { Docker = class Docker { @@ -49,21 +49,21 @@ describe('DockerRunner', function () { stat: sinon.stub().yields(null, { isDirectory() { return true - } - }) + }, + }), }), './Metrics': { Timer: (Timer = class Timer { done() {} - }) + }), }, './LockManager': { runWithLock(key, runner, callback) { return runner(callback) - } - } + }, + }, }, - globals: { Math } // used by lodash + globals: { Math }, // used by lodash }) this.Docker = Docker this.getContainer = Docker.prototype.getContainer @@ -172,10 +172,10 @@ describe('DockerRunner', function () { }) it('should re-write the bind directory', function () { - const volumes = this.DockerRunner._runAndWaitForContainer.lastCall - .args[1] + const volumes = + this.DockerRunner._runAndWaitForContainer.lastCall.args[1] return expect(volumes).to.deep.equal({ - '/some/host/dir/compiles/xyz': '/compile' + '/some/host/dir/compiles/xyz': '/compile', }) }) @@ -294,7 +294,7 @@ describe('DockerRunner', function () { beforeEach(function () { this.Settings.clsi.docker.allowedImages = [ 'repo/image:tag1', - 'repo/image:tag2' + 'repo/image:tag2', ] this.DockerRunner._runAndWaitForContainer = sinon .stub() @@ -368,9 +368,9 @@ describe('DockerRunner', function () { beforeEach(function () { this.Settings.clsi.docker.compileGroupConfig = { 'compile-group': { - 'HostConfig.newProperty': 'new-property' + 'HostConfig.newProperty': 'new-property', }, - 'other-group': { otherProperty: 'other-property' } + 'other-group': { otherProperty: 'other-property' }, } this.DockerRunner._runAndWaitForContainer = sinon .stub() @@ -388,14 +388,14 @@ describe('DockerRunner', function () { }) it('should set the docker options for the compile group', function () { - const options = this.DockerRunner._runAndWaitForContainer.lastCall - .args[0] + const options = + this.DockerRunner._runAndWaitForContainer.lastCall.args[0] return expect(options.HostConfig).to.deep.include({ Binds: ['/local/compile/directory:/compile:rw'], LogConfig: { Type: 'none', Config: {} }, CapDrop: 'ALL', SecurityOpt: ['no-new-privileges'], - newProperty: 'new-property' + newProperty: 'new-property', }) }) @@ -588,7 +588,7 @@ describe('DockerRunner', function () { this.fs.stat = sinon.stub().yields(null, { isDirectory() { return false - } + }, }) return this.DockerRunner.startContainer( this.options, @@ -715,23 +715,23 @@ describe('DockerRunner', function () { { Name: '/project-old-container-name', Id: 'old-container-id', - Created: nowInSeconds - oneHourInSeconds - 100 + Created: nowInSeconds - oneHourInSeconds - 100, }, { Name: '/project-new-container-name', Id: 'new-container-id', - Created: nowInSeconds - oneHourInSeconds + 100 + Created: nowInSeconds - oneHourInSeconds + 100, }, { Name: '/totally-not-a-project-container', Id: 'some-random-id', - Created: nowInSeconds - 2 * oneHourInSeconds - } + Created: nowInSeconds - 2 * oneHourInSeconds, + }, ] this.DockerRunner.MAX_CONTAINER_AGE = oneHourInMilliseconds this.listContainers.callsArgWith(1, null, this.containers) this.DockerRunner.destroyContainer = sinon.stub().callsArg(3) - return this.DockerRunner.destroyOldContainers((error) => { + return this.DockerRunner.destroyOldContainers(error => { this.callback(error) return done() }) @@ -778,7 +778,7 @@ describe('DockerRunner', function () { return this.DockerRunner._destroyContainer( this.containerId, false, - (err) => { + err => { this.Docker.prototype.getContainer.callCount.should.equal(1) this.Docker.prototype.getContainer .calledWith(this.containerId) @@ -792,7 +792,7 @@ describe('DockerRunner', function () { return this.DockerRunner._destroyContainer( this.containerId, true, - (err) => { + err => { this.fakeContainer.remove.callCount.should.equal(1) this.fakeContainer.remove .calledWith({ force: true, v: true }) @@ -806,7 +806,7 @@ describe('DockerRunner', function () { return this.DockerRunner._destroyContainer( this.containerId, false, - (err) => { + err => { this.fakeContainer.remove.callCount.should.equal(1) this.fakeContainer.remove .calledWith({ force: false, v: true }) @@ -820,7 +820,7 @@ describe('DockerRunner', function () { return this.DockerRunner._destroyContainer( this.containerId, false, - (err) => { + err => { expect(err).to.equal(null) return done() } @@ -832,7 +832,7 @@ describe('DockerRunner', function () { this.fakeError = new Error('woops') this.fakeError.statusCode = 404 this.fakeContainer = { - remove: sinon.stub().callsArgWith(1, this.fakeError) + remove: sinon.stub().callsArgWith(1, this.fakeError), } return (this.Docker.prototype.getContainer = sinon .stub() @@ -843,7 +843,7 @@ describe('DockerRunner', function () { return this.DockerRunner._destroyContainer( this.containerId, false, - (err) => { + err => { expect(err).to.equal(null) return done() } @@ -856,7 +856,7 @@ describe('DockerRunner', function () { this.fakeError = new Error('woops') this.fakeError.statusCode = 500 this.fakeContainer = { - remove: sinon.stub().callsArgWith(1, this.fakeError) + remove: sinon.stub().callsArgWith(1, this.fakeError), } return (this.Docker.prototype.getContainer = sinon .stub() @@ -867,7 +867,7 @@ describe('DockerRunner', function () { return this.DockerRunner._destroyContainer( this.containerId, false, - (err) => { + err => { expect(err).to.not.equal(null) expect(err).to.equal(this.fakeError) return done() @@ -887,7 +887,7 @@ describe('DockerRunner', function () { }) it('should get the container', function (done) { - return this.DockerRunner.kill(this.containerId, (err) => { + return this.DockerRunner.kill(this.containerId, err => { this.Docker.prototype.getContainer.callCount.should.equal(1) this.Docker.prototype.getContainer .calledWith(this.containerId) @@ -897,14 +897,14 @@ describe('DockerRunner', function () { }) it('should try to force-destroy the container', function (done) { - return this.DockerRunner.kill(this.containerId, (err) => { + return this.DockerRunner.kill(this.containerId, err => { this.fakeContainer.kill.callCount.should.equal(1) return done() }) }) it('should not produce an error', function (done) { - return this.DockerRunner.kill(this.containerId, (err) => { + return this.DockerRunner.kill(this.containerId, err => { expect(err).to.equal(undefined) return done() }) @@ -917,7 +917,7 @@ describe('DockerRunner', function () { this.fakeError.message = 'Cannot kill container <whatever> is not running' this.fakeContainer = { - kill: sinon.stub().callsArgWith(0, this.fakeError) + kill: sinon.stub().callsArgWith(0, this.fakeError), } return (this.Docker.prototype.getContainer = sinon .stub() @@ -925,7 +925,7 @@ describe('DockerRunner', function () { }) return it('should not produce an error', function (done) { - return this.DockerRunner.kill(this.containerId, (err) => { + return this.DockerRunner.kill(this.containerId, err => { expect(err).to.equal(undefined) return done() }) @@ -938,7 +938,7 @@ describe('DockerRunner', function () { this.fakeError.statusCode = 500 this.fakeError.message = 'Totally legitimate reason to throw an error' this.fakeContainer = { - kill: sinon.stub().callsArgWith(0, this.fakeError) + kill: sinon.stub().callsArgWith(0, this.fakeError), } return (this.Docker.prototype.getContainer = sinon .stub() @@ -946,7 +946,7 @@ describe('DockerRunner', function () { }) return it('should produce an error', function (done) { - return this.DockerRunner.kill(this.containerId, (err) => { + return this.DockerRunner.kill(this.containerId, err => { expect(err).to.not.equal(undefined) expect(err).to.equal(this.fakeError) return done() diff --git a/services/clsi/test/unit/js/DraftModeManagerTests.js b/services/clsi/test/unit/js/DraftModeManagerTests.js index 13291e0262..b2391554ca 100644 --- a/services/clsi/test/unit/js/DraftModeManagerTests.js +++ b/services/clsi/test/unit/js/DraftModeManagerTests.js @@ -19,8 +19,8 @@ describe('DraftModeManager', function () { beforeEach(function () { return (this.DraftModeManager = SandboxedModule.require(modulePath, { requires: { - fs: (this.fs = {}) - } + fs: (this.fs = {}), + }, })) }) diff --git a/services/clsi/test/unit/js/LatexRunnerTests.js b/services/clsi/test/unit/js/LatexRunnerTests.js index 13858ff42a..16f40bd7d8 100644 --- a/services/clsi/test/unit/js/LatexRunnerTests.js +++ b/services/clsi/test/unit/js/LatexRunnerTests.js @@ -25,19 +25,19 @@ describe('LatexRunner', function () { requires: { '@overleaf/settings': (this.Settings = { docker: { - socketPath: '/var/run/docker.sock' - } + socketPath: '/var/run/docker.sock', + }, }), './Metrics': { Timer: (Timer = class Timer { done() {} - }) + }), }, './CommandRunner': (this.CommandRunner = {}), fs: (this.fs = { - writeFile: sinon.stub().callsArg(2) - }) - } + writeFile: sinon.stub().callsArg(2), + }), + }, }) this.directory = '/local/compile/directory' @@ -54,7 +54,7 @@ describe('LatexRunner', function () { beforeEach(function () { return (this.CommandRunner.run = sinon.stub().callsArgWith(7, null, { stdout: 'this is stdout', - stderr: 'this is stderr' + stderr: 'this is stderr', })) }) @@ -69,7 +69,7 @@ describe('LatexRunner', function () { timeout: (this.timeout = 42000), image: this.image, environment: this.env, - compileGroup: this.compileGroup + compileGroup: this.compileGroup, }, (error, output, stats, timings) => { this.timings = timings @@ -116,7 +116,7 @@ describe('LatexRunner', function () { '\tCommand being timed: "sh -c timeout 1 yes > /dev/null"\n' + '\tUser time (seconds): 0.28\n' + '\tSystem time (seconds): 0.70\n' + - '\tPercent of CPU this job got: 98%\n' + '\tPercent of CPU this job got: 98%\n', }) this.LatexRunner.runLatex( this.project_id, @@ -127,7 +127,7 @@ describe('LatexRunner', function () { timeout: (this.timeout = 42000), image: this.image, environment: this.env, - compileGroup: this.compileGroup + compileGroup: this.compileGroup, }, (error, output, stats, timings) => { this.timings = timings @@ -152,7 +152,7 @@ describe('LatexRunner', function () { mainFile: 'main-file.Rtex', compiler: this.compiler, image: this.image, - timeout: (this.timeout = 42000) + timeout: (this.timeout = 42000), }, this.callback ) @@ -175,7 +175,7 @@ describe('LatexRunner', function () { compiler: this.compiler, image: this.image, timeout: (this.timeout = 42000), - flags: ['-file-line-error', '-halt-on-error'] + flags: ['-file-line-error', '-halt-on-error'], }, this.callback ) @@ -184,7 +184,7 @@ describe('LatexRunner', function () { return it('should include the flags in the command', function () { const command = this.CommandRunner.run.args[0][1] const flags = command.filter( - (arg) => arg === '-file-line-error' || arg === '-halt-on-error' + arg => arg === '-file-line-error' || arg === '-halt-on-error' ) flags.length.should.equal(2) flags[0].should.equal('-file-line-error') diff --git a/services/clsi/test/unit/js/LockManagerTests.js b/services/clsi/test/unit/js/LockManagerTests.js index 4f9cc31f21..e109054801 100644 --- a/services/clsi/test/unit/js/LockManagerTests.js +++ b/services/clsi/test/unit/js/LockManagerTests.js @@ -25,10 +25,10 @@ describe('DockerLockManager', function () { '@overleaf/settings': {}, fs: { lstat: sinon.stub().callsArgWith(1), - readdir: sinon.stub().callsArgWith(1) + readdir: sinon.stub().callsArgWith(1), }, - lockfile: (this.Lockfile = {}) - } + lockfile: (this.Lockfile = {}), + }, }) return (this.lockFile = '/local/compile/directory/.project-lock') }) diff --git a/services/clsi/test/unit/js/OutputFileFinderTests.js b/services/clsi/test/unit/js/OutputFileFinderTests.js index d094f7c66d..9d1eb4388c 100644 --- a/services/clsi/test/unit/js/OutputFileFinderTests.js +++ b/services/clsi/test/unit/js/OutputFileFinderTests.js @@ -25,11 +25,11 @@ describe('OutputFileFinder', function () { this.OutputFileFinder = SandboxedModule.require(modulePath, { requires: { fs: (this.fs = {}), - child_process: { spawn: (this.spawn = sinon.stub()) } + child_process: { spawn: (this.spawn = sinon.stub()) }, }, globals: { - Math // used by lodash - } + Math, // used by lodash + }, }) this.directory = '/test/dir' return (this.callback = sinon.stub()) @@ -57,12 +57,12 @@ describe('OutputFileFinder', function () { return expect(this.outputFiles).to.deep.equal([ { path: 'output.pdf', - type: 'pdf' + type: 'pdf', }, { path: 'extra/file.tex', - type: 'tex' - } + type: 'tex', + }, ]) }) }) diff --git a/services/clsi/test/unit/js/OutputFileOptimiserTests.js b/services/clsi/test/unit/js/OutputFileOptimiserTests.js index 4c78666084..6a0a015d97 100644 --- a/services/clsi/test/unit/js/OutputFileOptimiserTests.js +++ b/services/clsi/test/unit/js/OutputFileOptimiserTests.js @@ -27,9 +27,9 @@ describe('OutputFileOptimiser', function () { fs: (this.fs = {}), path: (this.Path = {}), child_process: { spawn: (this.spawn = sinon.stub()) }, - './Metrics': {} + './Metrics': {}, }, - globals: { Math } // used by lodash + globals: { Math }, // used by lodash }) this.directory = '/test/dir' return (this.callback = sinon.stub()) diff --git a/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js b/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js index be8050aa47..eceaf89821 100644 --- a/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js +++ b/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js @@ -32,11 +32,11 @@ describe('ProjectPersistenceManager', function () { path: { compilesDir: '/compiles', outputDir: '/output', - clsiCacheDir: '/cache' - } + clsiCacheDir: '/cache', + }, }), - './db': (this.db = {}) - } + './db': (this.db = {}), + }, }) this.callback = sinon.stub() this.project_id = 'project-id-123' @@ -47,7 +47,7 @@ describe('ProjectPersistenceManager', function () { it('should leave expiry alone if plenty of disk', function (done) { this.diskusage.check.resolves({ available: 40, - total: 100 + total: 100, }) this.ProjectPersistenceManager.refreshExpiryTimeout(() => { @@ -61,7 +61,7 @@ describe('ProjectPersistenceManager', function () { it('should drop EXPIRY_TIMEOUT 10% if low disk usage', function (done) { this.diskusage.check.resolves({ available: 5, - total: 100 + total: 100, }) this.ProjectPersistenceManager.refreshExpiryTimeout(() => { @@ -73,7 +73,7 @@ describe('ProjectPersistenceManager', function () { it('should not drop EXPIRY_TIMEOUT to below 50% of project_cache_length_ms', function (done) { this.diskusage.check.resolves({ available: 5, - total: 100 + total: 100, }) this.ProjectPersistenceManager.EXPIRY_TIMEOUT = 500 this.ProjectPersistenceManager.refreshExpiryTimeout(() => { @@ -105,7 +105,7 @@ describe('ProjectPersistenceManager', function () { }) it('should clear each expired project', function () { - return Array.from(this.project_ids).map((project_id) => + return Array.from(this.project_ids).map(project_id => this.ProjectPersistenceManager.clearProjectFromCache .calledWith(project_id) .should.equal(true) diff --git a/services/clsi/test/unit/js/RequestParserTests.js b/services/clsi/test/unit/js/RequestParserTests.js index 840e55ddb6..48364990b4 100644 --- a/services/clsi/test/unit/js/RequestParserTests.js +++ b/services/clsi/test/unit/js/RequestParserTests.js @@ -25,7 +25,7 @@ describe('RequestParser', function () { this.validResource = { path: 'main.tex', date: '12:00 01/02/03', - content: 'Hello world' + content: 'Hello world', } this.validRequest = { compile: { @@ -33,15 +33,15 @@ describe('RequestParser', function () { options: { imageName: 'basicImageName/here:2017-1', compiler: 'pdflatex', - timeout: 42 + timeout: 42, }, - resources: [] - } + resources: [], + }, } return (this.RequestParser = SandboxedModule.require(modulePath, { requires: { - '@overleaf/settings': (this.settings = {}) - } + '@overleaf/settings': (this.settings = {}), + }, })) }) @@ -118,7 +118,7 @@ describe('RequestParser', function () { this.settings.clsi = { docker: {} } this.settings.clsi.docker.allowedImages = [ 'repo/name:tag1', - 'repo/name:tag2' + 'repo/name:tag2', ] }) @@ -402,7 +402,7 @@ describe('RequestParser', function () { this.validRequest.compile.resources.push({ path: this.badPath, date: '12:00 01/02/03', - content: 'Hello world' + content: 'Hello world', }) this.RequestParser.parse(this.validRequest, this.callback) return (this.data = this.callback.args[0][1]) diff --git a/services/clsi/test/unit/js/ResourceStateManagerTests.js b/services/clsi/test/unit/js/ResourceStateManagerTests.js index 1536299c2d..0a97d7b705 100644 --- a/services/clsi/test/unit/js/ResourceStateManagerTests.js +++ b/services/clsi/test/unit/js/ResourceStateManagerTests.js @@ -25,14 +25,14 @@ describe('ResourceStateManager', function () { singleOnly: true, requires: { fs: (this.fs = {}), - './SafeReader': (this.SafeReader = {}) - } + './SafeReader': (this.SafeReader = {}), + }, }) this.basePath = '/path/to/write/files/to' this.resources = [ { path: 'resource-1-mock' }, { path: 'resource-2-mock' }, - { path: 'resource-3-mock' } + { path: 'resource-3-mock' }, ] this.state = '1234567890' this.resourceFileName = `${this.basePath}/.project-sync-state` @@ -175,7 +175,7 @@ describe('ResourceStateManager', function () { this.allFiles = [ this.resources[0].path, this.resources[1].path, - this.resources[2].path + this.resources[2].path, ] return this.ResourceStateManager.checkResourceFiles( this.resources, @@ -220,7 +220,7 @@ describe('ResourceStateManager', function () { this.allFiles = [ this.resources[0].path, this.resources[1].path, - this.resources[2].path + this.resources[2].path, ] return this.ResourceStateManager.checkResourceFiles( this.resources, diff --git a/services/clsi/test/unit/js/ResourceWriterTests.js b/services/clsi/test/unit/js/ResourceWriterTests.js index 267fc2c3a8..4d69f557f6 100644 --- a/services/clsi/test/unit/js/ResourceWriterTests.js +++ b/services/clsi/test/unit/js/ResourceWriterTests.js @@ -27,7 +27,7 @@ describe('ResourceWriter', function () { requires: { fs: (this.fs = { mkdir: sinon.stub().callsArg(1), - unlink: sinon.stub().callsArg(1) + unlink: sinon.stub().callsArg(1), }), './ResourceStateManager': (this.ResourceStateManager = {}), wrench: (this.wrench = {}), @@ -43,9 +43,9 @@ describe('ResourceWriter', function () { } Timer.initClass() return Timer - })()) - }) - } + })()), + }), + }, }) this.project_id = 'project-id-123' this.basePath = '/path/to/write/files/to' @@ -62,7 +62,7 @@ describe('ResourceWriter', function () { { project_id: this.project_id, syncState: (this.syncState = '0123456789abcdef'), - resources: this.resources + resources: this.resources, }, this.basePath, this.callback @@ -76,7 +76,7 @@ describe('ResourceWriter', function () { }) it('should write each resource to disk', function () { - return Array.from(this.resources).map((resource) => + return Array.from(this.resources).map(resource => this.ResourceWriter._writeResourceToDisk .calledWith(this.project_id, resource, this.basePath) .should.equal(true) @@ -111,7 +111,7 @@ describe('ResourceWriter', function () { project_id: this.project_id, syncType: 'incremental', syncState: (this.syncState = '1234567890abcdef'), - resources: this.resources + resources: this.resources, }, this.basePath, this.callback @@ -137,7 +137,7 @@ describe('ResourceWriter', function () { }) it('should write each resource to disk', function () { - return Array.from(this.resources).map((resource) => + return Array.from(this.resources).map(resource => this.ResourceWriter._writeResourceToDisk .calledWith(this.project_id, resource, this.basePath) .should.equal(true) @@ -160,7 +160,7 @@ describe('ResourceWriter', function () { project_id: this.project_id, syncType: 'incremental', syncState: (this.syncState = '1234567890abcdef'), - resources: this.resources + resources: this.resources, }, this.basePath, this.callback @@ -183,58 +183,57 @@ describe('ResourceWriter', function () { this.output_files = [ { path: 'output.pdf', - type: 'pdf' + type: 'pdf', }, { path: 'extra/file.tex', - type: 'tex' + type: 'tex', }, { path: 'extra.aux', - type: 'aux' + type: 'aux', }, { - path: 'cache/_chunk1' + path: 'cache/_chunk1', }, { path: 'figures/image-eps-converted-to.pdf', - type: 'pdf' + type: 'pdf', }, { path: 'foo/main-figure0.md5', - type: 'md5' + type: 'md5', }, { path: 'foo/main-figure0.dpth', - type: 'dpth' + type: 'dpth', }, { path: 'foo/main-figure0.pdf', - type: 'pdf' + type: 'pdf', }, { path: '_minted-main/default-pyg-prefix.pygstyle', - type: 'pygstyle' + type: 'pygstyle', }, { path: '_minted-main/default.pygstyle', - type: 'pygstyle' + type: 'pygstyle', }, { - path: - '_minted-main/35E248B60965545BD232AE9F0FE9750D504A7AF0CD3BAA7542030FC560DFCC45.pygtex', - type: 'pygtex' + path: '_minted-main/35E248B60965545BD232AE9F0FE9750D504A7AF0CD3BAA7542030FC560DFCC45.pygtex', + type: 'pygtex', }, { path: '_markdown_main/30893013dec5d869a415610079774c2f.md.tex', - type: 'tex' + type: 'tex', }, { - path: 'output.stdout' + path: 'output.stdout', }, { - path: 'output.stderr' - } + path: 'output.stderr', + }, ] this.resources = 'mock-resources' this.OutputFileFinder.findOutputFiles = sinon @@ -368,7 +367,7 @@ describe('ResourceWriter', function () { this.resource = { path: 'main.tex', url: 'http://www.example.com/main.tex', - modified: Date.now() + modified: Date.now(), } this.UrlCache.downloadUrlToFile = sinon .stub() @@ -413,7 +412,7 @@ describe('ResourceWriter', function () { beforeEach(function () { this.resource = { path: 'main.tex', - content: 'Hello world' + content: 'Hello world', } this.fs.writeFile = sinon.stub().callsArg(2) this.fs.mkdir = sinon.stub().callsArg(2) @@ -451,7 +450,7 @@ describe('ResourceWriter', function () { beforeEach(function () { this.resource = { path: '../../main.tex', - content: 'Hello world' + content: 'Hello world', } this.fs.writeFile = sinon.stub().callsArg(2) return this.ResourceWriter._writeResourceToDisk( diff --git a/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js b/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js index 86e279e687..dd67506bfc 100644 --- a/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js +++ b/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js @@ -23,16 +23,16 @@ describe('StaticServerForbidSymlinks', function () { beforeEach(function () { this.settings = { path: { - compilesDir: '/compiles/here' - } + compilesDir: '/compiles/here', + }, } this.fs = {} this.ForbidSymlinks = SandboxedModule.require(modulePath, { requires: { '@overleaf/settings': this.settings, - fs: this.fs - } + fs: this.fs, + }, }) this.dummyStatic = (rootDir, options) => (req, res, next) => @@ -46,8 +46,8 @@ describe('StaticServerForbidSymlinks', function () { ) this.req = { params: { - project_id: '12345' - } + project_id: '12345', + }, } this.res = {} diff --git a/services/clsi/test/unit/js/TikzManager.js b/services/clsi/test/unit/js/TikzManager.js index 30a3807552..8e01194955 100644 --- a/services/clsi/test/unit/js/TikzManager.js +++ b/services/clsi/test/unit/js/TikzManager.js @@ -21,8 +21,8 @@ describe('TikzManager', function () { requires: { './ResourceWriter': (this.ResourceWriter = {}), './SafeReader': (this.SafeReader = {}), - fs: (this.fs = {}) - } + fs: (this.fs = {}), + }, })) }) diff --git a/services/clsi/test/unit/js/UrlCacheTests.js b/services/clsi/test/unit/js/UrlCacheTests.js index 32e175f7ca..33decd3418 100644 --- a/services/clsi/test/unit/js/UrlCacheTests.js +++ b/services/clsi/test/unit/js/UrlCacheTests.js @@ -25,10 +25,10 @@ describe('UrlCache', function () { './db': {}, './UrlFetcher': (this.UrlFetcher = {}), '@overleaf/settings': (this.Settings = { - path: { clsiCacheDir: '/cache/dir' } + path: { clsiCacheDir: '/cache/dir' }, }), - fs: (this.fs = { copyFile: sinon.stub().yields() }) - } + fs: (this.fs = { copyFile: sinon.stub().yields() }), + }, })) }) @@ -339,7 +339,7 @@ describe('UrlCache', function () { }) it('should clear the cache for each url in the project', function () { - return Array.from(this.urls).map((url) => + return Array.from(this.urls).map(url => this.UrlCache._clearUrlFromCache .calledWith(this.project_id, url) .should.equal(true) diff --git a/services/clsi/test/unit/js/UrlFetcherTests.js b/services/clsi/test/unit/js/UrlFetcherTests.js index ac94540f70..8e79bced73 100644 --- a/services/clsi/test/unit/js/UrlFetcherTests.js +++ b/services/clsi/test/unit/js/UrlFetcherTests.js @@ -21,17 +21,17 @@ describe('UrlFetcher', function () { return (this.UrlFetcher = SandboxedModule.require(modulePath, { requires: { request: { - defaults: (this.defaults = sinon.stub().returns((this.request = {}))) + defaults: (this.defaults = sinon.stub().returns((this.request = {}))), }, fs: (this.fs = {}), '@overleaf/settings': (this.settings = { apis: { clsiPerf: { - host: 'localhost:3043' - } - } - }) - } + host: 'localhost:3043', + }, + }, + }), + }, })) }) describe('pipeUrlToFileWithRetry', function () { @@ -41,7 +41,7 @@ describe('UrlFetcher', function () { it('should call pipeUrlToFile', function (done) { this.UrlFetcher.pipeUrlToFile.callsArgWith(2) - this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, (err) => { + this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, err => { expect(err).to.equal(undefined) this.UrlFetcher.pipeUrlToFile.called.should.equal(true) done() @@ -51,7 +51,7 @@ describe('UrlFetcher', function () { it('should call pipeUrlToFile multiple times on error', function (done) { const error = new Error("couldn't download file") this.UrlFetcher.pipeUrlToFile.callsArgWith(2, error) - this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, (err) => { + this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, err => { expect(err).to.equal(error) this.UrlFetcher.pipeUrlToFile.callCount.should.equal(3) done() @@ -61,7 +61,7 @@ describe('UrlFetcher', function () { it('should call pipeUrlToFile twice if only 1 error', function (done) { this.UrlFetcher.pipeUrlToFile.onCall(0).callsArgWith(2, 'error') this.UrlFetcher.pipeUrlToFile.onCall(1).callsArgWith(2) - this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, (err) => { + this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, err => { expect(err).to.equal(undefined) this.UrlFetcher.pipeUrlToFile.callCount.should.equal(2) done() @@ -181,7 +181,7 @@ describe('UrlFetcher', function () { describe('with non success status code', function () { beforeEach(function (done) { - this.UrlFetcher.pipeUrlToFile(this.url, this.path, (err) => { + this.UrlFetcher.pipeUrlToFile(this.url, this.path, err => { this.callback(err) return done() }) @@ -202,7 +202,7 @@ describe('UrlFetcher', function () { return describe('with error', function () { beforeEach(function (done) { - this.UrlFetcher.pipeUrlToFile(this.url, this.path, (err) => { + this.UrlFetcher.pipeUrlToFile(this.url, this.path, err => { this.callback(err) return done() }) diff --git a/services/clsi/test/unit/lib/pdfjsTests.js b/services/clsi/test/unit/lib/pdfjsTests.js index 72b36a3355..f90c6f4924 100644 --- a/services/clsi/test/unit/lib/pdfjsTests.js +++ b/services/clsi/test/unit/lib/pdfjsTests.js @@ -28,14 +28,14 @@ async function loadContext(example) { const snapshot = blob ? JSON.parse(blob) : null return { size, - snapshot + snapshot, } } async function backFillSnapshot(example, size) { const table = await parseXrefTable(pdfPath(example), size, () => {}) await fs.promises.mkdir(Path.dirname(snapshotPath(example)), { - recursive: true + recursive: true, }) await fs.promises.writeFile( snapshotPath(example), From 0e5f5afe7990cc51a196d0f878613a6e32229985 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 13 Jul 2021 12:21:14 +0100 Subject: [PATCH 750/754] [misc] temporary override a few new/changed eslint rules --- services/clsi/.eslintrc | 15 ++++++++++++++- services/clsi/app/js/RequestParser.js | 1 - services/clsi/test/load/js/loadTest.js | 3 --- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/services/clsi/.eslintrc b/services/clsi/.eslintrc index 1c14f50efe..a97661b15f 100644 --- a/services/clsi/.eslintrc +++ b/services/clsi/.eslintrc @@ -5,7 +5,7 @@ "extends": [ "eslint:recommended", "standard", - "prettier", + "prettier" ], "parserOptions": { "ecmaVersion": 2018 @@ -20,6 +20,19 @@ "mocha": true }, "rules": { + // TODO(das7pad): remove overrides after fixing all the violations manually (https://github.com/overleaf/issues/issues/3882#issuecomment-878999671) + // START of temporary overrides + "array-callback-return": "off", + "no-dupe-else-if": "off", + "no-var": "off", + "no-empty": "off", + "node/handle-callback-err": "off", + "no-loss-of-precision": "off", + "node/no-callback-literal": "off", + "node/no-path-concat": "off", + "prefer-regex-literals": "off", + // END of temporary overrides + // Swap the no-unused-expressions rule with a more chai-friendly one "no-unused-expressions": 0, "chai-friendly/no-unused-expressions": "error", diff --git a/services/clsi/app/js/RequestParser.js b/services/clsi/app/js/RequestParser.js index cb097f64f4..04502e422c 100644 --- a/services/clsi/app/js/RequestParser.js +++ b/services/clsi/app/js/RequestParser.js @@ -4,7 +4,6 @@ no-throw-literal, no-unused-vars, no-useless-escape, - standard/no-callback-literal, valid-typeof, */ // TODO: This file was created by bulk-decaffeinate. diff --git a/services/clsi/test/load/js/loadTest.js b/services/clsi/test/load/js/loadTest.js index 96f15a4cbe..f5e1fce063 100644 --- a/services/clsi/test/load/js/loadTest.js +++ b/services/clsi/test/load/js/loadTest.js @@ -1,6 +1,3 @@ -/* eslint-disable - standard/no-callback-literal, -*/ // TODO: This file was created by bulk-decaffeinate. // Fix any style issues and re-enable lint. /* From bc401ac44eb2b6515decc2ce158effe5a29bcc61 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 13 Jul 2021 12:26:36 +0100 Subject: [PATCH 751/754] [misc] upgrade node version to latest v12 LTS version 12.22.3 --- services/clsi/.nvmrc | 2 +- services/clsi/Dockerfile | 2 +- services/clsi/buildscript.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/services/clsi/.nvmrc b/services/clsi/.nvmrc index e68b860383..5a80a7e912 100644 --- a/services/clsi/.nvmrc +++ b/services/clsi/.nvmrc @@ -1 +1 @@ -12.21.0 +12.22.3 diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index b02e828a4d..8bd4b9c62f 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/sharelatex/sharelatex-dev-environment -FROM node:12.21.0 as base +FROM node:12.22.3 as base WORKDIR /app COPY install_deps.sh /app diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index e04c135353..6e927a8835 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -4,6 +4,6 @@ clsi --docker-repos=gcr.io/overleaf-ops --env-add= --env-pass-through=TEXLIVE_IMAGE ---node-version=12.21.0 +--node-version=12.22.3 --public-repo=True --script-version=3.11.0 From d1c06d1878ceab2039fba49b971be5ca486cfc66 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Tue, 20 Jul 2021 14:12:42 +0100 Subject: [PATCH 752/754] [perf] trim down install_deps.sh -- install docker cli only --- services/clsi/install_deps.sh | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/services/clsi/install_deps.sh b/services/clsi/install_deps.sh index 49bdc5c963..f0cc386ff5 100755 --- a/services/clsi/install_deps.sh +++ b/services/clsi/install_deps.sh @@ -1,4 +1,24 @@ -/bin/sh -wget -qO- https://get.docker.com/ | sh -apt-get install poppler-utils vim ghostscript --yes -npm rebuild +#!/bin/bash +set -ex + +apt-get update +apt-get install -y \ + apt-transport-https \ + ca-certificates \ + curl \ + gnupg \ + lsb-release + +curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg +echo \ + "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \ + $(lsb_release -cs) stable" \ + > /etc/apt/sources.list.d/docker.list +apt-get update + +apt-get install -y \ + docker-ce-cli \ + poppler-utils \ + ghostscript \ + +rm -rf /var/lib/apt/lists/* From 262793c04f19c5ccec70e6006f36088f16b9ca72 Mon Sep 17 00:00:00 2001 From: Brian Gough <brian.gough@overleaf.com> Date: Wed, 21 Jul 2021 14:53:35 +0100 Subject: [PATCH 753/754] add option for apparmor profile --- services/clsi/app/js/DockerRunner.js | 6 ++++++ services/clsi/config/settings.defaults.js | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js index 5de0586818..28d7636f0a 100644 --- a/services/clsi/app/js/DockerRunner.js +++ b/services/clsi/app/js/DockerRunner.js @@ -270,6 +270,12 @@ const DockerRunner = { ) } + if (Settings.clsi.docker.apparmor_profile != null) { + options.HostConfig.SecurityOpt.push( + `apparmor=${Settings.clsi.docker.apparmor_profile}` + ) + } + if (Settings.clsi.docker.runtime) { options.HostConfig.Runtime = Settings.clsi.docker.runtime } diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index 352c045d27..e36334df1b 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -143,6 +143,15 @@ if (process.env.DOCKER_RUNNER) { process.exit(1) } + if (process.env.APPARMOR_PROFILE) { + try { + module.exports.clsi.docker.apparmor_profile = process.env.APPARMOR_PROFILE + } catch (error) { + console.error(error, 'could not apply apparmor profile setting') + process.exit(1) + } + } + if (process.env.ALLOWED_IMAGES) { try { module.exports.clsi.docker.allowedImages = From 76e749777d95e6096136fad96c92e87d14822329 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann <jakob.ackermann@overleaf.com> Date: Mon, 26 Jul 2021 12:14:56 +0100 Subject: [PATCH 754/754] [misc] make build scripts happy again - move pdf.js tests into test/unit/js - add env override to build script config file - update build scripts --- services/clsi/buildscript.txt | 2 +- services/clsi/package.json | 2 +- services/clsi/test/unit/{lib => js}/pdfjsTests.js | 2 +- .../snapshots => js/snapshots/pdfjs}/asymptote/XrefTable.json | 0 .../snapshots/pdfjs}/biber_bibliography/XrefTable.json | 0 .../snapshots => js/snapshots/pdfjs}/epstopdf/XrefTable.json | 0 .../{lib/snapshots => js/snapshots/pdfjs}/feynmf/XrefTable.json | 0 .../{lib/snapshots => js/snapshots/pdfjs}/feynmp/XrefTable.json | 0 .../snapshots => js/snapshots/pdfjs}/fontawesome/XrefTable.json | 0 .../snapshots/pdfjs}/fontawesome_xelatex/XrefTable.json | 0 .../snapshots => js/snapshots/pdfjs}/glossaries/XrefTable.json | 0 .../snapshots => js/snapshots/pdfjs}/gnuplot/XrefTable.json | 0 .../{lib/snapshots => js/snapshots/pdfjs}/hebrew/XrefTable.json | 0 .../{lib/snapshots => js/snapshots/pdfjs}/knitr/XrefTable.json | 0 .../snapshots => js/snapshots/pdfjs}/knitr_utf8/XrefTable.json | 0 .../snapshots/pdfjs}/latex_compiler/XrefTable.json | 0 .../snapshots/pdfjs}/lualatex_compiler/XrefTable.json | 0 .../snapshots/pdfjs}/makeindex-custom-style/XrefTable.json | 0 .../snapshots => js/snapshots/pdfjs}/makeindex/XrefTable.json | 0 .../{lib/snapshots => js/snapshots/pdfjs}/minted/XrefTable.json | 0 .../snapshots/pdfjs}/multibib_bibliography/XrefTable.json | 0 .../snapshots/pdfjs}/nomenclature/XrefTable.json | 0 .../snapshots/pdfjs}/references_in_include/XrefTable.json | 0 .../snapshots/pdfjs}/simple_bibliography/XrefTable.json | 0 .../snapshots/pdfjs}/subdirectories/XrefTable.json | 0 .../snapshots/pdfjs}/tikz_feynman/XrefTable.json | 0 .../snapshots/pdfjs}/xelatex_compiler/XrefTable.json | 0 27 files changed, 3 insertions(+), 3 deletions(-) rename services/clsi/test/unit/{lib => js}/pdfjsTests.js (97%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/asymptote/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/biber_bibliography/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/epstopdf/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/feynmf/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/feynmp/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/fontawesome/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/fontawesome_xelatex/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/glossaries/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/gnuplot/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/hebrew/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/knitr/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/knitr_utf8/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/latex_compiler/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/lualatex_compiler/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/makeindex-custom-style/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/makeindex/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/minted/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/multibib_bibliography/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/nomenclature/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/references_in_include/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/simple_bibliography/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/subdirectories/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/tikz_feynman/XrefTable.json (100%) rename services/clsi/test/unit/{lib/snapshots => js/snapshots/pdfjs}/xelatex_compiler/XrefTable.json (100%) diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index 6e927a8835..9b755dfe46 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -2,7 +2,7 @@ clsi --data-dirs=cache,compiles,db,output --dependencies= --docker-repos=gcr.io/overleaf-ops ---env-add= +--env-add=ENABLE_PDF_CACHING="true" --env-pass-through=TEXLIVE_IMAGE --node-version=12.22.3 --public-repo=True diff --git a/services/clsi/package.json b/services/clsi/package.json index 4f22ebd729..0b2b011d49 100644 --- a/services/clsi/package.json +++ b/services/clsi/package.json @@ -10,7 +10,7 @@ "start": "node $NODE_APP_OPTIONS app.js", "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", "test:acceptance": "npm run test:acceptance:_run -- --grep=$MOCHA_GREP", - "test:unit:_run": "mocha --recursive --reporter spec $@ test/unit/js test/unit/lib", + "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": "eslint --max-warnings 0 --format unix .", diff --git a/services/clsi/test/unit/lib/pdfjsTests.js b/services/clsi/test/unit/js/pdfjsTests.js similarity index 97% rename from services/clsi/test/unit/lib/pdfjsTests.js rename to services/clsi/test/unit/js/pdfjsTests.js index f90c6f4924..92f10279d2 100644 --- a/services/clsi/test/unit/lib/pdfjsTests.js +++ b/services/clsi/test/unit/js/pdfjsTests.js @@ -3,7 +3,7 @@ const Path = require('path') const { expect } = require('chai') const { parseXrefTable } = require('../../../app/lib/pdfjs/parseXrefTable') const PATH_EXAMPLES = 'test/acceptance/fixtures/examples/' -const PATH_SNAPSHOTS = 'test/unit/lib/snapshots/' +const PATH_SNAPSHOTS = 'test/unit/js/snapshots/pdfjs/' const EXAMPLES = fs.readdirSync(PATH_EXAMPLES) function snapshotPath(example) { diff --git a/services/clsi/test/unit/lib/snapshots/asymptote/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/asymptote/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/asymptote/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/asymptote/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/biber_bibliography/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/biber_bibliography/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/biber_bibliography/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/biber_bibliography/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/epstopdf/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/epstopdf/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/epstopdf/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/epstopdf/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/feynmf/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/feynmf/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/feynmf/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/feynmf/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/feynmp/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/feynmp/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/feynmp/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/feynmp/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/fontawesome/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/fontawesome/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/fontawesome/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/fontawesome/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/fontawesome_xelatex/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/fontawesome_xelatex/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/fontawesome_xelatex/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/fontawesome_xelatex/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/glossaries/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/glossaries/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/glossaries/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/glossaries/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/gnuplot/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/gnuplot/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/gnuplot/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/gnuplot/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/hebrew/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/hebrew/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/hebrew/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/hebrew/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/knitr/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/knitr/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/knitr/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/knitr/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/knitr_utf8/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/knitr_utf8/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/knitr_utf8/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/knitr_utf8/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/latex_compiler/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/latex_compiler/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/latex_compiler/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/latex_compiler/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/lualatex_compiler/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/lualatex_compiler/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/lualatex_compiler/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/lualatex_compiler/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/makeindex-custom-style/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/makeindex-custom-style/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/makeindex-custom-style/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/makeindex-custom-style/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/makeindex/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/makeindex/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/makeindex/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/makeindex/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/minted/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/minted/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/minted/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/minted/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/multibib_bibliography/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/multibib_bibliography/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/multibib_bibliography/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/multibib_bibliography/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/nomenclature/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/nomenclature/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/nomenclature/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/nomenclature/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/references_in_include/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/references_in_include/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/references_in_include/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/references_in_include/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/simple_bibliography/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/simple_bibliography/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/simple_bibliography/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/simple_bibliography/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/subdirectories/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/subdirectories/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/subdirectories/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/subdirectories/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/tikz_feynman/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/tikz_feynman/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/tikz_feynman/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/tikz_feynman/XrefTable.json diff --git a/services/clsi/test/unit/lib/snapshots/xelatex_compiler/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/xelatex_compiler/XrefTable.json similarity index 100% rename from services/clsi/test/unit/lib/snapshots/xelatex_compiler/XrefTable.json rename to services/clsi/test/unit/js/snapshots/pdfjs/xelatex_compiler/XrefTable.json